This commit contains the latest updates from the generic Website template, which move the toolchain to Swift 6.4, gate analytics and structured data behind traits, and rework page routing: - Updated the Swift toolchain for all the package dependencies and the Website service to version 6.4. (345b305) - Adapted all the existing middlewares in the Infrastructure package to Swift 6.4. (26008df) - Moved test resource teardown in the Persistence and the Website library into defer blocks. (27984a9) - Gated analytics behind a trait and reworked structured data in the Infrastructure package. (c7d4769) - Enabled the Analytics trait in the Website service. (f6a6bbd) - Reworked page routing in the Website service. (d1cb2c1) - Gated the structured data features in the Infrastructure package behind a trait. (f0a8791) - Enabled the StructuredData trait of the Infrastructure package in the Website service. (bbfdeb3) - Fixed the asset pin and a build warning in the Dockerfile of the Website service. (6ed0f9b) The root README change in 345b305 was left out, as this project no longer carries that file. From d1cb2c1, the RootController rework was left out, as the prototype serves a session-aware landing page and routes its other pages through their own controllers; the view-event reporter was appended to the prototype's shared script instead. The prototype code was adapted to the stricter Swift 6.4 settings: `String(format:)` was swapped for safe formatting, the NoIndex middleware takes a `@concurrent` next handler, and the tests flagged by warnings-as-errors were tightened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
4.1 KiB
Swift
143 lines
4.1 KiB
Swift
import Hummingbird
|
|
import HummingbirdTesting
|
|
import Infrastructure
|
|
import Logging
|
|
import NIOCore
|
|
import Persistence
|
|
import Testing
|
|
|
|
@testable import WebsiteLibrary
|
|
|
|
@Suite(
|
|
"HealthController controller",
|
|
.tags(.controller)
|
|
)
|
|
struct HealthControllerTests {
|
|
|
|
// MARK: Functional tests
|
|
|
|
@Test
|
|
func `serves the status payload at the health path`() async throws {
|
|
try await app(
|
|
probe: nil
|
|
).test(.router) { client in
|
|
try await client.execute(
|
|
uri: "/health",
|
|
method: .get
|
|
) { response in
|
|
let body = String(buffer: response.body)
|
|
|
|
#expect(response.status == .ok)
|
|
#expect(response.headers[.contentType] == "application/json")
|
|
#expect(body == #"{"status":"ok"}"#)
|
|
#expect(response.headers[.robotsTag] == "noindex")
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `serves ready at the readiness path when the database is reachable`() async throws {
|
|
let service = try Service(
|
|
driver: .inMemory,
|
|
logger: Logger(label: "test")
|
|
)
|
|
let fluent = service()
|
|
|
|
defer {
|
|
try? await fluent.shutdown()
|
|
}
|
|
|
|
try await app(
|
|
probe: Probe(fluent: fluent)
|
|
).test(.router) { client in
|
|
try await client.execute(
|
|
uri: "/health/ready",
|
|
method: .get
|
|
) { response in
|
|
let body = String(buffer: response.body)
|
|
|
|
#expect(response.status == .ok)
|
|
#expect(response.headers[.contentType] == "application/json")
|
|
#expect(body == #"{"status":"ready"}"#)
|
|
#expect(response.headers[.robotsTag] == "noindex")
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `serves unavailable at the readiness path when the database is unreachable`() async throws {
|
|
// Port 1 on the loopback interface has nothing listening, so the probe's connection is refused
|
|
// immediately instead of timing out.
|
|
let service = try Service(
|
|
driver: .postgres(
|
|
.init(
|
|
host: "127.0.0.1",
|
|
port: 1,
|
|
name: "unreachable",
|
|
username: "nobody",
|
|
password: "nothing",
|
|
tls: .off,
|
|
maxConnectionsPerEventLoop: 1,
|
|
poolTimeout: .seconds(10)
|
|
)
|
|
),
|
|
logger: Logger(label: "test")
|
|
)
|
|
let fluent = service()
|
|
|
|
defer {
|
|
try? await fluent.shutdown()
|
|
}
|
|
|
|
try await app(
|
|
probe: Probe(fluent: fluent)
|
|
).test(.router) { client in
|
|
try await client.execute(
|
|
uri: "/health/ready",
|
|
method: .get
|
|
) { response in
|
|
let body = String(buffer: response.body)
|
|
|
|
#expect(response.status == .serviceUnavailable)
|
|
#expect(response.headers[.contentType] == "application/json")
|
|
#expect(body == #"{"status":"unavailable"}"#)
|
|
#expect(response.headers[.robotsTag] == "noindex")
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `does not serve the readiness path without a probe`() async throws {
|
|
try await app(probe: nil).test(.router) { client in
|
|
try await client.execute(
|
|
uri: "/health/ready",
|
|
method: .get
|
|
) { response in
|
|
#expect(response.status == .notFound)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private extension HealthControllerTests {
|
|
|
|
/// Builds a test application serving the ``HealthController`` routes for the given probe.
|
|
/// - Parameter probe: the probe supplied to the controller, or `nil` for liveness only.
|
|
/// - Returns: the configured test application.
|
|
func app(
|
|
probe: Probe?
|
|
) -> some ApplicationProtocol {
|
|
Application(router: {
|
|
let router = Router()
|
|
|
|
router.addRoutes(HealthController<BasicRequestContext>(probe: probe).routes)
|
|
|
|
return router
|
|
}())
|
|
}
|
|
|
|
}
|