Initial commit.

This commit is contained in:
2026-08-19 23:19:08 +02:00
commit 22e737d9c2
153 changed files with 11680 additions and 0 deletions
@@ -0,0 +1,82 @@
import Infrastructure
import Testing
@testable import WebsiteLibrary
@Suite(
"StaticFile enumeration",
.tags(.enumeration)
)
struct StaticFileTests {
// MARK: Type aliases
typealias File = StaticFile
// MARK: Computed tests
@Test(arguments: zip(
File.allCases,
Self.fileExtensions
))
func `file extensions`(
for file: File,
expects extensions: [AssetExtension]
) {
#expect(file.fileExtensions == extensions)
}
@Test(arguments: zip(
File.allCases,
Self.fileNames
))
func `file name`(
for file: File,
expects fileName: String
) {
#expect(file.fileName == fileName)
}
// MARK: CaseIterable tests
@Test
func `all cases`() {
#expect(File.allCases.count == 11)
}
}
// MARK: - Helpers
private extension StaticFileTests {
// MARK: Constants
static let fileExtensions: [[AssetExtension]] = [
[.png],
[.ico],
[.svg],
[.png],
[.png],
[.css, .js],
[.css, .js],
[.txt],
[.css, .js],
[.webmanifest],
[.xml]
]
static let fileNames: [String] = [
"apple-touch-icon",
"favicon",
"icon",
"icon-192",
"icon-512",
"index",
"not-found",
"robots",
"shared",
"site",
"sitemap"
]
}
@@ -0,0 +1,49 @@
import Elementary
import Foundation
import Testing
@testable import WebsiteLibrary
@Suite(
"IndexPage page",
.tags(.page)
)
struct IndexPageTests {
// MARK: Functional tests
@Test
func `renders its markup`() {
let html = IndexPage(
locale: .init(identifier: "en")
).render()
#expect(html.contains("<!DOCTYPE html>"))
#expect(html.contains(#"lang="en""#))
#expect(html.contains("/css/shared.css"))
#expect(html.contains("/css/index.css"))
#expect(html.contains("/favicon.ico"))
#expect(html.contains("/icon.svg"))
#expect(html.contains("/apple-touch-icon.png"))
#expect(html.contains("/site.webmanifest"))
#expect(html.contains(#"media="(prefers-color-scheme: dark)""#))
#expect(html.contains("Hello world!"))
#expect(html.contains("/js/shared.js"))
#expect(html.contains("/js/index.js"))
}
@Test
func `renders versioned asset URLs when given a version`() {
let html = IndexPage(
locale: .init(identifier: "en"),
assetVersion: "0123456789abcdef"
).render()
#expect(html.contains("/css/shared.css?v=0123456789abcdef"))
#expect(html.contains("/css/index.css?v=0123456789abcdef"))
#expect(html.contains("/js/shared.js?v=0123456789abcdef"))
#expect(html.contains("/js/index.js?v=0123456789abcdef"))
#expect(html.contains("/favicon.ico?v=0123456789abcdef"))
}
}
@@ -0,0 +1,31 @@
import Elementary
import Foundation
import Testing
@testable import WebsiteLibrary
@Suite(
"NotFoundPage page",
.tags(.page)
)
struct NotFoundPageTests {
// MARK: Functional tests
@Test
func `renders its markup`() {
let html = NotFoundPage(
locale: .init(identifier: "en")
).render()
#expect(html.contains("<!DOCTYPE html>"))
#expect(html.contains(#"lang="en""#))
#expect(html.contains("Page Not Found"))
#expect(html.contains("Sorry, but the page you were trying to view does not exist."))
#expect(html.contains("/css/shared.css"))
#expect(html.contains("/css/not-found.css"))
#expect(html.contains("/js/not-found.js"))
#expect(html.contains("/js/shared.js"))
}
}
@@ -0,0 +1,146 @@
import Hummingbird
import HummingbirdTesting
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"}"#)
}
}
}
@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()
do {
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"}"#)
}
}
} catch {
try? await fluent.shutdown()
throw error
}
try await fluent.shutdown()
}
@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()
do {
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"}"#)
}
}
} catch {
try? await fluent.shutdown()
throw error
}
try await fluent.shutdown()
}
@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
}())
}
}
@@ -0,0 +1,202 @@
import Hummingbird
import HummingbirdTesting
import Infrastructure
import NIOCore
import Testing
@testable import WebsiteLibrary
@Suite(
"RootController controller",
.tags(.controller)
)
struct RootControllerTests {
// MARK: Constants
private let app: Application = .init(router: {
let router = Router(context: WebsiteRequestContext.self)
router.addMiddleware {
LocalizationMiddleware()
}
router.addRoutes(RootController<WebsiteRequestContext>().routes)
return router
}())
// MARK: Functional tests
@Test
func `serves the landing page at the root path`() async throws {
try await app.test(.router) { client in
try await client.execute(
uri: "/",
method: .get
) { response in
let body = String(buffer: response.body)
#expect(response.status == .ok)
#expect(response.headers[.contentType] == "text/html; charset=utf-8")
#expect(response.headers[.contentLanguage] == "en")
#expect(response.headers[.vary] == "Accept-Language")
#expect(body.contains("Hello world!"))
}
}
}
@Test
func `serves the landing page with revalidation headers`() async throws {
try await app.test(.router) { client in
try await client.execute(
uri: "/",
method: .get
) { response in
let eTag = try #require(response.headers[.eTag])
#expect(eTag.hasPrefix(#"W/""#))
#expect(response.headers[.cacheControl] == "public, no-cache")
}
}
}
@Test
func `revalidates a matching conditional request with a 304`() async throws {
try await app.test(.router) { client in
let eTag = try await client.execute(
uri: "/",
method: .get
) { response in
try #require(response.headers[.eTag])
}
try await client.execute(
uri: "/",
method: .get,
headers: [.ifNoneMatch: eTag]
) { response in
#expect(response.status == .notModified)
#expect(response.headers[.eTag] == eTag)
#expect(response.body.readableBytes == 0)
}
}
}
@Test
func `serves the full page to a non-matching conditional request`() async throws {
try await app.test(.router) { client in
try await client.execute(
uri: "/",
method: .get,
headers: [.ifNoneMatch: #"W/"0123456789abcdef""#]
) { response in
let body = String(buffer: response.body)
#expect(response.status == .ok)
#expect(body.contains("Hello world!"))
}
}
}
@Test
func `renders versioned asset URLs when given a version`() async throws {
try await app(
assetVersion: "0123456789abcdef"
).test(.router) { client in
try await client.execute(
uri: "/",
method: .get
) { response in
let body = String(buffer: response.body)
#expect(body.contains("/css/index.css?v=0123456789abcdef"))
#expect(body.contains("/js/index.js?v=0123456789abcdef"))
}
}
}
@Test
func `renders unversioned asset URLs by default`() async throws {
try await app.test(.router) { client in
try await client.execute(
uri: "/",
method: .get
) { response in
let body = String(buffer: response.body)
#expect(body.contains(#"href="/css/index.css""#))
#expect(!body.contains("?v="))
}
}
}
@Test
func `embeds no analytics tracker by default`() async throws {
try await app.test(.router) { client in
try await client.execute(
uri: "/",
method: .get
) { response in
let body = String(buffer: response.body)
#expect(!body.contains("data-website-id"))
#expect(!body.contains("analytics"))
}
}
}
@Test
func `embeds the analytics tracker when one is configured`() async throws {
try await app(
analytics: .init(
scriptURL: "https://analytics.example.com/script",
websiteID: "0000-website-id",
domains: "example.com"
)
).test(.router) { client in
try await client.execute(
uri: "/",
method: .get
) { response in
let body = String(buffer: response.body)
#expect(body.contains(#"<link rel="preconnect" href="https://analytics.example.com">"#))
#expect(body.contains(#"<script defer src="https://analytics.example.com/script" data-website-id="0000-website-id" data-domains="example.com""#))
}
}
}
}
// MARK: - Helpers
private extension RootControllerTests {
// MARK: Methods
/// Builds an application whose root controller appends the given version token to the landing
/// page's asset URLs and embeds the given analytics tracker.
/// - Parameters:
/// - assetVersion: the version token appended to the page's asset URLs.
/// - analytics: the analytics tracker the landing page embeds, or `nil` (the default) to omit it.
/// - Returns: the configured application.
func app(
assetVersion: String? = nil,
analytics: Analytics? = nil
) -> some ApplicationProtocol {
let router = Router(context: WebsiteRequestContext.self)
router.addMiddleware {
LocalizationMiddleware()
}
router.addRoutes(RootController<WebsiteRequestContext>(
assetVersion: assetVersion,
analytics: analytics
).routes)
return Application(router: router)
}
}