Improved and documented the DocCModilleware middleware in the library target.

This commit is contained in:
2025-03-08 13:52:20 +01:00
parent 5b379fbc86
commit 980c8e15f6
5 changed files with 318 additions and 24 deletions
@@ -0,0 +1,206 @@
//
// Test.swift
// DocCRepo
//
// Created by Javier Cicchelli on 08/03/2025.
//
import Hummingbird
import HummingbirdTesting
import Testing
@testable import AppLibrary
@Suite("DocCMiddleware", .tags(.middleware))
struct DoccMiddlewareTests {
@Test(arguments: zip([String].urisRedirect, [String].pathsRedirect))
func redirects(
from uri: String,
to path: String
) async throws {
// GIVEN
let router = Router.test()
let app = Application(router: router)
// WHEN
// THEN
try await app.test(.router) { client in
try await client.execute(uri: uri, method: .get) { response in
#expect(response.status == .seeOther)
#expect(response.headers[.location] == path)
}
}
}
@Test(arguments: zip([String].urisServes, [String].identifiersServes))
func servesFile(
for uri: String,
with identifier: String
) async throws {
// GIVEN
var provider = FileProviderMock()
provider.setFile(identifier: identifier)
let router = Router.test(assetProvider: provider)
let app = Application(router: router)
// WHEN
// THEN
try await app.test(.router) { client in
try await client.execute(uri: uri, method: .get) { response in
#expect(response.status == .ok)
#expect(response.body == ByteBuffer(string: identifier))
}
}
}
@Test(arguments: zip([String].urisInvalid, [HTTPResponse.Status].statusesInvalid))
func throwError(
for uri: String,
with status: HTTPResponse.Status
) async throws {
// GIVEN
let router = Router.test()
let app = Application(router: router)
// WHEN
// THEN
try await app.test(.router) { client in
try await client.execute(uri: uri, method: .get) { response in
#expect(response.status == status)
}
}
}
}
// MARK: - Router+Constants
private extension Router<BasicRequestContext> {
// MARK: Functions
static func test(assetProvider: some FileProvider = FileProviderMock()) -> Router {
let router = Router()
router.addMiddleware {
DocCMiddleware(assetProvider: assetProvider)
}
return router
}
}
// MARK: - Collection+String
private extension Collection where Element == String {
// MARK: Computed
static var identifiersServes: [String] {[
"/SomeArchive.doccarchive/documentation/somearchive/index.html",
"/SomeArchive.doccarchive/tutorials/somearchive/index.html",
"/SomeArchive.doccarchive/favicon.ico",
"/SomeArchive.doccarchive/favicon.svg",
"/SomeArchive.doccarchive/theme-settings.json",
"/SomeArchive.doccarchive/data/documentation/somearchive.json",
"/SomeArchive.doccarchive/css/some-css-file.css",
"/SomeArchive.doccarchive/data/some-data-file.bin",
"/SomeArchive.doccarchive/downloads/some-download-file",
"/SomeArchive.doccarchive/images/some-image-file.jpg",
"/SomeArchive.doccarchive/img/some-image-file.png",
"/SomeArchive.doccarchive/index/some-index-file",
"/SomeArchive.doccarchive/js/some-js-file.js",
"/SomeArchive.doccarchive/videos/some-video-file.mp4",
]}
static var pathsRedirect: [String] {[
"/archives/SomeArchive/documentation",
"/archives/SomeArchive/documentation/",
"/archives/SomeArchive/tutorials/",
]}
static var urisInvalid: [String] {[
"",
"some-path",
"some/uri/path",
"../",
"/../",
"/archives",
"/archives/SomeArchive/favicon.ico",
"/archives/SomeArchive/favicon.svg",
"/archives/SomeArchive/theme-settings.json",
"/archives/SomeArchive/data/documentation.json",
"/archives/SomeArchive/css/some-css-file.css",
"/archives/SomeArchive/data/some-data-file.bin",
"/archives/SomeArchive/downloads/some-download-file",
"/archives/SomeArchive/images/some-image-file.jpg",
"/archives/SomeArchive/img/some-image-file.png",
"/archives/SomeArchive/index/some-index-file",
"/archives/SomeArchive/js/some-js-file.js",
"/archives/SomeArchive/videos/some-video-file.mp4",
"/archives/SomeArchive/index.html",
"/archives/SomeArchive/xxx",
"/archives/SomeArchive/xxx/index.html"
]}
static var urisRedirect: [String] {[
"/archives/SomeArchive/",
"/archives/SomeArchive/documentation",
"/archives/SomeArchive/tutorials",
]}
static var urisServes: [String] {[
"/archives/SomeArchive/documentation/",
"/archives/SomeArchive/tutorials/",
"/archives/SomeArchive/favicon.ico",
"/archives/SomeArchive/favicon.svg",
"/archives/SomeArchive/theme-settings.json",
"/archives/SomeArchive/data/documentation.json",
"/archives/SomeArchive/css/some-css-file.css",
"/archives/SomeArchive/data/some-data-file.bin",
"/archives/SomeArchive/downloads/some-download-file",
"/archives/SomeArchive/images/some-image-file.jpg",
"/archives/SomeArchive/img/some-image-file.png",
"/archives/SomeArchive/index/some-index-file",
"/archives/SomeArchive/js/some-js-file.js",
"/archives/SomeArchive/videos/some-video-file.mp4",
]}
}
// MARK: - Collection+HTTPResponse.Status
private extension Collection where Element == HTTPResponse.Status {
// MARK: Computed
static var statusesInvalid: [HTTPResponse.Status] {[
.notFound,
.badRequest,
.badRequest,
.badRequest,
.badRequest,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notFound,
.notImplemented,
.notImplemented,
.notImplemented
]}
}
@@ -5,6 +5,7 @@ extension Tag {
// MARK: Constants
@Tag static var enumeration: Tag
@Tag static var middleware: Tag
@Tag static var provider: Tag
}
@@ -0,0 +1,55 @@
import Foundation
import Hummingbird
struct FileProviderMock {
// MARK: Properties
private var attributes: [String: Data] = [:]
private var identifiers: [String] = []
// MARK: Functions
mutating func setFile(identifier: String) {
identifiers += [identifier]
attributes[identifier] = identifier.data(using: .utf8)
}
}
// MARK: - FileProvider
extension FileProviderMock: FileProvider {
// MARK: Type aliases
typealias FileAttributes = Data
typealias FileIdentifier = String
// MARK: Functions
func getFileIdentifier(_ path: String) -> String? {
identifiers.first { $0 == path }
}
func getAttributes(id: String) async throws -> Data? {
attributes[id]
}
func loadFile(id: String, context: some RequestContext) async throws -> ResponseBody {
guard let fileData = attributes[id] else {
throw HTTPError(.notFound)
}
return .init(byteBuffer: .init(data: fileData))
}
func loadFile(
id: String,
range: ClosedRange<Int>,
context: some RequestContext
) async throws -> ResponseBody {
try await loadFile(id: id, context: context)
}
}