a native ios client for gitbay

client ios swift

https://gitbay.org

gitbayTests/LiveInstanceTests.swift

main
gitbay-ios/gitbayTests/LiveInstanceTests.swift history · blame · raw

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}