import CodexBarCore import Foundation import Testing #if canImport(Darwin) import Darwin #else import Glibc #endif @Suite(.serialized) struct GeminiStatusProbeAPITests { @Test func `missing credentials throws not logged in`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path) await Self.expectError(.notLoggedIn) { _ = try await probe.fetch() } } @Test(arguments: ["gemini-api-key", "api-key"]) func `rejects api key auth types`(authType: String) async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeSettings(authType: authType) let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path) await Self.expectError(.unsupportedAuthType("API key")) { _ = try await probe.fetch() } } @Test func `rejects vertex auth type`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeSettings(authType: "vertex-ai") let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path) await Self.expectError(.unsupportedAuthType("Vertex AI")) { _ = try await probe.fetch() } } @Test func `refreshes expired token and updates stored credentials`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: "old-token", refreshToken: "refresh-token", expiry: Date().addingTimeInterval(-3600), idToken: GeminiAPITestHelpers.makeIDToken(email: "user@example.com")) let binURL = try env.writeFakeGeminiCLI() let previousValue = ProcessInfo.processInfo.environment["GEMINI_CLI_PATH"] setenv("GEMINI_CLI_PATH", binURL.path, 1) defer { if let previousValue { setenv("GEMINI_CLI_PATH", previousValue, 1) } else { unsetenv("GEMINI_CLI_PATH") } } let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "oauth2.googleapis.com": // Fail the refresh if the client_id did not come from the test stub. // This guards against the probe accidentally extracting OAuth creds // from an unrelated Gemini install on the developer's machine. let body = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? "" guard body.contains("client_id=test-client-id") else { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 400, body: Data()) } let json = GeminiAPITestHelpers.jsonData([ "access_token": "new-token", "expires_in": 3600, "id_token": GeminiAPITestHelpers.makeIDToken(email: "user@example.com"), ]) return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json) case "cloudresourcemanager.googleapis.com": let json = GeminiAPITestHelpers.jsonData(["projects": []]) return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json) case "cloudcode-pa.googleapis.com": if url.path == "/v1internal:loadCodeAssist" { let auth = request.value(forHTTPHeaderField: "Authorization") if auth != "Bearer new-token" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse()) } if url.path != "/v1internal:retrieveUserQuota" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } let auth = request.value(forHTTPHeaderField: "Authorization") if auth != "Bearer new-token" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.sampleQuotaResponse()) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 2, homeDirectory: env.homeURL.path, dataLoader: dataLoader) let snapshot = try await probe.fetch() #expect(snapshot.accountPlan == "Paid") let updated = try env.readCredentials() #expect(updated["access_token"] as? String == "new-token") } @Test func `refreshes when stored Gemini credentials only have refresh token`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: nil, refreshToken: "refresh-token", expiry: Date().addingTimeInterval(3600), idToken: nil) let binURL = try env.writeFakeGeminiCLI() let previousValue = ProcessInfo.processInfo.environment["GEMINI_CLI_PATH"] setenv("GEMINI_CLI_PATH", binURL.path, 1) defer { if let previousValue { setenv("GEMINI_CLI_PATH", previousValue, 1) } else { unsetenv("GEMINI_CLI_PATH") } } let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "oauth2.googleapis.com": let body = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? "" guard body.contains("client_id=test-client-id") else { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 400, body: Data()) } let json = GeminiAPITestHelpers.jsonData([ "access_token": "new-token", "expires_in": 3600, "id_token": GeminiAPITestHelpers.makeIDToken(email: "user@example.com"), ]) return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json) case "cloudresourcemanager.googleapis.com": return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.jsonData(["projects": []])) case "cloudcode-pa.googleapis.com": if url.path == "/v1internal:loadCodeAssist" { return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse()) } let auth = request.value(forHTTPHeaderField: "Authorization") guard auth == "Bearer new-token" else { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.sampleQuotaResponse()) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 2, homeDirectory: env.homeURL.path, dataLoader: dataLoader) let snapshot = try await probe.fetch() #expect(snapshot.accountEmail == "user@example.com") let updated = try env.readCredentials() #expect(updated["access_token"] as? String == "new-token") } @Test func `refreshes expired token with nix share layout`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: "old-token", refreshToken: "refresh-token", expiry: Date().addingTimeInterval(-3600), idToken: GeminiAPITestHelpers.makeIDToken(email: "user@example.com")) let binURL = try env.writeFakeGeminiCLI(layout: .nixShare) let previousValue = ProcessInfo.processInfo.environment["GEMINI_CLI_PATH"] setenv("GEMINI_CLI_PATH", binURL.path, 1) defer { if let previousValue { setenv("GEMINI_CLI_PATH", previousValue, 1) } else { unsetenv("GEMINI_CLI_PATH") } } let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "oauth2.googleapis.com": // Fail the refresh if the client_id did not come from the test stub. // This guards against the probe accidentally extracting OAuth creds // from an unrelated Gemini install on the developer's machine. let body = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? "" guard body.contains("client_id=test-client-id") else { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 400, body: Data()) } let json = GeminiAPITestHelpers.jsonData([ "access_token": "new-token", "expires_in": 3600, "id_token": GeminiAPITestHelpers.makeIDToken(email: "user@example.com"), ]) return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json) case "cloudresourcemanager.googleapis.com": let json = GeminiAPITestHelpers.jsonData(["projects": []]) return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json) case "cloudcode-pa.googleapis.com": if url.path == "/v1internal:loadCodeAssist" { let auth = request.value(forHTTPHeaderField: "Authorization") if auth != "Bearer new-token" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse()) } if url.path != "/v1internal:retrieveUserQuota" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } let auth = request.value(forHTTPHeaderField: "Authorization") if auth != "Bearer new-token" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.sampleQuotaResponse()) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 2, homeDirectory: env.homeURL.path, dataLoader: dataLoader) let snapshot = try await probe.fetch() #expect(snapshot.accountPlan == "Paid") } @Test func `refreshes expired token with fnm bundle layout when fnm keeps stdout open`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } let childPIDFile = env.homeURL.appendingPathComponent("fnm-child.pid") defer { if let text = try? String(contentsOf: childPIDFile, encoding: .utf8), let childPID = pid_t(text.trimmingCharacters(in: .whitespacesAndNewlines)) { _ = kill(childPID, SIGKILL) } } try env.writeCredentials( accessToken: "old-token", refreshToken: "refresh-token", expiry: Date().addingTimeInterval(-3600), idToken: GeminiAPITestHelpers.makeIDToken(email: "user@example.com")) let binURL = try env.writeFakeGeminiCLI(layout: .fnmBundle) // Match the real fnm layout: package root is inside the same multishell // dir as the bin symlink target, under lib/node_modules/@google/gemini-cli. let multishellRoot = binURL.deletingLastPathComponent().deletingLastPathComponent() let packageJSONPath = multishellRoot .appendingPathComponent("lib") .appendingPathComponent("node_modules") .appendingPathComponent("@google") .appendingPathComponent("gemini-cli") .appendingPathComponent("package.json") let npmRoot = packageJSONPath .deletingLastPathComponent() .deletingLastPathComponent() .deletingLastPathComponent() .path _ = try env.writeFakeFnm( npmRoot: npmRoot, geminiPackageJSONPath: packageJSONPath.path, holdNpmRootStdoutOpen: true) let previousPath = ProcessInfo.processInfo.environment["PATH"] let previousPIDFile = ProcessInfo.processInfo.environment["CODEXBAR_TEST_CHILD_PID_FILE"] let fakeBinDir = env.homeURL.appendingPathComponent("bin").path let pathValue = if let previousPath, !previousPath.isEmpty { "\(fakeBinDir):\(binURL.deletingLastPathComponent().path):\(previousPath)" } else { "\(fakeBinDir):\(binURL.deletingLastPathComponent().path)" } setenv("PATH", pathValue, 1) setenv("CODEXBAR_TEST_CHILD_PID_FILE", childPIDFile.path, 1) let previousGeminiPath = ProcessInfo.processInfo.environment["GEMINI_CLI_PATH"] setenv("GEMINI_CLI_PATH", binURL.path, 1) defer { if let previousPath { setenv("PATH", previousPath, 1) } else { unsetenv("PATH") } if let previousPIDFile { setenv("CODEXBAR_TEST_CHILD_PID_FILE", previousPIDFile, 1) } else { unsetenv("CODEXBAR_TEST_CHILD_PID_FILE") } if let previousGeminiPath { setenv("GEMINI_CLI_PATH", previousGeminiPath, 1) } else { unsetenv("GEMINI_CLI_PATH") } } let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "oauth2.googleapis.com": // Fail the refresh if the client_id did not come from the test stub. // This guards against the probe accidentally extracting OAuth creds // from an unrelated Gemini install on the developer's machine. let body = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? "" guard body.contains("client_id=test-client-id") else { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 400, body: Data()) } let json = GeminiAPITestHelpers.jsonData([ "access_token": "new-token", "expires_in": 3600, "id_token": GeminiAPITestHelpers.makeIDToken(email: "user@example.com"), ]) return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json) case "cloudresourcemanager.googleapis.com": let json = GeminiAPITestHelpers.jsonData(["projects": []]) return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json) case "cloudcode-pa.googleapis.com": if url.path == "/v1internal:loadCodeAssist" { let auth = request.value(forHTTPHeaderField: "Authorization") if auth != "Bearer new-token" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse()) } if url.path != "/v1internal:retrieveUserQuota" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } let auth = request.value(forHTTPHeaderField: "Authorization") if auth != "Bearer new-token" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.sampleQuotaResponse()) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 2, homeDirectory: env.homeURL.path, dataLoader: dataLoader) let snapshot = try await probe.fetch() #expect(snapshot.accountPlan == "Paid") let childPIDText = try String(contentsOf: childPIDFile, encoding: .utf8) let childPID = try #require(pid_t(childPIDText.trimmingCharacters(in: .whitespacesAndNewlines))) #expect(kill(childPID, 0) == 0, "package discovery should return while the stdout-holding child is alive") let updated = try env.readCredentials() #expect(updated["access_token"] as? String == "new-token") } @Test func `fnm helper timeout hard stops a process that ignores SIGTERM`() throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } let pidFile = env.homeURL.appendingPathComponent("fnm-timeout.pid") let helper = env.homeURL.appendingPathComponent("fnm-timeout") try """ #!/bin/sh printf '%s\\n' "$$" > "$1" trap '' TERM while true; do sleep 1; done """.write(to: helper, atomically: true, encoding: .utf8) try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: helper.path) let clock = ContinuousClock() let start = clock.now let result = GeminiStatusProbe.runProcess( executable: helper.path, arguments: [pidFile.path], environment: [:], timeout: 5) let elapsed = start.duration(to: clock.now) let text = try String(contentsOf: pidFile, encoding: .utf8) let processID = try #require(pid_t(text.trimmingCharacters(in: .whitespacesAndNewlines))) defer { _ = kill(processID, SIGKILL) } #expect(result == nil) #expect(kill(processID, 0) == -1) #expect(elapsed < .seconds(7.5), "Ignored SIGTERM should escalate to SIGKILL, took \(elapsed)") } @Test func `fnm helper completed no-output failure returns before deadline`() throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } let helper = env.homeURL.appendingPathComponent("fnm-failure") try """ #!/bin/sh exit 23 """.write(to: helper, atomically: true, encoding: .utf8) try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: helper.path) let clock = ContinuousClock() let start = clock.now let result = GeminiStatusProbe.runProcess( executable: helper.path, arguments: [], environment: [:], timeout: 10) #expect(result == nil) #expect(start.duration(to: clock.now) < .seconds(5)) } @Test func `fnm helper successful output returns first line`() throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } let helper = env.homeURL.appendingPathComponent("fnm-success") try """ #!/bin/sh sleep 0.05 printf '%s\n' '/tmp/gemini-package' printf '%s\n' 'ignored trailing output' """.write(to: helper, atomically: true, encoding: .utf8) try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: helper.path) let result = GeminiStatusProbe.runProcess( executable: helper.path, arguments: [], environment: [:], timeout: 2) #expect(result == "/tmp/gemini-package") } @Test func `refreshes expired token with homebrew bundle layout`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: "old-token", refreshToken: "refresh-token", expiry: Date().addingTimeInterval(-3600), idToken: GeminiAPITestHelpers.makeIDToken(email: "user@example.com")) let binURL = try env.writeFakeGeminiCLI(layout: .homebrewBundle) let previousGeminiPath = ProcessInfo.processInfo.environment["GEMINI_CLI_PATH"] setenv("GEMINI_CLI_PATH", binURL.path, 1) defer { if let previousGeminiPath { setenv("GEMINI_CLI_PATH", previousGeminiPath, 1) } else { unsetenv("GEMINI_CLI_PATH") } } let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "oauth2.googleapis.com": let body = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? "" guard body.contains("client_id=test-client-id") else { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 400, body: Data()) } let json = GeminiAPITestHelpers.jsonData([ "access_token": "new-token", "expires_in": 3600, "id_token": GeminiAPITestHelpers.makeIDToken(email: "user@example.com"), ]) return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json) case "cloudresourcemanager.googleapis.com": return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.jsonData(["projects": []])) case "cloudcode-pa.googleapis.com": guard request.value(forHTTPHeaderField: "Authorization") == "Bearer new-token" else { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) } if url.path == "/v1internal:loadCodeAssist" { return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse()) } if url.path == "/v1internal:retrieveUserQuota" { return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.sampleQuotaResponse()) } return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 2, homeDirectory: env.homeURL.path, dataLoader: dataLoader) let snapshot = try await probe.fetch() #expect(snapshot.accountPlan == "Paid") let updated = try env.readCredentials() #expect(updated["access_token"] as? String == "new-token") } @Test func `uses code assist project for quota`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: "token", refreshToken: nil, expiry: Date().addingTimeInterval(3600), idToken: nil) final class ProjectCapture: @unchecked Sendable { private let lock = NSLock() private var value: String? func set(_ newValue: String?) { self.lock.lock() self.value = newValue self.lock.unlock() } func get() -> String? { self.lock.lock() defer { self.lock.unlock() } return self.value } } let projectId = "managed-project-123" let seenProject = ProjectCapture() let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "cloudcode-pa.googleapis.com": if url.path == "/v1internal:loadCodeAssist" { return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.loadCodeAssistResponse( tierId: "free-tier", projectId: projectId)) } if url.path == "/v1internal:retrieveUserQuota" { if let body = request.httpBody, let json = try? JSONSerialization.jsonObject(with: body) as? [String: Any] { seenProject.set(json["project"] as? String) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.sampleQuotaResponse()) } return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) case "cloudresourcemanager.googleapis.com": return GeminiAPITestHelpers.response(url: url.absoluteString, status: 500, body: Data()) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader) _ = try await probe.fetch() #expect(seenProject.get() == projectId) } @Test func `falls back to curl loader when URL session times out`() async throws { let calls = LoaderCalls() let url = try #require(URL(string: "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota")) let request = URLRequest(url: url) let body = Data("{\"ok\":true}".utf8) let loader = GeminiStatusProbe.dataLoaderWithCurlFallback( primary: { _ in calls.incrementPrimary() throw URLError(.timedOut) }, fallback: { request in calls.incrementFallback() let (response, data) = GeminiAPITestHelpers.response( url: request.url!.absoluteString, status: 200, body: body) return (data, response) }) let (loadedBody, loadedResponse) = try await loader(request) let counts = calls.counts() #expect(loadedBody == body) #expect((loadedResponse as? HTTPURLResponse)?.statusCode == 200) #expect(counts.primary == 1) #expect(counts.fallback == 1) } @Test func `does not fall back to curl loader for non-timeout errors`() async throws { let calls = LoaderCalls() let url = try #require(URL(string: "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota")) let request = URLRequest(url: url) let loader = GeminiStatusProbe.dataLoaderWithCurlFallback( primary: { _ in calls.incrementPrimary() throw URLError(.cannotFindHost) }, fallback: { request in calls.incrementFallback() let (response, data) = GeminiAPITestHelpers.response( url: request.url!.absoluteString, status: 200, body: Data()) return (data, response) }) do { _ = try await loader(request) Issue.record("Expected non-timeout URLSession error") } catch let error as URLError { #expect(error.code == .cannotFindHost) } let counts = calls.counts() #expect(counts.primary == 1) #expect(counts.fallback == 0) } @Test func `reports api errors`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: "token", refreshToken: nil, expiry: Date().addingTimeInterval(3600), idToken: nil) let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "cloudresourcemanager.googleapis.com": return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.jsonData(["projects": []])) case "cloudcode-pa.googleapis.com": if url.path != "/v1internal:retrieveUserQuota" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } return GeminiAPITestHelpers.response(url: url.absoluteString, status: 500, body: Data()) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader) await Self.expectError(.apiError("HTTP 500")) { _ = try await probe.fetch() } } @Test func `reports not logged in when access token missing`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: "", refreshToken: nil, expiry: Date().addingTimeInterval(3600), idToken: nil) let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path) await Self.expectError(.notLoggedIn) { _ = try await probe.fetch() } } @Test func `reports not logged in on401`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: "token", refreshToken: nil, expiry: Date().addingTimeInterval(3600), idToken: nil) let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "cloudresourcemanager.googleapis.com": return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.jsonData(["projects": []])) case "cloudcode-pa.googleapis.com": if url.path != "/v1internal:retrieveUserQuota" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data()) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader) await Self.expectError(.notLoggedIn) { _ = try await probe.fetch() } } @Test func `reports parse errors for invalid payload`() async throws { let env = try GeminiTestEnvironment() defer { env.cleanup() } try env.writeCredentials( accessToken: "token", refreshToken: nil, expiry: Date().addingTimeInterval(3600), idToken: nil) let dataLoader = GeminiAPITestHelpers.dataLoader { request in guard let url = request.url, let host = url.host else { throw URLError(.badURL) } switch host { case "cloudresourcemanager.googleapis.com": return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.jsonData(["projects": []])) case "cloudcode-pa.googleapis.com": if url.path != "/v1internal:retrieveUserQuota" { return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } return GeminiAPITestHelpers.response( url: url.absoluteString, status: 200, body: GeminiAPITestHelpers.jsonData(["buckets": []])) default: return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data()) } } let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader) do { _ = try await probe.fetch() #expect(Bool(false)) } catch { let cast = error as? GeminiStatusProbeError #expect(cast?.errorDescription?.contains("Could not parse Gemini usage") == true) } } private static func expectError( _ expected: GeminiStatusProbeError, operation: () async throws -> Void) async { do { try await operation() #expect(Bool(false)) } catch { #expect(error as? GeminiStatusProbeError == expected) } } private final class LoaderCalls: @unchecked Sendable { private let lock = NSLock() private var primaryCount = 0 private var fallbackCount = 0 func incrementPrimary() { self.lock.lock() self.primaryCount += 1 self.lock.unlock() } func incrementFallback() { self.lock.lock() self.fallbackCount += 1 self.lock.unlock() } func counts() -> (primary: Int, fallback: Int) { self.lock.lock() defer { self.lock.unlock() } return (self.primaryCount, self.fallbackCount) } } }