Improved upon the "listArchives(_: context: )" function for the ArchiveController controller in the library target.

This commit is contained in:
Javier Cicchelli 2025-03-12 23:43:19 +01:00
parent dbb7aeda1b
commit a14d388321
4 changed files with 108 additions and 25 deletions

View File

@ -1,6 +1,6 @@
import Hummingbird import Hummingbird
/// A controller that handles /// A controller type that provides information about the *DocC* archives containers.
struct ArchiveController<Context: RequestContext> { struct ArchiveController<Context: RequestContext> {
// MARK: Properties // MARK: Properties
@ -12,8 +12,8 @@ struct ArchiveController<Context: RequestContext> {
/// Initialises this controller. /// Initialises this controller.
/// - Parameters: /// - Parameters:
/// - folderArchives: <#folderArchives description#> /// - folderArchives: A folder in the file system where the *DocC* archive contained are located.
/// - fileService: <#fileService description#> /// - fileService: A service that interfaces with the local file system.
init( init(
_ folderArchives: String, _ folderArchives: String,
fileService: any FileServicing = FileService() fileService: any FileServicing = FileService()
@ -27,7 +27,7 @@ struct ArchiveController<Context: RequestContext> {
/// Registers the controller to a given router. /// Registers the controller to a given router.
/// - Parameter router: A router to register this controller to. /// - Parameter router: A router to register this controller to.
func register(to router: Router<Context>) { func register(to router: Router<Context>) {
router.get(.archives, use: listAllArchives) router.get(.archives, use: listArchives)
} }
} }
@ -38,16 +38,26 @@ private extension ArchiveController {
// MARK: Functions // MARK: Functions
@Sendable func listAllArchives( @Sendable func listArchives(
_ request: Request, _ request: Request,
context: Context context: Context
) async throws -> ArchiveList { ) async throws (HTTPError) -> ArchiveList {
let archives = try await fileService do {
let nameArchives = try await fileService
.listItems(in: folderArchives) .listItems(in: folderArchives)
.filter { $0.hasSuffix(.suffixArchive) } .filter { $0.hasSuffix(.suffixArchive) }
.map { $0.dropLast(String.suffixArchive.count) }
.map(String.init)
.sorted { $0 < $1 } .sorted { $0 < $1 }
return .init(archives) return .init(nameArchives)
} catch .folderNotFound {
throw .init(.notFound)
} catch .folderPathEmpty, .folderNotDirectory {
throw .init(.unprocessableContent)
} catch {
throw .init(.badRequest)
}
} }
} }

View File

@ -1,9 +1,9 @@
/// A type that services access to information from the local file system. /// A type that interfaces with the local file system.
protocol FileServicing: Sendable { protocol FileServicing: Sendable {
// MARK: Functions // MARK: Functions
/// Lists the names of items located at a given folder. path /// Lists the names of items located at a given folder path. in the local file system.
/// - Parameter folder: A path to a folder that could be found in the local file system. /// - Parameter folder: A path to a folder that could be found in the local file system.
/// - Returns: A list of names related to the items retrieved from a given folder path. /// - Returns: A list of names related to the items retrieved from a given folder path.
/// - Throws: A ``FileServiceError`` error type in case any error happens while retrieving the list. /// - Throws: A ``FileServiceError`` error type in case any error happens while retrieving the list.

View File

@ -12,12 +12,19 @@ struct ArchiveControllerTests {
private let decoder: JSONDecoder = .init() private let decoder: JSONDecoder = .init()
@Test func xxx() async throws { // MARK: Controller tests
@Test(arguments: zip([[String].folderWithArchives, .folderWithNoArchives, .folderEmpty],
[[String].listWithArchives, .listWithNoArchives, .listWithNoArchives]))
func getArchives(
with archivesInFolder: [String],
expects archivesInList: [String]
) async throws {
// GIVEN // GIVEN
let fileService = FileServiceMock(items: ["SomeArchive.doccarchive", "some-file.txt"]) let fileService = FileServiceMock(items: archivesInFolder)
let router = Router() let router = Router()
ArchiveController("/path/to/archives/folder", fileService: fileService) ArchiveController(.Path.archivesFolder, fileService: fileService)
.register(to: router) .register(to: router)
let app = Application(router: router) let app = Application(router: router)
@ -25,21 +32,26 @@ struct ArchiveControllerTests {
// WHEN // WHEN
// THEN // THEN
try await app.test(.router) { client in try await app.test(.router) { client in
try await client.execute(uri: "/archives", method: .get) { response in try await client.execute(uri: .Path.archivesResource, method: .get) { response in
let archiveList = try decoder.decode(ArchiveList.self, from: response.body) let archiveList = try decoder.decode(ArchiveList.self, from: response.body)
#expect(response.status == .ok) #expect(response.status == .ok)
#expect(archiveList.archives == ["SomeArchive.doccarchive"]) #expect(archiveList.archives == archivesInList)
} }
} }
} }
@Test func yyy() async throws { @Test(arguments: zip([FileServiceError].errorFileService,
[HTTPResponse.Status].errorResponse))
func getArchives(
with errorInFolder: FileServiceError,
expects errorResponse: HTTPResponse.Status
) async throws {
// GIVEN // GIVEN
let fileService = FileServiceMock(error: .folderNotFound) let fileService = FileServiceMock(error: errorInFolder)
let router = Router() let router = Router()
ArchiveController("/path/to/archives/folder", fileService: fileService) ArchiveController(.Path.archivesFolder, fileService: fileService)
.register(to: router) .register(to: router)
let app = Application(router: router) let app = Application(router: router)
@ -47,10 +59,71 @@ struct ArchiveControllerTests {
// WHEN // WHEN
// THEN // THEN
try await app.test(.router) { client in try await app.test(.router) { client in
try await client.execute(uri: "/archives", method: .get) { response in try await client.execute(uri: .Path.archivesResource, method: .get) { response in
#expect(response.status == .ok) #expect(response.status == errorResponse)
} }
} }
} }
} }
// MARK: - Collection+String
private extension Collection where Element == String {
static var folderEmpty: [Element] {[]}
static var folderWithArchives: [Element] {[
".DS_Store",
"SomeOtherArchive.doccarchive",
"some-text.txt",
"some-zipped-file.zip",
"SomeArchive.doccarchive",
"some-image.png",
"AnotherArchive.doccarchive",
"some-folder"
]}
static var folderWithNoArchives: [Element] {[
".DS_Store",
"some-text.txt",
"some-zipped-file.zip",
"some-image.png",
"some-folder"
]}
static var listWithArchives: [Element] {[
"AnotherArchive",
"SomeArchive",
"SomeOtherArchive"
]}
static var listWithNoArchives: [Element] {[]}
}
// MARK: - Collection+FileServiceError
private extension Collection where Element == FileServiceError {
static var errorFileService: [Element] {[
.folderPathEmpty,
.folderNotFound,
.folderNotDirectory,
.other(NSError(domain: "", code: 0))
]}
}
// MARK: - Collection+HTTPResponseStatus
private extension Collection where Element == HTTPResponse.Status {
static var errorResponse: [Element] {[
.unprocessableContent,
.notFound,
.unprocessableContent,
.badRequest
]}
}
// MARK: - String+Constants
private extension String {
enum Path {
static let archivesFolder: String = "/path/to/archives/folder"
static let archivesResource: String = "/archives"
}
}

View File

@ -10,7 +10,7 @@ struct AppBuilderTests {
private let appBuilder = AppBuilder( private let appBuilder = AppBuilder(
appName: "DoxyTest", appName: "DoxyTest",
archivesFolder: "Resources/Archives/Test" folderArchives: "Resources/Archives/Test"
) )
private let arguments = TestArguments() private let arguments = TestArguments()