import CodexBarCore import Foundation import Testing @testable import CodexBar @Suite(.serialized) @MainActor struct ManagedCodexAccountCoordinatorTests { @Test func `coordinator exposes in flight state and rejects overlapping managed authentication`() async throws { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) defer { try? FileManager.default.removeItem(at: root) } let existingAccountID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-111111111111")) let runner = BlockingManagedCodexLoginRunner() let service = ManagedCodexAccountService( store: InMemoryManagedCodexAccountStoreForCoordinatorTests( accounts: ManagedCodexAccountSet(version: 1, accounts: [])), homeFactory: CoordinatorTestManagedCodexHomeFactory(root: root), loginRunner: runner, identityReader: CoordinatorStubManagedCodexIdentityReader(email: "user@example.com")) let coordinator = ManagedCodexAccountCoordinator(service: service) let authTask = Task { try await coordinator.authenticateManagedAccount(existingAccountID: existingAccountID) } await runner.waitUntilStarted() #expect(coordinator.isAuthenticatingManagedAccount) #expect(coordinator.authenticatingManagedAccountID == existingAccountID) await #expect(throws: ManagedCodexAccountCoordinatorError.authenticationInProgress) { try await coordinator.authenticateManagedAccount() } await runner.resume() let account = try await authTask.value #expect(account.email == "user@example.com") #expect(coordinator.isAuthenticatingManagedAccount == false) #expect(coordinator.authenticatingManagedAccountID == nil) } @Test func `coordinator clears in flight state after managed login timeout`() async throws { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) defer { try? FileManager.default.removeItem(at: root) } let loginResult = CodexLoginRunner.Result(outcome: .timedOut, output: "timed out") let existingAccountID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-222222222222")) let service = ManagedCodexAccountService( store: InMemoryManagedCodexAccountStoreForCoordinatorTests( accounts: ManagedCodexAccountSet(version: 1, accounts: [])), homeFactory: CoordinatorTestManagedCodexHomeFactory(root: root), loginRunner: TimedOutManagedCodexLoginRunner(result: loginResult), identityReader: CoordinatorStubManagedCodexIdentityReader(email: "user@example.com")) let coordinator = ManagedCodexAccountCoordinator(service: service) do { _ = try await coordinator.authenticateManagedAccount(existingAccountID: existingAccountID, timeout: 0.2) Issue.record("Expected managed login timeout to throw") } catch let error as ManagedCodexAccountServiceError { #expect(error == .loginFailed(loginResult)) } catch { Issue.record("Expected ManagedCodexAccountServiceError.loginFailed, got \(error)") } #expect(coordinator.isAuthenticatingManagedAccount == false) #expect(coordinator.authenticatingManagedAccountID == nil) } } private actor BlockingManagedCodexLoginRunner: ManagedCodexLoginRunning { private var waiters: [CheckedContinuation] = [] private var startedWaiters: [CheckedContinuation] = [] private var didStart = false func run(homePath _: String, timeout _: TimeInterval) async -> CodexLoginRunner.Result { self.didStart = true self.startedWaiters.forEach { $0.resume() } self.startedWaiters.removeAll() return await withCheckedContinuation { continuation in self.waiters.append(continuation) } } func waitUntilStarted() async { if self.didStart { return } await withCheckedContinuation { continuation in self.startedWaiters.append(continuation) } } func resume() { let result = CodexLoginRunner.Result(outcome: .success, output: "ok") self.waiters.forEach { $0.resume(returning: result) } self.waiters.removeAll() } } private struct TimedOutManagedCodexLoginRunner: ManagedCodexLoginRunning { let result: CodexLoginRunner.Result func run(homePath _: String, timeout _: TimeInterval) async -> CodexLoginRunner.Result { self.result } } private final class InMemoryManagedCodexAccountStoreForCoordinatorTests: ManagedCodexAccountStoring, @unchecked Sendable { var snapshot: ManagedCodexAccountSet init(accounts: ManagedCodexAccountSet) { self.snapshot = accounts } func loadAccounts() throws -> ManagedCodexAccountSet { self.snapshot } func storeAccounts(_ accounts: ManagedCodexAccountSet) throws { self.snapshot = accounts } func ensureFileExists() throws -> URL { FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) } } private final class CoordinatorTestManagedCodexHomeFactory: ManagedCodexHomeProducing, @unchecked Sendable { let root: URL init(root: URL) { self.root = root } func makeHomeURL() -> URL { self.root.appendingPathComponent(UUID().uuidString, isDirectory: true) } func validateManagedHomeForDeletion(_ url: URL) throws { try ManagedCodexHomeFactory(root: self.root).validateManagedHomeForDeletion(url) } } private final class CoordinatorStubManagedCodexIdentityReader: ManagedCodexIdentityReading, @unchecked Sendable { let email: String init(email: String) { self.email = email } func loadAccountIdentity(homePath _: String) throws -> CodexAuthBackedAccount { CodexAuthBackedAccount( identity: CodexIdentityResolver.resolve(accountId: nil, email: self.email), email: self.email, plan: "Pro") } }