import Hummingbird import HummingbirdTesting import Testing @testable import WebsiteLibrary @Suite("SecurityHeadersMiddleware middleware", .tags(.middleware)) struct SecurityHeadersMiddlewareTests { // MARK: Functional tests @Test func `applies the default security headers to a response`() 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[.contentSecurityPolicy] == String.Security.contentSecurityPolicy) #expect(response.headers[.xContentTypeOptions] == String.Security.contentTypeOptions) #expect(response.headers[.frameOptions] == String.Security.frameOptions) #expect(response.headers[.referrerPolicy] == String.Security.referrerPolicy) #expect(response.headers[.permissionsPolicy] == String.Security.permissionsPolicy) } } } @Test func `omits strict-transport-security by default`() async throws { try await app().test(.router) { client in try await client.execute( uri: "/hello", method: .get ) { response in #expect(response.headers[.strictTransportSecurity] == nil) } } } @Test func `applies strict-transport-security when configured`() async throws { let value = "max-age=31536000; includeSubDomains" try await app( configuration: .init(strictTransportSecurity: value) ).test(.router) { client in try await client.execute( uri: "/hello", method: .get ) { response in #expect(response.headers[.strictTransportSecurity] == value) } } } @Test func `applies a custom header value`() async throws { let value = "default-src 'none'" try await app( configuration: .init(contentSecurityPolicy: value) ).test(.router) { client in try await client.execute( uri: "/hello", method: .get ) { response in #expect(response.headers[.contentSecurityPolicy] == value) } } } @Test func `omits a header whose configured value is nil`() async throws { try await app( configuration: .init(contentTypeOptions: nil) ).test(.router) { client in try await client.execute( uri: "/hello", method: .get ) { response in #expect(response.headers[.xContentTypeOptions] == nil) } } } @Test func `replaces an existing header value set downstream`() async throws { try await app().test(.router) { client in try await client.execute( uri: "/weak", method: .get ) { response in #expect(response.headers[.xContentTypeOptions] == String.Security.contentTypeOptions) } } } } // MARK: - Helpers private extension SecurityHeadersMiddlewareTests { // MARK: Methods /// Builds an application whose router applies the security-headers middleware ahead of two /// routes: `/hello` returns a plain body, and `/weak` returns a response that already carries a /// deliberately weak `X-Content-Type-Options` value for the middleware to override. func app( configuration: SecurityHeadersMiddleware.Configuration = .init() ) -> some ApplicationProtocol { let router = Router() router.addMiddleware { SecurityHeadersMiddleware(configuration: configuration) } router.get("hello") { _, _ in "Hello!" } router.get("weak") { _, _ -> Response in var response = Response(status: .ok) response.headers[.xContentTypeOptions] = "weak" return response } return Application(router: router) } }