diff options
| author | Christian Cleberg <[email protected]> | 2026-04-15 15:29:57 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-15 15:30:40 -0500 |
| commit | 06c36c4352f47655cb39a5042a7e5fd475c118e1 (patch) | |
| tree | 448054ebc937a258c694ed2338a7c2f7edbd17f2 /HutchTests | |
| parent | 3727afa0fdb6e7b9884bbe00b9aed3e994952c5a (diff) | |
| download | hutch-06c36c4352f47655cb39a5042a7e5fd475c118e1.tar.gz hutch-06c36c4352f47655cb39a5042a7e5fd475c118e1.tar.bz2 hutch-06c36c4352f47655cb39a5042a7e5fd475c118e1.zip | |
feat: add custom hutch user-agent to api calls
Implements: https://todo.sr.ht/~ccleberg/hutch/64
Diffstat (limited to 'HutchTests')
| -rw-r--r-- | HutchTests/BundleUserAgentTests.swift | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/HutchTests/BundleUserAgentTests.swift b/HutchTests/BundleUserAgentTests.swift new file mode 100644 index 0000000..b2f701b --- /dev/null +++ b/HutchTests/BundleUserAgentTests.swift @@ -0,0 +1,130 @@ +import Foundation +import Testing +@testable import Hutch + +// MARK: - URLProtocol stub + +/// Captures outgoing URLRequests and returns a minimal 401 so callers fail fast +/// without touching the real network. +private final class CapturingURLProtocol: URLProtocol, @unchecked Sendable { + nonisolated(unsafe) static var capturedRequests: [URLRequest] = [] + + override class func canInit(with request: URLRequest) -> Bool { true } + override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } + + override func startLoading() { + CapturingURLProtocol.capturedRequests.append(request) + let response = HTTPURLResponse( + url: request.url!, + statusCode: 401, + httpVersion: nil, + headerFields: nil + )! + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocol(self, didLoad: Data()) + client?.urlProtocolDidFinishLoading(self) + } + + override func stopLoading() {} + + static func makeSession() -> URLSession { + let config = URLSessionConfiguration.ephemeral + config.protocolClasses = [CapturingURLProtocol.self] + return URLSession(configuration: config) + } +} + +// MARK: - Tests + +/// Tests are serialized because CapturingURLProtocol uses shared static state. +@Suite(.serialized) +struct BundleUserAgentTests { + + // MARK: Bundle extension + + @Test + func hutchUserAgentHasNameSlashVersion() { + let ua = Bundle.main.hutchUserAgent + let parts = ua.split(separator: "/", maxSplits: 1) + #expect(parts.count == 2) + #expect(parts[0] == "Hutch") + #expect(!parts[1].isEmpty) + } + + @Test + func hutchUserAgentContainsNoParenthesizedContext() { + // The old SystemStatusService user-agent appended "(System Status)". + // The shared agent should be plain "Hutch/<version>". + #expect(!Bundle.main.hutchUserAgent.contains("(")) + } + + // MARK: SRHTClient + + @Test + func sRHTClientSetsUserAgentOnExecute() async { + CapturingURLProtocol.capturedRequests = [] + let client = SRHTClient(session: CapturingURLProtocol.makeSession(), token: "test-token") + + _ = try? await client.execute( + service: .builds, + query: "{ jobs { results { id } } }", + responseType: [String: String].self + ) + + guard let captured = CapturingURLProtocol.capturedRequests.first else { + Issue.record("No request was captured by SRHTClient.execute.") + return + } + #expect(captured.value(forHTTPHeaderField: "User-Agent") == Bundle.main.hutchUserAgent) + } + + @Test + func sRHTClientSetsUserAgentOnFetchText() async { + CapturingURLProtocol.capturedRequests = [] + let client = SRHTClient(session: CapturingURLProtocol.makeSession(), token: "test-token") + let url = try! #require(URL(string: "https://builds.sr.ht/~test/job/1/log")) + + _ = try? await client.fetchText(url: url) + + guard let captured = CapturingURLProtocol.capturedRequests.first else { + Issue.record("No request was captured by SRHTClient.fetchText.") + return + } + #expect(captured.value(forHTTPHeaderField: "User-Agent") == Bundle.main.hutchUserAgent) + } + + // MARK: SystemStatusService + + @Test + func systemStatusServiceSetsUserAgent() async { + CapturingURLProtocol.capturedRequests = [] + let service = SystemStatusService(session: CapturingURLProtocol.makeSession()) + + _ = try? await service.fetchSnapshotHTML() + + guard let captured = CapturingURLProtocol.capturedRequests.first else { + Issue.record("No request was captured by SystemStatusService.") + return + } + #expect(captured.value(forHTTPHeaderField: "User-Agent") == Bundle.main.hutchUserAgent) + } + + // MARK: HutchStatsService + + @Test + func hutchStatsServiceSetsUserAgent() async { + CapturingURLProtocol.capturedRequests = [] + let service = HutchStatsService( + session: CapturingURLProtocol.makeSession(), + configuration: AppConfiguration(environment: [:]) + ) + + _ = try? await service.fetchContributionCalendar(actor: "testuser", endingOn: .now) + + guard let captured = CapturingURLProtocol.capturedRequests.first else { + Issue.record("No request was captured by HutchStatsService.") + return + } + #expect(captured.value(forHTTPHeaderField: "User-Agent") == Bundle.main.hutchUserAgent) + } +} |
