diff --git a/Tests/CLITests/client/AppleDocumentationClientErrorTests.swift b/Tests/CLITests/client/AppleDocumentationClientErrorTests.swift index 8e14161..9ef6b0e 100644 --- a/Tests/CLITests/client/AppleDocumentationClientErrorTests.swift +++ b/Tests/CLITests/client/AppleDocumentationClientErrorTests.swift @@ -4,10 +4,6 @@ import Testing @testable import CLI -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - @Suite("Apple documentation client errors") struct AppleDocumentationClientErrorTests { @Test("maps a missing type to discovery guidance") @@ -24,11 +20,11 @@ struct AppleDocumentationClientErrorTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: LookupTestTransport( + dependencies: HTTPTestTransport( responses: [ - typeURL: .init(statusCode: 404, data: Data()), - technologiesURL: .init(statusCode: 200, data: swiftDataCatalogData), - rootURL: .init(statusCode: 200, data: modelRootData), + typeURL: .http(statusCode: 404, data: Data()), + technologiesURL: .http(statusCode: 200, data: swiftDataCatalogData), + rootURL: .http(statusCode: 200, data: modelRootData), ] ) ) @@ -88,29 +84,3 @@ private let modelRootData = Data( } """.utf8 ) - -private struct LookupTestTransport: HTTPDataTransport { - struct Response: Sendable { - let statusCode: Int - let data: Data - } - - let responses: [URL: Response] - - func data(from url: URL) async throws -> (Data, URLResponse) { - guard let result = responses[url] else { - throw LookupTestError.unexpectedURL(url) - } - let response = HTTPURLResponse( - url: url, - statusCode: result.statusCode, - httpVersion: nil, - headerFields: ["Content-Type": "application/json"] - )! - return (result.data, response) - } -} - -private enum LookupTestError: Error { - case unexpectedURL(URL) -} diff --git a/Tests/CLITests/client/AppleDocumentationClientLoggingTests.swift b/Tests/CLITests/client/AppleDocumentationClientLoggingTests.swift index 08442db..81e4303 100644 --- a/Tests/CLITests/client/AppleDocumentationClientLoggingTests.swift +++ b/Tests/CLITests/client/AppleDocumentationClientLoggingTests.swift @@ -19,7 +19,9 @@ struct AppleDocumentationClientLoggingTests { let data = Data("private response body".utf8) let client = DefaultAppleDocumentationClient( logger: recorder.logger(), - dependencies: LoggingTestTransport(result: .success((data, try response(status: 200)))), + dependencies: HTTPTestTransport(responses: [ + url.appending(path: "documentation/swift/string.json"): .http(data: data) + ]), baseURL: url ) @@ -53,12 +55,14 @@ struct AppleDocumentationClientLoggingTests { let recorder = ClientLogRecorder() let client = DefaultAppleDocumentationClient( logger: recorder.logger(), - dependencies: LoggingTestTransport(result: .success((Data(), try response(status: status)))) + dependencies: HTTPTestTransport(responses: [ + try documentationURL("swift"): .http(statusCode: status, data: Data()) + ]) ) let expectedLevel: Logger.Level = status == 404 ? .debug : (status >= 500 ? .error : .warning) // -- Act -- - await #expect(throws: DefaultAppleDocumentationClient.Error.httpStatus(status)) { + await #expect(throws: DefaultAppleDocumentationClient.Error.httpStatus(status)) { try await client.fetchDocumentationPage(path: "/documentation/swift") } @@ -78,7 +82,9 @@ struct AppleDocumentationClientLoggingTests { let recorder = ClientLogRecorder() let client = DefaultAppleDocumentationClient( logger: recorder.logger(), - dependencies: LoggingTestTransport(result: .failure(URLError(code))) + dependencies: HTTPTestTransport(responses: [ + try documentationURL("swift/string"): .failure(URLError(code)) + ]) ) // -- Act -- @@ -101,11 +107,13 @@ struct AppleDocumentationClientLoggingTests { let response = URLResponse(url: url, mimeType: nil, expectedContentLength: 0, textEncodingName: nil) let client = DefaultAppleDocumentationClient( logger: recorder.logger(), - dependencies: LoggingTestTransport(result: .success((Data(), response))) + dependencies: HTTPTestTransport(responses: [ + try documentationURL("swift/string"): .response(data: Data(), response: response) + ]) ) // -- Act -- - await #expect(throws: DefaultAppleDocumentationClient.Error.invalidResponse) { + await #expect(throws: DefaultAppleDocumentationClient.Error.invalidResponse) { try await client.fetchType(named: "String", technology: "Swift") } @@ -123,9 +131,10 @@ struct AppleDocumentationClientLoggingTests { let recorder = ClientLogRecorder() let client = DefaultAppleDocumentationClient( logger: recorder.logger(), - dependencies: LoggingTestTransport( - result: .success((Data("sensitive malformed body".utf8), try response(status: 200))) - ) + dependencies: HTTPTestTransport(responses: [ + try documentationURL(catalog ? "technologies" : "swift"): + .http(data: Data("sensitive malformed body".utf8)) + ]) ) // -- Act -- @@ -151,14 +160,14 @@ struct AppleDocumentationClientLoggingTests { let recorder = ClientLogRecorder() let client = DefaultAppleDocumentationClient( logger: recorder.logger(), - dependencies: LoggingTestTransport( - result: .success((Data("{\"references\":{}}".utf8), try response(status: 200))) - ) + dependencies: HTTPTestTransport(responses: [ + try documentationURL("swift"): .http(data: Data("{\"references\":{}}".utf8)) + ]) ) // -- Act -- await #expect( - throws: DefaultAppleDocumentationClient.Error.typeSearchNoResults( + throws: DefaultAppleDocumentationClient.Error.typeSearchNoResults( query: "Missing", technology: "Swift", technologyURL: "https://developer.apple.com/documentation/swift" ) ) { @@ -186,9 +195,9 @@ struct AppleDocumentationClientLoggingTests { """ let client = DefaultAppleDocumentationClient( logger: recorder.logger(), - dependencies: LoggingSearchTransport(responses: [ - "/tutorials/data/documentation/swiftui.json": (200, root), - "/tutorials/data/documentation/swiftui/controls.json": (404, ""), + dependencies: HTTPTestTransport(responses: [ + try documentationURL("swiftui"): .http(data: Data(root.utf8)), + try documentationURL("swiftui/controls"): .http(statusCode: 404, data: Data()), ]) ) @@ -221,10 +230,10 @@ struct AppleDocumentationClientLoggingTests { """ let client = DefaultAppleDocumentationClient( logger: recorder.logger(), - dependencies: LoggingSearchTransport(responses: [ - "/tutorials/data/documentation/apple cryptokit/aes.json": (404, ""), - "/tutorials/data/documentation/technologies.json": (200, catalog), - "/tutorials/data/documentation/cryptokit/aes.json": (200, "raw document"), + dependencies: HTTPTestTransport(responses: [ + try documentationURL("apple%20cryptokit/aes"): .http(statusCode: 404, data: Data()), + try documentationURL("technologies"): .http(data: Data(catalog.utf8)), + try documentationURL("cryptokit/aes"): .http(data: Data("raw document".utf8)), ]) ) @@ -242,26 +251,7 @@ struct AppleDocumentationClientLoggingTests { #expect(!recorder.events.contains { $0.level >= .warning }) } - private func response(status: Int) throws -> HTTPURLResponse { - let url = try #require(URL(string: "https://example.com")) - return try #require(HTTPURLResponse(url: url, statusCode: status, httpVersion: nil, headerFields: nil)) - } -} - -private struct LoggingSearchTransport: HTTPDataTransport { - let responses: [String: (Int, String)] - - func data(from url: URL) async throws -> (Data, URLResponse) { - let (status, body) = try #require(responses[url.path]) - let response = try #require(HTTPURLResponse(url: url, statusCode: status, httpVersion: nil, headerFields: nil)) - return (Data(body.utf8), response) - } -} - -private struct LoggingTestTransport: HTTPDataTransport { - let result: Result<(Data, URLResponse), Swift.Error> - - func data(from _: URL) async throws -> (Data, URLResponse) { - try result.get() + private func documentationURL(_ path: String) throws -> URL { + try #require(URL(string: "https://developer.apple.com/tutorials/data/documentation/\(path).json")) } } diff --git a/Tests/CLITests/client/AppleDocumentationClientRootTests.swift b/Tests/CLITests/client/AppleDocumentationClientRootTests.swift index 8a784bb..73de47a 100644 --- a/Tests/CLITests/client/AppleDocumentationClientRootTests.swift +++ b/Tests/CLITests/client/AppleDocumentationClientRootTests.swift @@ -4,10 +4,6 @@ import Testing @testable import CLI -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - @Suite("Apple documentation root lookup") struct AppleDocumentationClientRootTests { @Test("maps a missing canonical root to unsupported technology guidance", arguments: [false, true]) @@ -22,10 +18,10 @@ struct AppleDocumentationClientRootTests { let canonicalURL = try #require( URL(string: "https://developer.apple.com/tutorials/data/documentation/cryptokit.json") ) - let transport = RootTestTransport(responses: [ - requestedURL: .init(statusCode: 404, data: Data()), - catalogURL: .init(statusCode: 200, data: cryptoKitCatalogData), - canonicalURL: .init(statusCode: 404, data: Data()), + let transport = HTTPTestTransport(responses: [ + requestedURL: .http(statusCode: 404, data: Data()), + catalogURL: .http(statusCode: 200, data: cryptoKitCatalogData), + canonicalURL: .http(statusCode: 404, data: Data()), ]) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, dependencies: transport @@ -33,7 +29,7 @@ struct AppleDocumentationClientRootTests { // -- Act -- await #expect( - throws: DefaultAppleDocumentationClient.Error.unsupportedTechnology( + throws: DefaultAppleDocumentationClient.Error.unsupportedTechnology( name: "Apple CryptoKit", url: "https://developer.apple.com/documentation/cryptokit" ) ) { @@ -57,9 +53,9 @@ struct AppleDocumentationClientRootTests { let catalogURL = try #require( URL(string: "https://developer.apple.com/tutorials/data/documentation/technologies.json") ) - let transport = RootTestTransport(responses: [ - rootURL: .init(statusCode: 404, data: Data()), - catalogURL: .init(statusCode: 200, data: cryptoKitCatalogData), + let transport = HTTPTestTransport(responses: [ + rootURL: .http(statusCode: 404, data: Data()), + catalogURL: .http(statusCode: 200, data: cryptoKitCatalogData), ]) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, dependencies: transport @@ -67,7 +63,7 @@ struct AppleDocumentationClientRootTests { // -- Act -- await #expect( - throws: DefaultAppleDocumentationClient.Error.unsupportedTechnology( + throws: DefaultAppleDocumentationClient.Error.unsupportedTechnology( name: "Apple CryptoKit", url: "https://developer.apple.com/documentation/cryptokit" ) ) { @@ -101,10 +97,10 @@ struct AppleDocumentationClientRootTests { }}} """.utf8 ) - let transport = RootTestTransport(responses: [ - requestedURL: .init(statusCode: 404, data: Data()), - catalogURL: .init(statusCode: 200, data: cryptoKitCatalogData), - canonicalURL: .init(statusCode: 200, data: rootData), + let transport = HTTPTestTransport(responses: [ + requestedURL: .http(statusCode: 404, data: Data()), + catalogURL: .http(statusCode: 200, data: cryptoKitCatalogData), + canonicalURL: .http(statusCode: 200, data: rootData), ]) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, dependencies: transport @@ -128,13 +124,13 @@ struct AppleDocumentationClientRootTests { let rootURL = try #require( URL(string: "https://developer.apple.com/tutorials/data/documentation/cryptokit.json") ) - let transport = RootTestTransport(responses: [rootURL: .init(statusCode: status, data: Data())]) + let transport = HTTPTestTransport(responses: [rootURL: .http(statusCode: status, data: Data())]) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, dependencies: transport ) // -- Act -- - await #expect(throws: DefaultAppleDocumentationClient.Error.httpStatus(status)) { + await #expect(throws: DefaultAppleDocumentationClient.Error.httpStatus(status)) { if search { _ = try await client.searchTypes(query: "AES", technology: "CryptoKit") } else { @@ -155,29 +151,3 @@ private let cryptoKitCatalogData = Data( }]}]}]} """.utf8 ) - -private actor RootTestTransport: HTTPDataTransport { - struct Response: Sendable { - let statusCode: Int - let data: Data - } - - let responses: [URL: Response] - private(set) var requestedURLs: [URL] = [] - - init(responses: [URL: Response]) { - self.responses = responses - } - - func data(from url: URL) async throws -> (Data, URLResponse) { - requestedURLs.append(url) - let result = try #require(responses[url], "Unexpected request: \(url)") - let response = try #require( - HTTPURLResponse( - url: url, statusCode: result.statusCode, httpVersion: nil, - headerFields: ["Content-Type": "application/json"] - ) - ) - return (result.data, response) - } -} diff --git a/Tests/CLITests/client/AppleDocumentationClientSearchTests.swift b/Tests/CLITests/client/AppleDocumentationClientSearchTests.swift index 1fd8c60..40b36fe 100644 --- a/Tests/CLITests/client/AppleDocumentationClientSearchTests.swift +++ b/Tests/CLITests/client/AppleDocumentationClientSearchTests.swift @@ -4,10 +4,6 @@ import Testing @testable import CLI -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - @Suite("Apple documentation type search client") struct AppleDocumentationClientSearchTests { @Test("searches symbols across nested collection groups") @@ -30,13 +26,10 @@ struct AppleDocumentationClientSearchTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: SearchTestTransport( - responses: [ - rootURL: rootSearchPage, - controlsURL: controlsSearchPage, - stylesURL: stylesSearchPage, - ] - ) + dependencies: HTTPTestTransport(responses: [ + rootURL: .http(data: rootSearchPage), controlsURL: .http(data: controlsSearchPage), + stylesURL: .http(data: stylesSearchPage), + ]) ) // -- Act -- @@ -55,8 +48,8 @@ struct AppleDocumentationClientSearchTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: SearchTestTransport( - responses: [rootURL: duplicateRootSearchPage] + dependencies: HTTPTestTransport( + responses: [rootURL: .http(data: duplicateRootSearchPage)] ) ) @@ -88,11 +81,11 @@ struct AppleDocumentationClientSearchTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: SearchFallbackTransport( + dependencies: HTTPTestTransport( responses: [ - rootURL: .init(statusCode: 200, data: partialFailureRootSearchPage), - controlsURL: .init(statusCode: 404, data: Data()), - stylesURL: .init(statusCode: 200, data: stylesSearchPage), + rootURL: .http(statusCode: 200, data: partialFailureRootSearchPage), + controlsURL: .http(statusCode: 404, data: Data()), + stylesURL: .http(statusCode: 200, data: stylesSearchPage), ] ) ) @@ -125,11 +118,11 @@ struct AppleDocumentationClientSearchTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: SearchFallbackTransport( + dependencies: HTTPTestTransport( responses: [ - requestedRootURL: .init(statusCode: 404, data: Data()), - technologiesURL: .init(statusCode: 200, data: cryptoKitCatalogData), - resolvedRootURL: .init(statusCode: 200, data: cryptoKitRootData), + requestedRootURL: .http(statusCode: 404, data: Data()), + technologiesURL: .http(statusCode: 200, data: cryptoKitCatalogData), + resolvedRootURL: .http(statusCode: 200, data: cryptoKitRootData), ] ) ) @@ -162,13 +155,10 @@ struct AppleDocumentationClientSearchTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: SearchTestTransport( - responses: [ - rootURL: rootSearchPage, - controlsURL: controlsSearchPage, - stylesURL: stylesSearchPage, - ] - ) + dependencies: HTTPTestTransport(responses: [ + rootURL: .http(data: rootSearchPage), controlsURL: .http(data: controlsSearchPage), + stylesURL: .http(data: stylesSearchPage), + ]) ) // -- Act -- @@ -314,23 +304,6 @@ private let stylesSearchPage = Data( """.utf8 ) -private struct SearchTestTransport: HTTPDataTransport { - let responses: [URL: Data] - - func data(from url: URL) async throws -> (Data, URLResponse) { - guard let data = responses[url] else { - throw SearchTestError.unexpectedURL(url) - } - let response = HTTPURLResponse( - url: url, - statusCode: 200, - httpVersion: nil, - headerFields: ["Content-Type": "application/json"] - )! - return (data, response) - } -} - private let cryptoKitCatalogData = Data( """ { @@ -363,29 +336,3 @@ private let cryptoKitRootData = Data( } """.utf8 ) - -private struct SearchFallbackTransport: HTTPDataTransport { - struct Response: Sendable { - let statusCode: Int - let data: Data - } - - let responses: [URL: Response] - - func data(from url: URL) async throws -> (Data, URLResponse) { - guard let result = responses[url] else { - throw SearchTestError.unexpectedURL(url) - } - let response = HTTPURLResponse( - url: url, - statusCode: result.statusCode, - httpVersion: nil, - headerFields: ["Content-Type": "application/json"] - )! - return (result.data, response) - } -} - -private enum SearchTestError: Error { - case unexpectedURL(URL) -} diff --git a/Tests/CLITests/client/AppleDocumentationClientTechnologyTests.swift b/Tests/CLITests/client/AppleDocumentationClientTechnologyTests.swift index e55a0ab..5c26af5 100644 --- a/Tests/CLITests/client/AppleDocumentationClientTechnologyTests.swift +++ b/Tests/CLITests/client/AppleDocumentationClientTechnologyTests.swift @@ -4,10 +4,6 @@ import Testing @testable import CLI -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - @Suite("Apple documentation technology client") struct AppleDocumentationClientTechnologyTests { @Test("fetches technologies from every catalog group") @@ -16,20 +12,7 @@ struct AppleDocumentationClientTechnologyTests { let expectedURL = try #require( URL(string: "https://developer.apple.com/tutorials/data/documentation/technologies.json") ) - let response = try #require( - HTTPURLResponse( - url: expectedURL, - statusCode: 200, - httpVersion: nil, - headerFields: ["Content-Type": "application/json"] - ) - ) - let data = technologyCatalogData - let transport = TechnologyCatalogTransport( - expectedURL: expectedURL, - response: response, - data: data - ) + let transport = HTTPTestTransport(responses: [expectedURL: .http(data: technologyCatalogData)]) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, dependencies: transport @@ -107,20 +90,3 @@ private let technologyCatalogData = Data( } """.utf8 ) - -private struct TechnologyCatalogTransport: HTTPDataTransport { - let expectedURL: URL - let response: URLResponse - let data: Data - - func data(from url: URL) async throws -> (Data, URLResponse) { - guard url == expectedURL else { - throw TechnologyCatalogTransportError.unexpectedURL(url) - } - return (data, response) - } -} - -private enum TechnologyCatalogTransportError: Error { - case unexpectedURL(URL) -} diff --git a/Tests/CLITests/client/AppleDocumentationClientTests.swift b/Tests/CLITests/client/AppleDocumentationClientTests.swift index bb1635a..3c25d81 100644 --- a/Tests/CLITests/client/AppleDocumentationClientTests.swift +++ b/Tests/CLITests/client/AppleDocumentationClientTests.swift @@ -4,25 +4,14 @@ import Testing @testable import CLI -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - @Suite("Apple documentation client") struct AppleDocumentationClientTests { @Test("requests and decodes a type documentation page") func fetchesTypeDocumentation() async throws { + // -- Arrange -- let expectedURL = try #require( URL(string: "https://developer.apple.com/tutorials/data/documentation/metrickit/mxhangdiagnostic.json") ) - let response = try #require( - HTTPURLResponse( - url: expectedURL, - statusCode: 200, - httpVersion: nil, - headerFields: ["Content-Type": "application/json"] - ) - ) let data = Data( """ { @@ -39,22 +28,18 @@ struct AppleDocumentationClientTests { } """.utf8 ) - let transport = TypePageTransport( - expectedURL: expectedURL, - response: response, - data: data - ) + let transport = HTTPTestTransport(responses: [expectedURL: .http(data: data)]) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, dependencies: transport ) - let document = try await client.fetchType( - named: "MXHangDiagnostic", - technology: "MetricKit" - ) + // -- Act -- + let document = try await client.fetchType(named: "MXHangDiagnostic", technology: "MetricKit") + // -- Assert -- #expect(document.data == data) + #expect(await transport.requestedURLs == [expectedURL]) } @Test("preserves a successful response that the text renderer cannot decode") @@ -63,22 +48,10 @@ struct AppleDocumentationClientTests { let expectedURL = try #require( URL(string: "https://developer.apple.com/tutorials/data/documentation/swift/string.json") ) - let response = try #require( - HTTPURLResponse( - url: expectedURL, - statusCode: 200, - httpVersion: nil, - headerFields: ["Content-Type": "application/json"] - ) - ) let data = Data("{\"newUpstreamShape\":true}".utf8) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: TypePageTransport( - expectedURL: expectedURL, - response: response, - data: data - ) + dependencies: HTTPTestTransport(responses: [expectedURL: .http(data: data)]) ) // -- Act -- @@ -96,78 +69,42 @@ struct AppleDocumentationClientTests { // -- Arrange -- let expectedURL = try #require( URL( - string: - "https://developer.apple.com/tutorials/data/documentation/foundation/urlsession/asyncbytes.json" - ) - ) - let response = try #require( - HTTPURLResponse( - url: expectedURL, - statusCode: 200, - httpVersion: nil, - headerFields: ["Content-Type": "application/json"] + string: "https://developer.apple.com/tutorials/data/documentation/foundation/urlsession/asyncbytes.json" ) ) + let transport = HTTPTestTransport(responses: [expectedURL: .http(data: Data("{}".utf8))]) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: TypePageTransport( - expectedURL: expectedURL, - response: response, - data: Data("{}".utf8) - ) + dependencies: transport ) // -- Act -- _ = try await client.fetchType(named: name, technology: "Foundation") // -- Assert -- - // The transport rejects any request that does not use the expected nested URL. + #expect(await transport.requestedURLs == [expectedURL]) } @Test("reports unsuccessful documentation responses") func reportsHTTPError() async throws { + // -- Arrange -- let expectedURL = try #require( URL(string: "https://developer.apple.com/tutorials/data/documentation/metrickit/missingtype.json") ) - let response = try #require( - HTTPURLResponse( - url: expectedURL, - statusCode: 500, - httpVersion: nil, - headerFields: nil - ) - ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: TypePageTransport( - expectedURL: expectedURL, - response: response, - data: Data("Not Found".utf8) + dependencies: HTTPTestTransport( + responses: [expectedURL: .http(statusCode: 500, data: Data("Not Found".utf8))] ) ) + // -- Act -- do { _ = try await client.fetchType(named: "MissingType", technology: "MetricKit") Issue.record("Expected the request to fail") - } catch let error as DefaultAppleDocumentationClient.Error { + } catch let error as DefaultAppleDocumentationClient.Error { + // -- Assert -- #expect(error == .httpStatus(500)) } } } - -private struct TypePageTransport: HTTPDataTransport { - let expectedURL: URL - let response: URLResponse - let data: Data - - func data(from url: URL) async throws -> (Data, URLResponse) { - guard url == expectedURL else { - throw TestTransportError.unexpectedURL(url) - } - return (data, response) - } -} - -private enum TestTransportError: Error { - case unexpectedURL(URL) -} diff --git a/Tests/CLITests/client/AppleDocumentationClientTypeListTests.swift b/Tests/CLITests/client/AppleDocumentationClientTypeListTests.swift index 1c79881..fe10c85 100644 --- a/Tests/CLITests/client/AppleDocumentationClientTypeListTests.swift +++ b/Tests/CLITests/client/AppleDocumentationClientTypeListTests.swift @@ -4,10 +4,6 @@ import Testing @testable import CLI -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - @Suite("Apple documentation type catalog client") struct AppleDocumentationClientTypeListTests { @Test("lists direct symbols from a technology root document") @@ -18,8 +14,8 @@ struct AppleDocumentationClientTypeListTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: TypeCatalogTestTransport( - responses: [rootURL: .init(statusCode: 200, data: swiftDataRootData)] + dependencies: HTTPTestTransport( + responses: [rootURL: .http(statusCode: 200, data: swiftDataRootData)] ) ) @@ -59,11 +55,11 @@ struct AppleDocumentationClientTypeListTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: TypeCatalogTestTransport( + dependencies: HTTPTestTransport( responses: [ - requestedRootURL: .init(statusCode: 404, data: Data()), - technologiesURL: .init(statusCode: 200, data: cryptoKitCatalogData), - resolvedRootURL: .init(statusCode: 404, data: Data()), + requestedRootURL: .http(statusCode: 404, data: Data()), + technologiesURL: .http(statusCode: 200, data: cryptoKitCatalogData), + resolvedRootURL: .http(statusCode: 404, data: Data()), ] ) ) @@ -96,10 +92,10 @@ struct AppleDocumentationClientTypeListTests { ) let client = DefaultAppleDocumentationClient( logger: Logger(label: "test") { _ in SwiftLogNoOpLogHandler() }, - dependencies: TypeCatalogTestTransport( + dependencies: HTTPTestTransport( responses: [ - rootURL: .init(statusCode: 404, data: Data()), - technologiesURL: .init(statusCode: 200, data: externalTechnologyCatalogData), + rootURL: .http(statusCode: 404, data: Data()), + technologiesURL: .http(statusCode: 200, data: externalTechnologyCatalogData), ] ) ) @@ -191,29 +187,3 @@ private let externalTechnologyCatalogData = Data( } """.utf8 ) - -private struct TypeCatalogTestTransport: HTTPDataTransport { - struct Response: Sendable { - let statusCode: Int - let data: Data - } - - let responses: [URL: Response] - - func data(from url: URL) async throws -> (Data, URLResponse) { - guard let result = responses[url] else { - throw TypeCatalogTestError.unexpectedURL(url) - } - let response = HTTPURLResponse( - url: url, - statusCode: result.statusCode, - httpVersion: nil, - headerFields: ["Content-Type": "application/json"] - )! - return (result.data, response) - } -} - -private enum TypeCatalogTestError: Error { - case unexpectedURL(URL) -} diff --git a/Tests/CLITests/http/HTTPTestTransport.swift b/Tests/CLITests/http/HTTPTestTransport.swift new file mode 100644 index 0000000..f284694 --- /dev/null +++ b/Tests/CLITests/http/HTTPTestTransport.swift @@ -0,0 +1,42 @@ +import Foundation +import Testing + +@testable import CLI + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +actor HTTPTestTransport: HTTPDataTransport { + enum Response: Sendable { + case http(statusCode: Int = 200, data: Data) + case response(data: Data, response: URLResponse) + case failure(any Error) + } + + private let responses: [URL: Response] + private(set) var requestedURLs: [URL] = [] + + init(responses: [URL: Response]) { + self.responses = responses + } + + func data(from url: URL) async throws -> (Data, URLResponse) { + requestedURLs.append(url) + let response = try #require(responses[url], "Unexpected request: \(url)") + switch response { + case .http(let statusCode, let data): + let response = try #require( + HTTPURLResponse( + url: url, statusCode: statusCode, httpVersion: nil, + headerFields: ["Content-Type": "application/json"] + ) + ) + return (data, response) + case .response(let data, let response): + return (data, response) + case .failure(let error): + throw error + } + } +} diff --git a/Tests/CLITests/http/HTTPTestTransportTests.swift b/Tests/CLITests/http/HTTPTestTransportTests.swift new file mode 100644 index 0000000..a928df8 --- /dev/null +++ b/Tests/CLITests/http/HTTPTestTransportTests.swift @@ -0,0 +1,20 @@ +import Foundation +import Testing + +@Suite("HTTP test transport") +struct HTTPTestTransportTests { + @Test("reports unexpected requests even when the caller catches the error") + func reportsUnexpectedRequests() async throws { + // -- Arrange -- + let url = try #require(URL(string: "https://example.com/unstubbed")) + let transport = HTTPTestTransport(responses: [:]) + + // -- Act -- + await withKnownIssue("Unstubbed requests must fail the calling test") { + _ = try? await transport.data(from: url) + } + + // -- Assert -- + #expect(await transport.requestedURLs == [url]) + } +}