466 lines
21 KiB
Swift
466 lines
21 KiB
Swift
import CodexBarCore
|
|
import Foundation
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
@MainActor
|
|
extension CodexAccountScopedRefreshTests {
|
|
@Test
|
|
func `credits completion retires usage from another workspace member`() async {
|
|
let suite = "CodexUsageOwnerRaceTests-credits-first"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "member-a@example.com",
|
|
identity: .providerAccount(id: "shared-workspace"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: "member-a@example.com",
|
|
weeklyUsedPercent: 72,
|
|
weeklyReset: now.addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
_ = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior,
|
|
error: nil)
|
|
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "member-b@example.com",
|
|
identity: .providerAccount(id: "shared-workspace"))
|
|
store._test_codexCreditsLoaderOverride = { self.credits(remaining: 23) }
|
|
defer { store._test_codexCreditsLoaderOverride = nil }
|
|
|
|
await store.refreshCreditsIfNeeded()
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.lastCodexUsagePublicationGuard == nil)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "member-b@example.com")
|
|
#expect(store.credits?.remaining == 23)
|
|
|
|
let nextReset = now.addingTimeInterval(9 * 24 * 60 * 60)
|
|
let loader = SequencedCodexSnapshotLoader(steps: [
|
|
.success(self.codexWeeklySnapshot(
|
|
email: "member-b@example.com",
|
|
weeklyUsedPercent: 0.2,
|
|
weeklyReset: nextReset,
|
|
updatedAt: now.addingTimeInterval(-30))),
|
|
.failure("confirmation unavailable"),
|
|
])
|
|
self.installContextualCodexProvider(on: store) { _ in try await loader.load() }
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(await loader.callCount == 2)
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.credits?.remaining == 23)
|
|
}
|
|
|
|
@Test
|
|
func `dashboard cleanup retires cli usage from another workspace member`() async {
|
|
let suite = "CodexUsageOwnerRaceTests-dashboard-cleanup"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .auto
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "member-a@example.com",
|
|
identity: .providerAccount(id: "shared-workspace"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: "member-a@example.com",
|
|
weeklyUsedPercent: 72,
|
|
weeklyReset: Date().addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: Date().addingTimeInterval(-60))
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
_ = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior,
|
|
error: nil)
|
|
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "member-b@example.com",
|
|
identity: .providerAccount(id: "shared-workspace"))
|
|
await store.applyOpenAIDashboard(
|
|
self.dashboard(email: "member-a@example.com", creditsRemaining: 8, usedPercent: 40),
|
|
targetEmail: "member-b@example.com")
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.lastCodexUsagePublicationGuard == nil)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "member-b@example.com")
|
|
}
|
|
|
|
@Test
|
|
func `stale usage rejection preserves newer owner credits`() async {
|
|
let suite = "CodexUsageOwnerRaceTests-stale-usage-new-credits"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-a@example.com",
|
|
identity: .providerAccount(id: "owner-a"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: "owner-a@example.com",
|
|
weeklyUsedPercent: 61,
|
|
weeklyReset: Date().addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: Date().addingTimeInterval(-60))
|
|
_ = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior,
|
|
error: nil)
|
|
let blocker = BlockingCodexFetchStrategy()
|
|
self.installBlockingCodexProvider(on: store, blocker: blocker)
|
|
|
|
let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) }
|
|
await blocker.waitUntilStarted()
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-b@example.com",
|
|
identity: .providerAccount(id: "owner-b"))
|
|
store._test_codexCreditsLoaderOverride = { self.credits(remaining: 31) }
|
|
defer { store._test_codexCreditsLoaderOverride = nil }
|
|
await store.refreshCreditsIfNeeded()
|
|
#expect(store.credits?.remaining == 31)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "owner-b@example.com")
|
|
|
|
await blocker.resume(with: .success(self.codexSnapshot(email: "owner-a@example.com", usedPercent: 62)))
|
|
await refreshTask.value
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.credits?.remaining == 31)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "owner-b@example.com")
|
|
}
|
|
|
|
@Test
|
|
func `stacked stale selection preserves newer selected account credits`() async throws {
|
|
let suite = "CodexUsageOwnerRaceTests-stacked-stale-selection"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings.multiAccountMenuLayout = .stacked
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "stacked-a@example.com",
|
|
identity: .providerAccount(id: "stacked-owner-a"))
|
|
let managedID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-515151515151"))
|
|
let managedHome = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-stacked-owner-race-\(UUID().uuidString)", isDirectory: true)
|
|
let managedAccount = try self.makeManagedCodexWeeklyPublicationAccount(
|
|
id: managedID,
|
|
email: "stacked-b@example.com",
|
|
workspaceID: "stacked-owner-b",
|
|
workspaceLabel: "Team B",
|
|
homeURL: managedHome)
|
|
let accountStoreURL = try self.makeManagedAccountStoreURL(accounts: [managedAccount])
|
|
defer {
|
|
settings._test_liveSystemCodexAccount = nil
|
|
settings._test_managedCodexAccountStoreURL = nil
|
|
try? FileManager.default.removeItem(at: accountStoreURL)
|
|
try? FileManager.default.removeItem(at: managedHome)
|
|
}
|
|
settings._test_managedCodexAccountStoreURL = accountStoreURL
|
|
settings.codexActiveSource = .liveSystem
|
|
|
|
let now = Date()
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: "stacked-a@example.com",
|
|
weeklyUsedPercent: 61,
|
|
weeklyReset: now.addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
_ = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior,
|
|
error: nil)
|
|
let blocker = BlockingCodexFetchStrategy()
|
|
let managedHomePath = managedHome.path
|
|
let managedSnapshot = self.codexSnapshot(email: "stacked-b@example.com", usedPercent: 33)
|
|
self.installContextualCodexProvider(on: store) { context in
|
|
if context.env["CODEX_HOME"] == managedHomePath {
|
|
return managedSnapshot
|
|
}
|
|
return try await blocker.awaitResult()
|
|
}
|
|
|
|
let refreshTask = Task { await store.refreshCodexVisibleAccountsForMenu() }
|
|
await blocker.waitUntilStarted()
|
|
settings.codexActiveSource = .managedAccount(id: managedID)
|
|
store._test_codexCreditsLoaderOverride = { self.credits(remaining: 37) }
|
|
defer { store._test_codexCreditsLoaderOverride = nil }
|
|
await store.refreshCreditsIfNeeded()
|
|
#expect(store.credits?.remaining == 37)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "stacked-b@example.com")
|
|
|
|
await blocker.resume(with: .success(self.codexSnapshot(email: "stacked-a@example.com", usedPercent: 62)))
|
|
await refreshTask.value
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.credits?.remaining == 37)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "stacked-b@example.com")
|
|
}
|
|
|
|
@Test
|
|
func `in flight success retires prior usage after owner switch`() async {
|
|
let suite = "CodexUsageOwnerRaceTests-in-flight-success"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-a@example.com",
|
|
identity: .providerAccount(id: "owner-a"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: "owner-a@example.com",
|
|
weeklyUsedPercent: 61,
|
|
weeklyReset: Date().addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: Date().addingTimeInterval(-60))
|
|
_ = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior,
|
|
error: nil)
|
|
let blocker = BlockingCodexFetchStrategy()
|
|
self.installBlockingCodexProvider(on: store, blocker: blocker)
|
|
|
|
let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) }
|
|
await blocker.waitUntilStarted()
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-b@example.com",
|
|
identity: .providerAccount(id: "owner-b"))
|
|
await blocker.resume(with: .success(self.codexSnapshot(email: "owner-a@example.com", usedPercent: 62)))
|
|
await refreshTask.value
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.lastCodexUsagePublicationGuard == nil)
|
|
}
|
|
|
|
@Test
|
|
func `in flight failure retires prior usage after owner switch`() async {
|
|
let suite = "CodexUsageOwnerRaceTests-in-flight-failure"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-a@example.com",
|
|
identity: .providerAccount(id: "owner-a"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: "owner-a@example.com",
|
|
weeklyUsedPercent: 61,
|
|
weeklyReset: Date().addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: Date().addingTimeInterval(-60))
|
|
_ = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior,
|
|
error: nil)
|
|
let blocker = BlockingCodexFetchStrategy()
|
|
self.installBlockingCodexProvider(on: store, blocker: blocker)
|
|
|
|
let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) }
|
|
await blocker.waitUntilStarted()
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-b@example.com",
|
|
identity: .providerAccount(id: "owner-b"))
|
|
await blocker.resume(with: .failure(TestRefreshError(message: "old owner failure")))
|
|
await refreshTask.value
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.lastCodexUsagePublicationGuard == nil)
|
|
}
|
|
|
|
@Test
|
|
func `in flight confirmation retires prior usage after owner switch`() async {
|
|
let suite = "CodexUsageOwnerRaceTests-in-flight-confirmation"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-a@example.com",
|
|
identity: .providerAccount(id: "owner-a"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let priorReset = now.addingTimeInterval(2 * 24 * 60 * 60)
|
|
let nextReset = priorReset.addingTimeInterval(7 * 24 * 60 * 60)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: "owner-a@example.com",
|
|
weeklyUsedPercent: 61,
|
|
weeklyReset: priorReset,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let loader = SequencedCodexSnapshotLoader(steps: [
|
|
.success(self.codexWeeklySnapshot(
|
|
email: "owner-a@example.com",
|
|
weeklyUsedPercent: 0.2,
|
|
weeklyReset: nextReset,
|
|
updatedAt: now.addingTimeInterval(-40))),
|
|
.success(self.codexWeeklySnapshot(
|
|
email: "owner-a@example.com",
|
|
weeklyUsedPercent: 60,
|
|
weeklyReset: priorReset,
|
|
updatedAt: now.addingTimeInterval(-20)), gated: true),
|
|
])
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
_ = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior,
|
|
error: nil)
|
|
self.installContextualCodexProvider(on: store) { _ in try await loader.load() }
|
|
|
|
let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) }
|
|
#expect(await loader.waitUntilCallCount(2))
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-b@example.com",
|
|
identity: .providerAccount(id: "owner-b"))
|
|
await loader.release(call: 2)
|
|
await refreshTask.value
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.lastCodexUsagePublicationGuard == nil)
|
|
}
|
|
|
|
@Test
|
|
func `unresolved live publication remains stable across the next failed refresh`() async throws {
|
|
let suite = "CodexUsageOwnerRaceTests-unresolved-continuity"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings.codexActiveSource = .liveSystem
|
|
settings._test_liveSystemCodexAccount = nil
|
|
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
self.installImmediateCodexProvider(
|
|
on: store,
|
|
snapshot: self.codexSnapshot(email: "discovered@example.com", usedPercent: 27))
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
let publishedAt = try #require(store.snapshots[.codex]?.updatedAt)
|
|
#expect(store.lastCodexUsagePublicationGuard?.identity == .emailOnly(
|
|
normalizedEmail: "discovered@example.com"))
|
|
self.installFailingCodexProvider(
|
|
on: store,
|
|
error: TestRefreshError(message: "temporary failure"))
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(store.snapshots[.codex]?.updatedAt == publishedAt)
|
|
#expect(store.lastCodexUsagePublicationGuard?.accountKey == "discovered@example.com")
|
|
}
|
|
|
|
@Test
|
|
func `fresh failure is retired before attaching credits to another owner`() async {
|
|
let suite = "CodexUsageOwnerRaceTests-fresh-failure-then-credits"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-a@example.com",
|
|
identity: .providerAccount(id: "owner-a"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
self.installFailingCodexProvider(
|
|
on: store,
|
|
error: TestRefreshError(message: "owner A unavailable"))
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.errors[.codex] == "owner A unavailable")
|
|
#expect(store.lastFetchAttempts[.codex]?.isEmpty == false)
|
|
#expect(store.lastCodexUsagePublicationGuard?.accountKey == "owner-a@example.com")
|
|
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-b@example.com",
|
|
identity: .providerAccount(id: "owner-b"))
|
|
store._test_codexCreditsLoaderOverride = { self.credits(remaining: 29) }
|
|
defer { store._test_codexCreditsLoaderOverride = nil }
|
|
|
|
await store.refreshCreditsIfNeeded()
|
|
|
|
#expect(store.errors[.codex] == nil)
|
|
#expect(store.lastFetchAttempts[.codex] == nil)
|
|
#expect(store.lastSourceLabels[.codex] == nil)
|
|
#expect(store.credits?.remaining == 29)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "owner-b@example.com")
|
|
}
|
|
|
|
@Test
|
|
func `stacked fresh failure follows its owner across credits attachment`() async {
|
|
let suite = "CodexUsageOwnerRaceTests-stacked-failure-then-credits"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-a@example.com",
|
|
identity: .providerAccount(id: "owner-a"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
let ownerA = CodexVisibleAccount(
|
|
id: "live:owner-a",
|
|
email: "owner-a@example.com",
|
|
workspaceAccountID: "owner-a",
|
|
storedAccountID: nil,
|
|
selectionSource: .liveSystem,
|
|
isActive: true,
|
|
isLive: true,
|
|
canReauthenticate: false,
|
|
canRemove: false)
|
|
let failure = ProviderFetchOutcome(
|
|
result: .failure(TestRefreshError(message: "owner A unavailable")),
|
|
attempts: [ProviderFetchAttempt(
|
|
strategyID: "stacked-test",
|
|
kind: .cli,
|
|
wasAvailable: true,
|
|
errorDescription: "owner A unavailable")])
|
|
|
|
await store.applySelectedCodexVisibleAccountOutcome(
|
|
failure,
|
|
account: ownerA,
|
|
snapshot: nil,
|
|
sourceLabel: nil,
|
|
limitResetOwnerKey: nil)
|
|
|
|
store._test_codexCreditsLoaderOverride = { self.credits(remaining: 19) }
|
|
defer { store._test_codexCreditsLoaderOverride = nil }
|
|
await store.refreshCreditsIfNeeded()
|
|
|
|
#expect(store.errors[.codex] == "owner A unavailable")
|
|
#expect(store.lastFetchAttempts[.codex]?.first?.strategyID == "stacked-test")
|
|
#expect(store.credits?.remaining == 19)
|
|
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: "owner-b@example.com",
|
|
identity: .providerAccount(id: "owner-b"))
|
|
await store.refreshCreditsIfNeeded()
|
|
|
|
#expect(store.errors[.codex] == nil)
|
|
#expect(store.lastFetchAttempts[.codex] == nil)
|
|
#expect(store.credits?.remaining == 19)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "owner-b@example.com")
|
|
}
|
|
}
|