import Hummingbird import HummingbirdTesting import NIOCore import Testing import Infrastructure @testable import WebsiteLibrary @Suite("NotFoundMiddleware middleware", .tags(.middleware)) struct NotFoundMiddlewareTests { // MARK: Constants private let app: Application = .init(router: { let router = Router(context: WebsiteRequestContext.self) router.addMiddleware { LocalizationMiddleware() NotFoundMiddleware() } router.get("hello") { _, _ in "Hello!" } router.get("boom") { _, _ -> String in throw HTTPError(.badRequest) } return router }()) // MARK: Functional tests @Test func `renders the error page for an unmatched request`() async throws { try await app.test(.router) { client in try await client.execute( uri: "/this-path-does-not-exist", method: .get ) { response in let body = String(buffer: response.body) #expect(response.status == .notFound) #expect(response.headers[.contentType] == "text/html; charset=utf-8") #expect(response.headers[.contentLanguage] == "en") #expect(response.headers[.vary] == "Accept-Language") #expect(body.contains("Page Not Found")) } } } @Test func `passes a matched response through untouched`() 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.body == ByteBuffer(string: "Hello!")) } } } @Test func `rethrows a non-not-found error unchanged`() async throws { try await app.test(.router) { client in try await client.execute( uri: "/boom", method: .get ) { response in let body = String(buffer: response.body) #expect(response.status == .badRequest) #expect(!body.contains("Page Not Found")) } } } @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: "/this-path-does-not-exist", method: .get ) { response in let body = String(buffer: response.body) #expect(body.contains("/css/error.css?v=0123456789abcdef")) #expect(body.contains("/js/shared.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: "/this-path-does-not-exist", method: .get ) { response in let body = String(buffer: response.body) #expect(body.contains(#"href="/css/error.css""#)) #expect(!body.contains("?v=")) } } } @Test func `serves the error page without revalidation headers`() async throws { // A `304 Not Modified` only ever stands in for a success, so the error page must not // invite revalidation with an entity tag or a cache policy. try await app.test(.router) { client in try await client.execute( uri: "/this-path-does-not-exist", method: .get ) { response in #expect(response.status == .notFound) #expect(response.headers[.eTag] == nil) #expect(response.headers[.cacheControl] == nil) } } } @Test func `serves the full error page to a conditional request`() async throws { try await app.test(.router) { client in try await client.execute( uri: "/this-path-does-not-exist", method: .get, headers: [.ifNoneMatch: "*"] ) { response in let body = String(buffer: response.body) #expect(response.status == .notFound) #expect(body.contains("Page Not Found")) } } } } // MARK: - Helpers private extension NotFoundMiddlewareTests { // MARK: Methods /// Builds an application whose not-found middleware appends the given version token to the /// error page's asset URLs. /// - Parameter assetVersion: the version token appended to the page's asset URLs. /// - Returns: the configured application. func app( assetVersion: String? ) -> some ApplicationProtocol { let router = Router(context: WebsiteRequestContext.self) router.addMiddleware { LocalizationMiddleware() NotFoundMiddleware( assetVersion: assetVersion ) } return Application(router: router) } }