Tweaks and fixes throughout the project (#27)
This PR contains the work done to do a little bit of housekeeping pass across all packages and the Website service. To provide further details about the work: * Refreshed the READMEs and source documentation to match the current code; * Tagged every test case consistently across the Infrastructure, Localization, Persistence, and Website test targets; * Removed Website middleware tests now covered by Infrastructure's own suite; * Conformed the `PrepareDB` method to Sendable; * Relaxes the production Compose DATABASE_TLS default from require to prefer; * Added Persistence test verifying the prefer posture falls back to plaintext connections. Reviewed-on: rock-n-code/loud-amsterdam#27 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit is contained in:
@@ -3,7 +3,6 @@ The shared [Hummingbird](https://github.com/hummingbird-project/hummingbird) too
|
||||
|
||||
## Overview
|
||||
The package provides, grouped by role:
|
||||
|
||||
| Role | Types |
|
||||
| --- | --- |
|
||||
| Routing | `RouterController`, `RouteCollectionBuilder`, the `addController` extension on `RouterMethods` |
|
||||
@@ -11,23 +10,36 @@ The package provides, grouped by role:
|
||||
| Pages and assets | `Page`, `Asset`, `AssetExtension`, `FingerprintAssets` |
|
||||
| Responses | `CachedHTMLResponse`, `LocalizedHTMLCollectionResponse` |
|
||||
| Contexts | `LocalizedRequestContext` |
|
||||
| Constants | The `HTTPField.Name` header names, `Int.RateLimit` limits, and `String.Security` header values the middlewares default to |
|
||||
|
||||
## Design rules
|
||||
The package holds only what every service can reuse; anything a service owns is injected, never referenced:
|
||||
|
||||
- **No site-specific content.** No page markup, no asset catalog, no `Bundle.module` lookups. A type that needs a service's content takes it as a parameter: the `bundle:` whose String Catalog names the supported languages (`LocalizationMiddleware`, `LocalizedHTMLCollectionResponse`, `NotFoundMiddleware`), the `document:` closure that builds a page for a locale, and the `metadata` requirement through which a `Page` conformer supplies its icon links and theme colors.
|
||||
- **Services fill the gaps once, via extensions.** A service restores its convenient call sites with retroactive extensions — the Website's `Page+Defaults`, `LocalizationMiddleware+Defaults`, and `NotFoundMiddleware+Defaults` are the pattern to follow.
|
||||
- **Method structs.** Single-operation types such as `FingerprintAssets` hold their lifetime-fixed configuration in `init` and take only per-call inputs in `callAsFunction`.
|
||||
|
||||
## Layout
|
||||
Sources are split by visibility, then by kind, one type per file:
|
||||
```
|
||||
Sources/
|
||||
├── Public/ public API
|
||||
│ ├── Builders/ RouteCollectionBuilder
|
||||
│ ├── Enumerations/ AssetExtension
|
||||
│ ├── Extensions/ addController, plus the default header names and values
|
||||
│ ├── Methods/ FingerprintAssets
|
||||
│ ├── Middlewares/ the five HTTP middlewares
|
||||
│ ├── Protocols/ Asset, LocalizedRequestContext, Page, RouterController
|
||||
│ └── Responses/ CachedHTMLResponse, LocalizedHTMLCollectionResponse
|
||||
└── Internal/
|
||||
└── Types/ implementation details (FNV1aHash)
|
||||
Tests/
|
||||
├── Cases/ the test suites, mirroring the Sources/ layout
|
||||
├── Catalogs/ the String Catalog fixture, copied verbatim so it loads on Linux
|
||||
└── Utils/ stubs (StubAsset, StubPage, …) and the suite Tag constants
|
||||
```
|
||||
|
||||
```
|
||||
Sources/Public/<Kind>/ public API (Protocols, Middlewares, Responses, …)
|
||||
Sources/Internal/<Kind>/ implementation details (e.g. FNV1aHash)
|
||||
Tests/Cases/… mirrors the source layout
|
||||
Tests/Utils/… stubs and test-only extensions
|
||||
```
|
||||
## Testing
|
||||
Every suite carries a tag naming the kind of API it exercises — `.asset`, `.extension`, `.middleware`, `.protocol`, or `.type`, declared in `Tests/Utils/Extensions/Tag+Constants.swift` — so test plans and result summaries can slice the run by kind. A new suite must adopt the tag matching its subject (or add a tag there if none fits).
|
||||
|
||||
## Requirements
|
||||
- Swift 6.3 toolchain (`swift-tools-version:6.3`).
|
||||
|
||||
@@ -2,9 +2,8 @@ import Foundation
|
||||
|
||||
/// Hashes bytes with the FNV-1a 64-bit algorithm.
|
||||
///
|
||||
/// The hash is stable across processes and platforms, which `Hasher` deliberately is not, so it
|
||||
/// suits values that must agree between instances and survive restarts: the asset version token
|
||||
/// (``FingerprintAssets``) and the entity tags of the pre-rendered pages (`CachedHTMLResponse`).
|
||||
/// The hash is stable across processes and platforms, which `Hasher` deliberately is not, so it suits values that must agree between instances and survive
|
||||
/// restarts: the asset version token (``FingerprintAssets``) and the entity tags of the pre-rendered pages (`CachedHTMLResponse`).
|
||||
/// It is not cryptographic — a collision only risks serving a stale cached asset, not security.
|
||||
struct FNV1aHash {
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import Hummingbird
|
||||
|
||||
/// A result builder that collects the route collections of ``RouterController`` values into a stack.
|
||||
///
|
||||
/// Mirrors the `MiddlewareFixedTypeBuilder` Hummingbird uses for `addMiddleware`, letting
|
||||
/// controllers be listed declaratively rather than having their routes added one statement at a time.
|
||||
/// Mirrors the `MiddlewareFixedTypeBuilder` Hummingbird uses for `addMiddleware`, letting controllers be listed declaratively rather than having
|
||||
/// their routes added one statement at a time.
|
||||
@resultBuilder
|
||||
public enum RouteCollectionBuilder<Context: RequestContext> {
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/// A file extension used by an ``Asset``.
|
||||
///
|
||||
/// Each case's raw value is the extension itself (e.g. `"css"`), which an asset appends to its
|
||||
/// file name when resolving paths.
|
||||
/// Each case's raw value is the extension itself (e.g. `"css"`), which an asset appends to its file name when resolving paths.
|
||||
public enum AssetExtension: String, Sendable {
|
||||
/// A Cascading Style Sheets file.
|
||||
case css
|
||||
|
||||
+2
-4
@@ -4,8 +4,7 @@ public extension RouterMethods {
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Adds the routes of ``RouterController`` values to the router using the
|
||||
/// ``RouteCollectionBuilder`` result builder.
|
||||
/// Adds the routes of ``RouterController`` values to the router using the ``RouteCollectionBuilder`` result builder.
|
||||
///
|
||||
/// Mirrors `addMiddleware`, letting controllers be listed declaratively:
|
||||
///
|
||||
@@ -16,8 +15,7 @@ public extension RouterMethods {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Each controller's route collection is added at the router's root, exactly as a
|
||||
/// sequence of `addRoutes(_:)` calls would.
|
||||
/// Each controller's route collection is added at the router's root, exactly as a sequence of `addRoutes(_:)` calls would.
|
||||
/// - Parameter build: the controller stack result builder.
|
||||
/// - Returns: the router, so calls can be chained.
|
||||
@discardableResult
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
extension String {
|
||||
/// A namespace for the security headers' default configuration values.
|
||||
///
|
||||
/// `Strict-Transport-Security` is intentionally absent: it is only safe over HTTPS and is
|
||||
/// "sticky" in browsers, so it stays off unless explicitly configured in production.
|
||||
/// `Strict-Transport-Security` is intentionally absent: it is only safe over HTTPS and is "sticky" in browsers, so it stays off unless explicitly
|
||||
/// configured in production.
|
||||
public enum Security {
|
||||
/// The default `Content-Security-Policy`.
|
||||
///
|
||||
/// Restricts every resource to the site's own origin (`default-src 'self'`), blocks plugins
|
||||
/// (`object-src 'none'`), pins the document base URL (`base-uri 'self'`), and forbids framing
|
||||
/// (`frame-ancestors 'none'`). No inline-style exception is included, so pages must link
|
||||
/// external stylesheets.
|
||||
/// Restricts every resource to the site's own origin (`default-src 'self'`), blocks plugins (`object-src 'none'`), pins the document
|
||||
/// base URL (`base-uri 'self'`), and forbids framing (`frame-ancestors 'none'`). No inline-style exception is included, so pages must
|
||||
/// link external stylesheets.
|
||||
public static let contentSecurityPolicy = "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
|
||||
/// The default `X-Content-Type-Options` (disables MIME sniffing).
|
||||
public static let contentTypeOptions = "nosniff"
|
||||
|
||||
@@ -28,9 +28,8 @@ public struct FingerprintAssets: Sendable {
|
||||
|
||||
/// Fingerprints the static files under the given directory.
|
||||
///
|
||||
/// A file that cannot be read is reported to the ``logger`` and left out of the token, so its
|
||||
/// later changes would not bust caches — a warning there usually points at a permissions
|
||||
/// problem in the deployment.
|
||||
/// A file that cannot be read is reported to the ``logger`` and left out of the token, so its later changes would not bust caches — a warning there
|
||||
/// usually points at a permissions problem in the deployment.
|
||||
/// - Parameter path: the directory the static files are served from.
|
||||
/// - Returns: the version token, or `nil` when the directory holds no readable files (asset URLs are then left unversioned).
|
||||
public func callAsFunction(
|
||||
@@ -42,10 +41,9 @@ public struct FingerprintAssets: Sendable {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The path-based enumerator yields paths relative to the directory, so the token depends
|
||||
// only on the directory's contents — never on where the directory itself lives (the
|
||||
// URL-based enumerator standardizes symlinked bases, e.g. `/var/…` to `/private/var/…`,
|
||||
// which would leak the absolute path into the hash).
|
||||
// The path-based enumerator yields paths relative to the directory, so the token depends only on the
|
||||
// directory's contents — never on where the directory itself lives (the URL-based enumerator standardizes
|
||||
// symlinked bases, e.g. `/var/…` to `/private/var/…`, which would leak the absolute path into the hash).
|
||||
var files: [String] = []
|
||||
|
||||
while let relativePath = enumerated.nextObject() as? String {
|
||||
|
||||
@@ -5,12 +5,11 @@ import Localization
|
||||
|
||||
/// Resolves the visitor's preferred language and records it on the request context.
|
||||
///
|
||||
/// Placed ahead of the localized responders in the middleware chain, it reads the request's
|
||||
/// `Accept-Language` header, negotiates the best supported match (falling back to the default
|
||||
/// language), and stores it on the context's ``LocalizedRequestContext/language``.
|
||||
/// Placed ahead of the localized responders in the middleware chain, it reads the request's `Accept-Language` header, negotiates the best supported
|
||||
/// match (falling back to the default language), and stores it on the context's ``LocalizedRequestContext/language``.
|
||||
///
|
||||
/// The request is otherwise passed through untouched — the URL and routing are not affected — so
|
||||
/// each page is served at its existing path and varies its content by header.
|
||||
/// The request is otherwise passed through untouched — the URL and routing are not affected — so each page is served at its existing path and varies its
|
||||
/// content by header.
|
||||
public struct LocalizationMiddleware<Context: LocalizedRequestContext> {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
+16
-23
@@ -3,13 +3,12 @@ import Hummingbird
|
||||
|
||||
/// Stamps a set of security-related HTTP headers onto every response.
|
||||
///
|
||||
/// Placed at (or near) the top of the middleware chain, it adds the configured headers to whatever
|
||||
/// response bubbles back up — the rendered pages, the error page produced by
|
||||
/// ``NotFoundMiddleware``, and every static file served by `FileMiddleware` — so the browser applies
|
||||
/// the strict, hardened interpretation of the content instead of its lenient legacy defaults.
|
||||
/// Placed at (or near) the top of the middleware chain, it adds the configured headers to whatever response bubbles back up — the rendered pages, the
|
||||
/// error page produced by ``NotFoundMiddleware``, and every static file served by `FileMiddleware` — so the browser applies the strict, hardened
|
||||
/// interpretation of the content instead of its lenient legacy defaults.
|
||||
///
|
||||
/// The headers are precomputed once from the ``Configuration`` at initialization and reused for
|
||||
/// every request, so the per-request cost is a handful of header copies.
|
||||
/// The headers are precomputed once from the ``Configuration`` at initialization and reused for every request, so the per-request cost is a handful of
|
||||
/// header copies.
|
||||
public struct SecurityHeadersMiddleware<Context: RequestContext> {
|
||||
|
||||
// MARK: Properties
|
||||
@@ -20,9 +19,8 @@ public struct SecurityHeadersMiddleware<Context: RequestContext> {
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates a security-headers middleware.
|
||||
/// - Parameter configuration: the headers applied to every response. Defaults to a hardened
|
||||
/// baseline suitable for a static site, with `Strict-Transport-Security` left off (see
|
||||
/// ``Configuration``).
|
||||
/// - Parameter configuration: the headers applied to every response. Defaults to a hardened baseline suitable for a static site, with
|
||||
/// `Strict-Transport-Security` left off (see ``Configuration``).
|
||||
public init(
|
||||
configuration: Configuration = .init()
|
||||
) {
|
||||
@@ -37,14 +35,11 @@ extension SecurityHeadersMiddleware: RouterMiddleware {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
/// Passes the request down the chain and stamps the configured security headers onto the
|
||||
/// response on the way back up.
|
||||
/// Passes the request down the chain and stamps the configured security headers onto the response on the way back up.
|
||||
///
|
||||
/// Errors that can render themselves (`HTTPResponseError`, like the `HTTPError`s thrown by the
|
||||
/// controllers) are converted to their response here rather than left to the router: the router
|
||||
/// converts them above the middleware chain, where the response would escape these headers.
|
||||
/// Existing values for the same header names are replaced so downstream middleware cannot leave
|
||||
/// a weaker policy in place.
|
||||
/// Errors that can render themselves (`HTTPResponseError`, like the `HTTPError`s thrown by the controllers) are converted to their response
|
||||
/// here rather than left to the router: the router converts them above the middleware chain, where the response would escape these headers.
|
||||
/// Existing values for the same header names are replaced so downstream middleware cannot leave a weaker policy in place.
|
||||
/// - Parameters:
|
||||
/// - request: the incoming request.
|
||||
/// - context: the context the request is resolved against.
|
||||
@@ -106,10 +101,9 @@ private extension SecurityHeadersMiddleware.Configuration {
|
||||
extension SecurityHeadersMiddleware {
|
||||
/// The set of security headers a ``SecurityHeadersMiddleware`` applies.
|
||||
///
|
||||
/// Each property maps to a single response header. A `nil` value omits that header entirely,
|
||||
/// which is how `Strict-Transport-Security` stays disabled by default: it is only safe to send
|
||||
/// over HTTPS and is "sticky" in browsers, so it must stay off in plain-HTTP development and be
|
||||
/// switched on (via configuration) only in TLS-terminated production.
|
||||
/// Each property maps to a single response header. A `nil` value omits that header entirely, which is how `Strict-Transport-Security` stays
|
||||
/// disabled by default: it is only safe to send over HTTPS and is "sticky" in browsers, so it must stay off in plain-HTTP development and be switched on
|
||||
/// (via configuration) only in TLS-terminated production.
|
||||
public struct Configuration: Sendable {
|
||||
|
||||
// MARK: Properties
|
||||
@@ -131,9 +125,8 @@ extension SecurityHeadersMiddleware {
|
||||
|
||||
/// Creates a security-headers configuration.
|
||||
///
|
||||
/// Every parameter defaults to the hardened baseline defined in `String.Security`, except
|
||||
/// `strictTransportSecurity`, which defaults to `nil` (omitted). Pass `nil` for any header
|
||||
/// to drop it from the response.
|
||||
/// Every parameter defaults to the hardened baseline defined in `String.Security`, except `strictTransportSecurity`, which defaults
|
||||
/// to `nil` (omitted). Pass `nil` for any header to drop it from the response.
|
||||
/// - Parameters:
|
||||
/// - contentSecurityPolicy: the `Content-Security-Policy` value.
|
||||
/// - contentTypeOptions: the `X-Content-Type-Options` value.
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
/// An asset shipped with a website: a file stored under the static files root and served by
|
||||
/// Hummingbird's `FileMiddleware` middleware.
|
||||
/// An asset shipped with a website: a file stored under the static files root and served by Hummingbird's `FileMiddleware` middleware.
|
||||
///
|
||||
/// A conforming asset supplies its file name and the extensions it is available with, each
|
||||
/// resolving to its own file; the protocol derives the paths from them: the file's path within
|
||||
/// the static files root and the URL path it is served at, optionally versioned to bust caches.
|
||||
/// A conforming asset supplies its file name and the extensions it is available with, each resolving to its own file; the protocol derives the paths from them:
|
||||
/// the file's path within the static files root and the URL path it is served at, optionally versioned to bust caches.
|
||||
public protocol Asset: Sendable {
|
||||
|
||||
// MARK: Properties
|
||||
@@ -58,9 +56,8 @@ public extension Asset {
|
||||
|
||||
/// Resolves the absolute URL path the asset is served at (e.g. `"/css/shared.css"`).
|
||||
///
|
||||
/// A version token appends as a `v` query parameter (e.g. `"/css/shared.css?v=abc123"`):
|
||||
/// `FileMiddleware` ignores the query when resolving the file, while caches key on the full
|
||||
/// URL, so a deploy that changes the assets busts every cached copy at once.
|
||||
/// A version token appends as a `v` query parameter (e.g. `"/css/shared.css?v=abc123"`): `FileMiddleware` ignores the query when
|
||||
/// resolving the file, while caches key on the full URL, so a deploy that changes the assets busts every cached copy at once.
|
||||
/// - Parameters:
|
||||
/// - fileExtension: the extension of the file to resolve.
|
||||
/// - version: the version token to append, or `nil` to leave the URL unversioned.
|
||||
|
||||
@@ -2,9 +2,8 @@ import Hummingbird
|
||||
|
||||
/// A request context that carries the language negotiated for the request.
|
||||
///
|
||||
/// ``LocalizationMiddleware`` resolves the visitor's preferred language from the `Accept-Language`
|
||||
/// header and stores it here, so downstream controllers and middleware can serve the matching
|
||||
/// localization without re-reading the header.
|
||||
/// ``LocalizationMiddleware`` resolves the visitor's preferred language from the `Accept-Language` header and stores it here, so downstream
|
||||
/// controllers and middleware can serve the matching localization without re-reading the header.
|
||||
public protocol LocalizedRequestContext: RequestContext {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
@@ -62,8 +62,8 @@ public extension Page {
|
||||
|
||||
/// The viewport declaration and ``stylesheets`` links followed by the ``metadata``, placed in the document head.
|
||||
///
|
||||
/// The charset declaration is omitted: Elementary's `HTMLDocument` scaffolding already
|
||||
/// emits `<meta charset="UTF-8">` before this markup, and HTML5 allows only one.
|
||||
/// The charset declaration is omitted: Elementary's `HTMLDocument` scaffolding already emits `<meta charset="UTF-8">` before this markup,
|
||||
/// and HTML5 allows only one.
|
||||
@HTMLBuilder
|
||||
var head: some HTML {
|
||||
meta(
|
||||
|
||||
@@ -2,8 +2,8 @@ import Hummingbird
|
||||
|
||||
/// A type exposing its endpoints as a route collection ready to be added to a router.
|
||||
///
|
||||
/// Conforming controllers group related endpoints behind a single ``routes`` property,
|
||||
/// so the application composes them declaratively with ``Hummingbird/RouterMethods/addController(_:)``:
|
||||
/// Conforming controllers group related endpoints behind a single ``routes`` property, so the application composes them declaratively with
|
||||
/// ``Hummingbird/RouterMethods/addController(_:)``:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct HealthController<Context: RequestContext>: RouterController {
|
||||
|
||||
+5
-6
@@ -6,10 +6,9 @@ import Localization
|
||||
|
||||
/// A per-language collection of pre-rendered HTML responses.
|
||||
///
|
||||
/// At initialization it renders the document once for each language the bundle's ``LanguageList``
|
||||
/// reports and caches the bytes, mirroring ``CachedHTMLResponse``'s render-once model but keyed by
|
||||
/// language. Each cached response carries a `Content-Language` header and `Vary: Accept-Language`, so
|
||||
/// shared caches key on the negotiated language instead of serving one language to everyone.
|
||||
/// At initialization it renders the document once for each language the bundle's ``LanguageList`` reports and caches the bytes, mirroring
|
||||
/// ``CachedHTMLResponse``'s render-once model but keyed by language. Each cached response carries a `Content-Language` header and
|
||||
/// `Vary: Accept-Language`, so shared caches key on the negotiated language instead of serving one language to everyone.
|
||||
public struct LocalizedHTMLCollectionResponse: Sendable {
|
||||
|
||||
// MARK: Properties
|
||||
@@ -54,8 +53,8 @@ public struct LocalizedHTMLCollectionResponse: Sendable {
|
||||
/// - Parameters:
|
||||
/// - language: the negotiated language identifier.
|
||||
/// - request: the request the response answers, consulted for conditional revalidation.
|
||||
/// - Returns: the cached response for the language, the default language's response when the
|
||||
/// language is unavailable, or a `500 Internal Server Error` if neither is cached.
|
||||
/// - Returns: the cached response for the language, the default language's response when the language is unavailable, or a
|
||||
/// `500 Internal Server Error` if neither is cached.
|
||||
public func response(
|
||||
for language: String,
|
||||
request: Request
|
||||
|
||||
@@ -2,7 +2,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("FNV1aHash type")
|
||||
@Suite(
|
||||
"FNV1aHash type",
|
||||
.tags(.type)
|
||||
)
|
||||
struct FNV1aHashTests {
|
||||
|
||||
// MARK: Functional tests
|
||||
|
||||
@@ -2,7 +2,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("AssetExtension enumeration")
|
||||
@Suite(
|
||||
"AssetExtension enumeration",
|
||||
.tags(.asset)
|
||||
)
|
||||
struct AssetExtensionTests {
|
||||
|
||||
// MARK: Computed tests
|
||||
|
||||
+4
-1
@@ -5,7 +5,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("addController method")
|
||||
@Suite(
|
||||
"addController method",
|
||||
.tags(.`extension`)
|
||||
)
|
||||
struct RouterMethodsTests {
|
||||
|
||||
// MARK: Functional tests
|
||||
|
||||
@@ -3,7 +3,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("FingerprintAssets method")
|
||||
@Suite(
|
||||
"FingerprintAssets method",
|
||||
.tags(.asset)
|
||||
)
|
||||
struct FingerprintAssetsTests {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
+4
-1
@@ -7,7 +7,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("LocalizationMiddleware middleware", .tags(.middleware))
|
||||
@Suite(
|
||||
"LocalizationMiddleware middleware",
|
||||
.tags(.middleware)
|
||||
)
|
||||
struct LocalizationMiddlewareTests {
|
||||
|
||||
// MARK: Constants
|
||||
|
||||
+4
-1
@@ -6,7 +6,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("NotFoundMiddleware middleware", .tags(.middleware))
|
||||
@Suite(
|
||||
"NotFoundMiddleware middleware",
|
||||
.tags(.middleware)
|
||||
)
|
||||
struct NotFoundMiddlewareTests {
|
||||
|
||||
// MARK: Constants
|
||||
|
||||
+4
-1
@@ -4,7 +4,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("RateLimitMiddleware middleware", .tags(.middleware))
|
||||
@Suite(
|
||||
"RateLimitMiddleware middleware",
|
||||
.tags(.middleware)
|
||||
)
|
||||
struct RateLimitMiddlewareTests {
|
||||
|
||||
// MARK: Functional tests
|
||||
|
||||
+4
-1
@@ -4,7 +4,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("SecurityHeadersMiddleware middleware", .tags(.middleware))
|
||||
@Suite(
|
||||
"SecurityHeadersMiddleware middleware",
|
||||
.tags(.middleware)
|
||||
)
|
||||
struct SecurityHeadersMiddlewareTests {
|
||||
|
||||
// MARK: Functional tests
|
||||
|
||||
@@ -5,7 +5,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("VaryMiddleware middleware", .tags(.middleware))
|
||||
@Suite(
|
||||
"VaryMiddleware middleware",
|
||||
.tags(.middleware)
|
||||
)
|
||||
struct VaryMiddlewareTests {
|
||||
|
||||
// MARK: Functional tests
|
||||
|
||||
@@ -2,7 +2,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("Asset protocol")
|
||||
@Suite(
|
||||
"Asset protocol",
|
||||
.tags(.`protocol`)
|
||||
)
|
||||
struct AssetTests {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
@@ -4,7 +4,10 @@ import Testing
|
||||
|
||||
@testable import Infrastructure
|
||||
|
||||
@Suite("Page protocol", .tags(.page))
|
||||
@Suite(
|
||||
"Page protocol",
|
||||
.tags(.`protocol`)
|
||||
)
|
||||
struct PageTests {
|
||||
|
||||
// MARK: Functional tests
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import Testing
|
||||
|
||||
extension Tag {
|
||||
/// Tests exercising the asset scaffolding of the Infrastructure package.
|
||||
@Tag static var asset: Tag
|
||||
/// Tests exercising an extension of the Infrastructure package.
|
||||
@Tag static var `extension`: Tag
|
||||
/// Tests exercising a middleware of the Infrastructure package.
|
||||
@Tag static var middleware: Tag
|
||||
/// Tests exercising the page scaffolding of the Infrastructure package.
|
||||
@Tag static var page: Tag
|
||||
/// Tests exercising a protocol scaffolding of the Infrastructure package.
|
||||
@Tag static var `protocol`: Tag
|
||||
/// Tests exercising an internal type of the Infrastructure package.
|
||||
@Tag static var type: Tag
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user