From c2c603a810eacd9232d9b40604582b2183c836c1 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Wed, 24 Sep 2025 17:29:18 +0200 Subject: [PATCH] Implemented the RedirectURIUseCase use case in the library target. --- .../Use Cases/RedirectURIUseCase.swift | 78 +++++++++ .../LoggerMetadata+HelpersTests.swift | 7 - .../Use Cases/RedirectURIUseCaseTests.swift | 164 ++++++++++++++++++ .../Types/Extensions/String+Constants.swift | 20 +++ 4 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 Sources/DocCMiddleware/Internal/Use Cases/RedirectURIUseCase.swift create mode 100644 Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift create mode 100644 Tests/DocCMiddleware/Types/Extensions/String+Constants.swift diff --git a/Sources/DocCMiddleware/Internal/Use Cases/RedirectURIUseCase.swift b/Sources/DocCMiddleware/Internal/Use Cases/RedirectURIUseCase.swift new file mode 100644 index 0000000..7e49a3a --- /dev/null +++ b/Sources/DocCMiddleware/Internal/Use Cases/RedirectURIUseCase.swift @@ -0,0 +1,78 @@ +// ===----------------------------------------------------------------------=== +// +// This source file is part of the Hummingbird DocC Middleware open source project +// +// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors +// Licensed under the EUPL 1.2 or later. +// +// See LICENSE for license information +// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors +// +// ===----------------------------------------------------------------------=== + +import protocol Hummingbird.RequestContext + +import struct Hummingbird.Request +import struct Hummingbird.Response +import struct Logging.Logger + +/// A use case that produces a redirect response based on a given URI path. +struct RedirectURIUseCase { + + // MARK: Type aliases + + /// A pseudo-type that contains data about a request and its related context. + typealias ContextualInfo = (request: Request, context: any RequestContext) + + // MARK: Properties + + /// A type that interacts with the logging system. + private let logger: Logger + + // MARK: Initializers + + /// Initializes this use case. + /// - Parameter logger: A type that interacts with the logging system. + init(logger: Logger) { + self.logger = logger + } + + // MARK: Functions + + /// Produces a redirect response based on a given URI path + /// - Parameters: + /// - uriPath: A URI path to use in the redirection. + /// - contextualInfo: A pseudo-type that contains data about a request and its related context. + /// - Returns: A redirection response created out of a given URI path plus contextual information. + func callAsFunction( + _ uriPath: String, + with contextualInfo: ContextualInfo + ) -> Response { + defer { + logger.log( + level: .debug, + "The URI path is redirected to this path: \(uriPath)", + metadata: .metadata( + context: contextualInfo.context, + request: contextualInfo.request, + statusCode: .movedPermanently, + redirect: uriPath + ), + source: .source + ) + } + + return .redirect( + to: uriPath, + type: .permanent + ) + } + +} + +// MARK: - String+Constants + +private extension String { + /// A name of the middleware that triggered a logging event. + static let source = "DocCMiddleware" +} diff --git a/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift b/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift index b290c2c..dd0c9d0 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift +++ b/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift @@ -126,10 +126,3 @@ private extension LoggerMetadata_HelpersTests { } } - -// MARK: - Constants - -private extension String { - /// A URI path to use as a redirection sample. - static let uriRedirection = "/some/redirect/path" -} diff --git a/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift b/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift new file mode 100644 index 0000000..0f5841c --- /dev/null +++ b/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift @@ -0,0 +1,164 @@ +// ===----------------------------------------------------------------------=== +// +// This source file is part of the Hummingbird DocC Middleware open source project +// +// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors +// Licensed under the EUPL 1.2 or later. +// +// See LICENSE for license information +// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors +// +// ===----------------------------------------------------------------------=== + +import Hummingbird +import Testing + +import struct Logging.Logger + +@testable import struct DocCMiddleware.RedirectURIUseCase + +@Suite("Redirect URI Use Case", .tags(.useCase)) +struct RedirectURIUseCaseTests { + + // MARK: Use case tests + +#if swift(>=6.2) + @Test + func `response when logger expects an event`() async throws { + try await assertResponse( + logLevel: try randomLogLevelWithEvent, + uriRedirection: .uriRedirection, + expects: .movedPermanently + ) + } + + @Test + func `response when logger does not expects an event`() async throws { + try await assertResponse( + logLevel: try randomLogLevelWithNoEvent, + uriRedirection: .uriRedirection, + expects: .movedPermanently + ) + } +#else + @Test("response when logger expects an event") + func responseWhenLoggerExpectsEvent() async throws { + try await assertResponse( + logLevel: try randomLogLevelWithEvent, + uriRedirection: .uriRedirection, + expects: .movedPermanently + ) + } + + @Test("response when logger does not expects an event") + func responseWhenLogLevel() async throws { + try await assertResponse( + logLevel: try randomLogLevelWithNoEvent, + uriRedirection: .uriRedirection, + expects: .movedPermanently + ) + } +#endif + +} + +// MARK: - Assertions + +private extension RedirectURIUseCaseTests { + + // MARK: Functions + + /// Asserts the response returned by the ``RedirectURIUseCase`` use case based on the given `logLevel` logging level and the `uriRedirection` + /// URI path plus the expected status code of the response. + /// - Parameters: + /// - logLevel: A representation of the logging level to set in the `Logger` instance. + /// - uriRedirection: A URI path to use in the redirection. + /// - statusCode: An expected status code from the response coming out of the use case. + /// - Throws: An error in case an issue is encountered while asserting the use case. + func assertResponse( + logLevel: Logger.Level, + uriRedirection: String, + expects statusCode: HTTPResponse.Status + ) async throws { + let logHandler = LogHandlerMock() + let logger = Logger.test( + level: logLevel, + handler: logHandler + ) + + let context: any RequestContext = RequestContextMock(logger: logger) + let request: Request = .test(method: .get) + + let useCase = RedirectURIUseCase(logger: logger) + + // WHEN + let result = useCase( + uriRedirection, + with: (request, context) + ) + + // THEN + #expect(result.status == .movedPermanently) + #expect(result.body.contentLength == 0) + #expect(result.headers == [ + .location: uriRedirection, + .contentLength: "0" + ]) + + let events = await logHandler.entries + + if shouldEventBeLogged(logLevel) { + #expect(!events.isEmpty) + #expect(events.count == 1) + + let loggedEvent = try #require(events.first) + + #expect(loggedEvent == .init( + level: .debug, + metadata: [ + "hb.request.id": "\(context.id)", + "hb.request.method": "\(request.method.rawValue)", + "hb.request.path": "\(request.uri.path)", + "hb.request.status": "\(statusCode.code)", + "hb.request.redirect": "\(uriRedirection)" + ], + message: "The URI path is redirected to this path: \(uriRedirection)", + source: "DocCMiddleware" + )) + } else { + #expect(events.isEmpty) + } + } + +} + +// MARK: - Helpers + +private extension RedirectURIUseCaseTests { + + // MARK: Computed + + /// Extracts a random logging level that support event logging for the use case. + var randomLogLevelWithEvent: Logger.Level { + get throws { + try #require([.debug, .trace].randomElement()) + } + } + + /// Extracts a random logging level that does not support event logging for the use case. + var randomLogLevelWithNoEvent: Logger.Level { + get throws { + try #require([.critical, .error, .info, .notice, .warning].randomElement()) + } + } + + // MARK: Functions + + /// Checks whether a logging event should be logged or not, based on a given logging level. + /// - Parameter level: A representation of a logging level defined in the `Logger` instance. + /// - Returns: A boolean value that indicates whether a logging event should have been logged or not. + func shouldEventBeLogged(_ logLevel: Logger.Level) -> Bool { + [Logger.Level.trace, .debug].contains(logLevel) + } + +} diff --git a/Tests/DocCMiddleware/Types/Extensions/String+Constants.swift b/Tests/DocCMiddleware/Types/Extensions/String+Constants.swift new file mode 100644 index 0000000..72b41b6 --- /dev/null +++ b/Tests/DocCMiddleware/Types/Extensions/String+Constants.swift @@ -0,0 +1,20 @@ +// ===----------------------------------------------------------------------=== +// +// This source file is part of the Hummingbird DocC Middleware open source project +// +// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors +// Licensed under the EUPL 1.2 or later. +// +// See LICENSE for license information +// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors +// +// ===----------------------------------------------------------------------=== + +extension String { + + // MARK: Constants + + /// A URI path to use as a redirection sample. + static let uriRedirection = "/some/redirect/path" + +}