Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 4 additions & 34 deletions Tests/CLITests/client/AppleDocumentationClientErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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),
]
)
)
Expand Down Expand Up @@ -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)
}
72 changes: 31 additions & 41 deletions Tests/CLITests/client/AppleDocumentationClientLoggingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down Expand Up @@ -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<LoggingTestTransport>.Error.httpStatus(status)) {
await #expect(throws: DefaultAppleDocumentationClient<HTTPTestTransport>.Error.httpStatus(status)) {
try await client.fetchDocumentationPage(path: "/documentation/swift")
}

Expand All @@ -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 --
Expand All @@ -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<LoggingTestTransport>.Error.invalidResponse) {
await #expect(throws: DefaultAppleDocumentationClient<HTTPTestTransport>.Error.invalidResponse) {
try await client.fetchType(named: "String", technology: "Swift")
}

Expand All @@ -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 --
Expand All @@ -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<LoggingTestTransport>.Error.typeSearchNoResults(
throws: DefaultAppleDocumentationClient<HTTPTestTransport>.Error.typeSearchNoResults(
query: "Missing", technology: "Swift", technologyURL: "https://developer.apple.com/documentation/swift"
)
) {
Expand Down Expand Up @@ -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()),
])
)

Expand Down Expand Up @@ -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)),
])
)

Expand All @@ -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"))
}
}
60 changes: 15 additions & 45 deletions Tests/CLITests/client/AppleDocumentationClientRootTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -22,18 +18,18 @@ 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
)

// -- Act --
await #expect(
throws: DefaultAppleDocumentationClient<RootTestTransport>.Error.unsupportedTechnology(
throws: DefaultAppleDocumentationClient<HTTPTestTransport>.Error.unsupportedTechnology(
name: "Apple CryptoKit", url: "https://developer.apple.com/documentation/cryptokit"
)
) {
Expand All @@ -57,17 +53,17 @@ 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
)

// -- Act --
await #expect(
throws: DefaultAppleDocumentationClient<RootTestTransport>.Error.unsupportedTechnology(
throws: DefaultAppleDocumentationClient<HTTPTestTransport>.Error.unsupportedTechnology(
name: "Apple CryptoKit", url: "https://developer.apple.com/documentation/cryptokit"
)
) {
Expand Down Expand Up @@ -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
Expand All @@ -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<RootTestTransport>.Error.httpStatus(status)) {
await #expect(throws: DefaultAppleDocumentationClient<HTTPTestTransport>.Error.httpStatus(status)) {
if search {
_ = try await client.searchTypes(query: "AES", technology: "CryptoKit")
} else {
Expand All @@ -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)
}
}
Loading
Loading