Trailing slash middleware for the Infrastructure package (#53)

This commit is contained in:
2026-08-24 17:33:52 +02:00
parent 1520c44b16
commit f6a77305ac
7 changed files with 293 additions and 10 deletions
@@ -0,0 +1,150 @@
import HTTPTypes
import Hummingbird
import HummingbirdTesting
import Testing
@testable import Infrastructure
@Suite(
"TrailingSlashRedirectMiddleware middleware",
.tags(.middleware)
)
struct TrailingSlashRedirectMiddlewareTests {
// MARK: Functional tests
@Test
func `redirects a path carrying a trailing slash`() async throws {
try await app().test(.router) { client in
try await client.execute(
uri: "/hello/",
method: .get
) { response in
#expect(response.status == .movedPermanently)
#expect(response.headers[.location] == "/hello")
}
}
}
@Test
func `preserves the query of the redirected request`() async throws {
try await app().test(.router) { client in
try await client.execute(
uri: "/hello/?utm_source=test&utm_medium=email",
method: .get
) { response in
#expect(response.headers[.location] == "/hello?utm_source=test&utm_medium=email")
}
}
}
@Test
func `collapses a path of nothing but slashes onto the root`() async throws {
try await app().test(.router) { client in
try await client.execute(
uri: "//",
method: .get
) { response in
#expect(response.status == .movedPermanently)
#expect(response.headers[.location] == "/")
}
}
}
@Test
func `strips every trailing slash at once`() async throws {
try await app().test(.router) { client in
try await client.execute(
uri: "/hello///",
method: .get
) { response in
#expect(response.headers[.location] == "/hello")
}
}
}
@Test
func `passes the canonical path through`() async throws {
try await app().test(.router) { client in
try await client.execute(
uri: "/hello",
method: .get
) { response in
#expect(response.status == .ok)
#expect(response.headers[.location] == nil)
}
}
}
@Test
func `leaves the root path alone`() async throws {
try await app().test(.router) { client in
try await client.execute(
uri: "/",
method: .get
) { response in
#expect(response.status == .ok)
#expect(response.headers[.location] == nil)
}
}
}
@Test
func `redirects a head request as it does a get`() async throws {
try await app().test(.router) { client in
try await client.execute(
uri: "/hello/",
method: .head
) { response in
#expect(response.status == .movedPermanently)
#expect(response.headers[.location] == "/hello")
}
}
}
@Test
func `passes a post through so its body survives`() async throws {
try await app().test(.router) { client in
try await client.execute(
uri: "/hello/",
method: .post
) { response in
#expect(response.status == .ok)
#expect(response.headers[.location] == nil)
}
}
}
}
// MARK: - Helpers
private extension TrailingSlashRedirectMiddlewareTests {
// MARK: Methods
/// Builds an application whose router applies the trailing-slash middleware ahead of a `/hello`
/// route answering both `GET` and `POST`, and a root route standing in for the landing page.
func app() -> some ApplicationProtocol {
let router = Router()
router.addMiddleware {
TrailingSlashRedirectMiddleware()
}
router.get("hello") { _, _ in
"Hello!"
}
router.post("hello") { _, _ in
"Posted!"
}
router.get("/") { _, _ in
"Root!"
}
return Application(router: router)
}
}