gitbayTests/LiveInstanceTests.swift
87 lines · 3096 bytes
1import Foundation
2import Testing
3@testable import gitbay
4
5/// Exercises a real instance. Skipped unless a token is provided:
6///
7/// GITBAY_TEST_TOKEN=$(gitbay auth token create --name ci --scope read --ttl 1h)
8///
9/// Optionally GITBAY_TEST_INSTANCE (defaults to gitbay.org). A read-scoped
10/// token is enough — nothing here writes.
11struct LiveInstanceTests {
12
13 nonisolated private static var token: String? {
14 ProcessInfo.processInfo.environment["GITBAY_TEST_TOKEN"]
15 }
16
17 nonisolated private struct Whoami: Decodable, Sendable {
18 let username: String
19 }
20
21 @Test(.enabled(if: token != nil, "GITBAY_TEST_TOKEN is not set"))
22 func secondReadOfARealInstanceRevalidatesFor304() async throws {
23 let host = ProcessInfo.processInfo.environment["GITBAY_TEST_INSTANCE"] ?? "gitbay.org"
24 let instance = try GitbayInstance(url: host)
25
26 // A session that counts responses without touching their content,
27 // so the 304 is observed rather than inferred.
28 let client = GitbayClient(
29 instance: instance,
30 token: try #require(Self.token),
31 session: RecordingProtocol.session()
32 )
33 RecordingProtocol.reset()
34
35 let first = try await client.read(["whoami"], as: Whoami.self)
36 let second = try await client.read(["whoami"], as: Whoami.self)
37
38 #expect(first.username == second.username)
39 #expect(!first.username.isEmpty)
40 let statuses = RecordingProtocol.statuses
41 #expect(statuses == [200, 304])
42 }
43}
44
45/// Passes requests through to the network, recording each response status.
46nonisolated final class RecordingProtocol: URLProtocol, @unchecked Sendable {
47
48 private static let recorded = Mutex<[Int]>([])
49 private static let inner = URLSession(configuration: .ephemeral)
50
51 static var statuses: [Int] {
52 recorded.withLock { $0 }
53 }
54
55 static func reset() {
56 recorded.withLock { $0.removeAll() }
57 }
58
59 static func session() -> URLSession {
60 let configuration = URLSessionConfiguration.ephemeral
61 configuration.protocolClasses = [RecordingProtocol.self]
62 return URLSession(configuration: configuration)
63 }
64
65 override static func canInit(with _: URLRequest) -> Bool { true }
66
67 override static func canonicalRequest(for request: URLRequest) -> URLRequest { request }
68
69 override func startLoading() {
70 let request = self.request
71 Task {
72 do {
73 let (data, response) = try await Self.inner.data(for: request)
74 if let http = response as? HTTPURLResponse {
75 Self.recorded.withLock { $0.append(http.statusCode) }
76 }
77 self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
78 self.client?.urlProtocol(self, didLoad: data)
79 self.client?.urlProtocolDidFinishLoading(self)
80 } catch {
81 self.client?.urlProtocol(self, didFailWithError: error)
82 }
83 }
84 }
85
86 override func stopLoading() {}
87}