934 lines
43 KiB
Swift
934 lines
43 KiB
Swift
import CodexBarCore
|
|
import Foundation
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
@MainActor
|
|
extension CodexAccountScopedRefreshTests {
|
|
@Test
|
|
func `single refresh persists provider snapshot for startup confirmation`() async throws {
|
|
let suite = "CodexWeeklyResetPublicationTests-single-startup-hydration"
|
|
let email = "startup-hydrated@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-startup-hydrated"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(2 * 24 * 60 * 60)
|
|
let nextBoundary = priorBoundary.addingTimeInterval(7 * 24 * 60 * 60)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 69,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let initialLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.2,
|
|
weeklyReset: nextBoundary,
|
|
updatedAt: now.addingTimeInterval(-40))
|
|
let rebound = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 68,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-20))
|
|
let snapshotURL = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-weekly-round-trip-\(UUID().uuidString).json")
|
|
defer { try? FileManager.default.removeItem(at: snapshotURL) }
|
|
let snapshotStore = FileCodexAccountUsageSnapshotStore(fileURL: snapshotURL)
|
|
let firstStore = self.makeCodexWeeklyPublicationStore(
|
|
settings: settings,
|
|
suite: suite,
|
|
snapshotStore: snapshotStore)
|
|
self.installContextualCodexProvider(on: firstStore) { _ in prior }
|
|
|
|
await firstStore.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
let persistedSnapshots = snapshotStore.load(
|
|
for: settings.codexVisibleAccountProjection.visibleAccounts)
|
|
let persisted = try #require(persistedSnapshots.first)
|
|
#expect(persistedSnapshots.count == 1)
|
|
#expect(firstStore.codexAccountSnapshots.count == 1)
|
|
#expect(firstStore.codexAccountSnapshots.first?.id == persisted.id)
|
|
#expect(persisted.account.workspaceAccountID == "acct-startup-hydrated")
|
|
#expect(persisted.account.email == email)
|
|
#expect(persisted.snapshot?.updatedAt == prior.updatedAt)
|
|
#expect(persisted.snapshot?.accountEmail(for: .codex) == email)
|
|
#expect(persisted.sourceLabel == "test-codex")
|
|
|
|
let loader = SequencedCodexSnapshotLoader(steps: [
|
|
.success(initialLow),
|
|
.success(rebound, gated: true),
|
|
])
|
|
let store = self.makeCodexWeeklyPublicationStore(
|
|
settings: settings,
|
|
suite: suite,
|
|
snapshotStore: snapshotStore)
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.codexAccountSnapshots.first?.snapshot?.updatedAt == prior.updatedAt)
|
|
self.installContextualCodexProvider(on: store) { _ in try await loader.load() }
|
|
let recorder = CodexWeeklyPublicationEventRecorder(email: email)
|
|
defer { recorder.invalidate() }
|
|
|
|
let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) }
|
|
#expect(await loader.waitUntilCallCount(2))
|
|
|
|
#expect(store.snapshots[.codex]?.updatedAt == prior.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 69)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == prior.updatedAt)
|
|
#expect(store.lastSourceLabels[.codex] == "test-codex")
|
|
#expect(recorder.usedPercents.isEmpty)
|
|
|
|
await loader.release(call: 2)
|
|
await refreshTask.value
|
|
|
|
#expect(await loader.callCount == 2)
|
|
#expect(store.snapshots[.codex]?.updatedAt == rebound.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 68)
|
|
#expect(store.lastCodexAccountScopedRefreshGuard?.identity == .providerAccount(id: "acct-startup-hydrated"))
|
|
#expect(recorder.usedPercents.isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func `single persistence rejects another member in the same workspace`() async {
|
|
let suite = "CodexWeeklyResetPublicationTests-single-persistence-member-isolation"
|
|
let email = "current-member@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-shared-workspace"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: [])
|
|
let otherMember = self.codexWeeklySnapshot(
|
|
email: "other-member@example.com",
|
|
weeklyUsedPercent: 42,
|
|
weeklyReset: Date().addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: Date())
|
|
let store = self.makeCodexWeeklyPublicationStore(
|
|
settings: settings,
|
|
suite: suite,
|
|
snapshotStore: snapshotStore)
|
|
self.installContextualCodexProvider(on: store) { _ in otherMember }
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
#expect(store.codexAccountSnapshots.isEmpty)
|
|
#expect(snapshotStore.storedSnapshots.isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func `single startup rejects hydrated snapshot from another workspace`() async throws {
|
|
let suite = "CodexWeeklyResetPublicationTests-single-startup-workspace-isolation"
|
|
let email = "workspace-isolation@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-current-workspace"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let currentAccount = try #require(settings.codexVisibleAccountProjection.visibleAccounts.first)
|
|
let otherWorkspaceAccount = CodexVisibleAccount(
|
|
id: currentAccount.id,
|
|
email: currentAccount.email,
|
|
workspaceLabel: currentAccount.workspaceLabel,
|
|
workspaceAccountID: "acct-other-workspace",
|
|
authFingerprint: currentAccount.authFingerprint,
|
|
storedAccountID: currentAccount.storedAccountID,
|
|
selectionSource: currentAccount.selectionSource,
|
|
isActive: currentAccount.isActive,
|
|
isLive: currentAccount.isLive,
|
|
canReauthenticate: currentAccount.canReauthenticate,
|
|
canRemove: currentAccount.canRemove)
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(2 * 24 * 60 * 60)
|
|
let otherWorkspacePrior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 71,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let currentSnapshot = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 42,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-20))
|
|
let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: [
|
|
CodexAccountUsageSnapshot(
|
|
account: otherWorkspaceAccount,
|
|
snapshot: otherWorkspacePrior,
|
|
error: nil,
|
|
sourceLabel: "wrong-workspace"),
|
|
])
|
|
let loader = SequencedCodexSnapshotLoader(steps: [.success(currentSnapshot, gated: true)])
|
|
let store = self.makeCodexWeeklyPublicationStore(
|
|
settings: settings,
|
|
suite: suite,
|
|
snapshotStore: snapshotStore)
|
|
self.installContextualCodexProvider(on: store) { _ in try await loader.load() }
|
|
|
|
let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) }
|
|
#expect(await loader.waitUntilCallCount(1))
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(store.lastKnownResetSnapshots[.codex] == nil)
|
|
|
|
await loader.release(call: 1)
|
|
await refreshTask.value
|
|
|
|
#expect(await loader.callCount == 1)
|
|
#expect(store.snapshots[.codex]?.updatedAt == currentSnapshot.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 42)
|
|
let persisted = try #require(snapshotStore.storedSnapshots.first)
|
|
#expect(snapshotStore.storedSnapshots.count == 1)
|
|
#expect(store.codexAccountSnapshots.count == 1)
|
|
#expect(store.codexAccountSnapshots.first?.id == persisted.id)
|
|
#expect(persisted.account.workspaceAccountID == "acct-current-workspace")
|
|
#expect(persisted.account.email == email)
|
|
#expect(persisted.snapshot?.updatedAt == currentSnapshot.updatedAt)
|
|
}
|
|
|
|
@Test
|
|
func `single refresh retains the weekly lane when a source omits it`() async {
|
|
let suite = "CodexWeeklyResetPublicationTests-single-missing-weekly"
|
|
let email = "missing-weekly@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-missing-weekly"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(2 * 24 * 60 * 60)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 57,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let partial = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: nil,
|
|
weeklyReset: nil,
|
|
updatedAt: now.addingTimeInterval(-20),
|
|
sessionUsedPercent: 31)
|
|
let loader = SequencedCodexSnapshotLoader(steps: [.success(partial)])
|
|
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() }
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(await loader.callCount == 1)
|
|
#expect(store.snapshots[.codex]?.updatedAt == partial.updatedAt)
|
|
#expect(store.snapshots[.codex]?.primary?.usedPercent == 31)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 57)
|
|
#expect(store.snapshots[.codex]?.secondary?.resetsAt == priorBoundary)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.secondary?.usedPercent == 57)
|
|
}
|
|
|
|
@Test
|
|
func `single refresh keeps prior state private until rebound confirmation publishes`() async {
|
|
let suite = "CodexWeeklyResetPublicationTests-single-gated-rebound"
|
|
let email = "gated-rebound@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-gated-rebound"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(3 * 24 * 60 * 60)
|
|
let nextBoundary = priorBoundary.addingTimeInterval(7 * 24 * 60 * 60)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 64,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let initialLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.2,
|
|
weeklyReset: nextBoundary,
|
|
updatedAt: now.addingTimeInterval(-40))
|
|
let rebound = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 63,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-20))
|
|
let loader = SequencedCodexSnapshotLoader(steps: [
|
|
.success(initialLow),
|
|
.success(rebound, gated: true),
|
|
])
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
let priorRevision = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior)
|
|
self.installContextualCodexProvider(on: store) { _ in try await loader.load() }
|
|
let recorder = CodexWeeklyPublicationEventRecorder(email: email)
|
|
defer { recorder.invalidate() }
|
|
|
|
let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) }
|
|
#expect(await loader.waitUntilCallCount(2))
|
|
|
|
#expect(store.snapshots[.codex]?.updatedAt == prior.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 64)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == prior.updatedAt)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.secondary?.usedPercent == 64)
|
|
#expect(store.errors[.codex] == "prior error")
|
|
#expect(store.lastSourceLabels[.codex] == "prior-source")
|
|
#expect(store.lastFetchAttempts[.codex]?.count == 1)
|
|
#expect(store.lastFetchAttempts[.codex]?.first?.strategyID == "prior-strategy")
|
|
#expect(store.lastFetchAttempts[.codex]?.first?.errorDescription == "prior diagnostic")
|
|
#expect(store.planUtilizationHistoryRevision == priorRevision)
|
|
#expect(recorder.usedPercents.isEmpty)
|
|
|
|
await loader.release(call: 2)
|
|
await refreshTask.value
|
|
|
|
#expect(await loader.callCount == 2)
|
|
#expect(store.snapshots[.codex]?.updatedAt == rebound.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 63)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == rebound.updatedAt)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.secondary?.usedPercent == 63)
|
|
#expect(store.errors[.codex] == nil)
|
|
#expect(store.lastSourceLabels[.codex] == "test-codex")
|
|
#expect(store.lastFetchAttempts[.codex]?.count == 1)
|
|
#expect(store.lastFetchAttempts[.codex]?.first?.strategyID == "contextual-test-codex")
|
|
#expect(recorder.usedPercents.isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func `single refresh publishes the second matching low observation only`() async {
|
|
let suite = "CodexWeeklyResetPublicationTests-single-confirmed-low"
|
|
let email = "confirmed-low@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-confirmed-low"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(2 * 24 * 60 * 60)
|
|
let nextBoundary = priorBoundary.addingTimeInterval(7 * 24 * 60 * 60)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 72,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let initialLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.2,
|
|
weeklyReset: nextBoundary,
|
|
updatedAt: now.addingTimeInterval(-40))
|
|
let confirmedLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.7,
|
|
weeklyReset: nextBoundary.addingTimeInterval(30),
|
|
updatedAt: now.addingTimeInterval(-20))
|
|
let loader = SequencedCodexSnapshotLoader(steps: [
|
|
.success(initialLow),
|
|
.success(confirmedLow),
|
|
])
|
|
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 recorder = CodexWeeklyPublicationEventRecorder(email: email)
|
|
defer { recorder.invalidate() }
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(await loader.callCount == 2)
|
|
#expect(store.snapshots[.codex]?.updatedAt == confirmedLow.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 0.7)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent != initialLow.secondary?.usedPercent)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == confirmedLow.updatedAt)
|
|
#expect(recorder.count == 1)
|
|
#expect(recorder.usedPercents == [0.7])
|
|
}
|
|
|
|
@Test(arguments: CodexRejectedConfirmationCase.allCases)
|
|
func `rejected single confirmation preserves every prior public surface`(
|
|
rejection: CodexRejectedConfirmationCase) async
|
|
{
|
|
let suite = "CodexWeeklyResetPublicationTests-rejected-\(rejection.rawValue)"
|
|
let email = "rejected-\(rejection.rawValue)@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-rejected-\(rejection.rawValue)"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(2 * 24 * 60 * 60)
|
|
let nextBoundary = priorBoundary.addingTimeInterval(7 * 24 * 60 * 60)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 81,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let initialLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.1,
|
|
weeklyReset: nextBoundary,
|
|
updatedAt: now.addingTimeInterval(-40))
|
|
let confirmationStep: SequencedCodexSnapshotLoadStep = switch rejection {
|
|
case .error:
|
|
.failure("soft confirmation failure")
|
|
case .missingBoundary:
|
|
.success(self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.4,
|
|
weeklyReset: nil,
|
|
updatedAt: now.addingTimeInterval(-20)))
|
|
case .mismatchedBoundary:
|
|
.success(self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.4,
|
|
weeklyReset: nextBoundary.addingTimeInterval(3 * 60),
|
|
updatedAt: now.addingTimeInterval(-20)))
|
|
case .differentMember:
|
|
.success(self.codexWeeklySnapshot(
|
|
email: "another-member@example.com",
|
|
weeklyUsedPercent: 0.4,
|
|
weeklyReset: nextBoundary.addingTimeInterval(30),
|
|
updatedAt: now.addingTimeInterval(-20)))
|
|
}
|
|
let loader = SequencedCodexSnapshotLoader(steps: [.success(initialLow), confirmationStep])
|
|
let store = self.makeCodexWeeklyPublicationStore(settings: settings, suite: suite)
|
|
let priorRevision = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: prior,
|
|
error: nil)
|
|
self.installContextualCodexProvider(on: store) { _ in try await loader.load() }
|
|
let recorder = CodexWeeklyPublicationEventRecorder(email: email)
|
|
defer { recorder.invalidate() }
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(await loader.callCount == 2)
|
|
#expect(store.snapshots[.codex]?.updatedAt == prior.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 81)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == prior.updatedAt)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.secondary?.usedPercent == 81)
|
|
#expect(store.errors[.codex] == nil)
|
|
#expect(store.lastSourceLabels[.codex] == "prior-source")
|
|
#expect(store.lastFetchAttempts[.codex]?.count == 1)
|
|
#expect(store.lastFetchAttempts[.codex]?.first?.strategyID == "prior-strategy")
|
|
#expect(store.lastFetchAttempts[.codex]?.first?.errorDescription == "prior diagnostic")
|
|
#expect(store.planUtilizationHistoryRevision == priorRevision)
|
|
#expect(recorder.usedPercents.isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func `confirmed reset resists a later stale pre reset observation`() async {
|
|
let suite = "CodexWeeklyResetPublicationTests-post-reset-stale"
|
|
let email = "post-reset-stale@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-post-reset-stale"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(2 * 24 * 60 * 60)
|
|
let nextBoundary = priorBoundary.addingTimeInterval(7 * 24 * 60 * 60)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 78,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let initialLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.2,
|
|
weeklyReset: nextBoundary,
|
|
updatedAt: now.addingTimeInterval(-40))
|
|
let confirmedLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.6,
|
|
weeklyReset: nextBoundary.addingTimeInterval(30),
|
|
updatedAt: now.addingTimeInterval(-20))
|
|
let stalePreReset = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 77,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-10))
|
|
let loader = SequencedCodexSnapshotLoader(steps: [
|
|
.success(initialLow),
|
|
.success(confirmedLow),
|
|
.success(stalePreReset),
|
|
])
|
|
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 recorder = CodexWeeklyPublicationEventRecorder(email: email)
|
|
defer { recorder.invalidate() }
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
let acceptedRevision = store.planUtilizationHistoryRevision
|
|
#expect(store.snapshots[.codex]?.updatedAt == confirmedLow.updatedAt)
|
|
#expect(recorder.usedPercents == [0.6])
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(await loader.callCount == 3)
|
|
#expect(store.snapshots[.codex]?.updatedAt == confirmedLow.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 0.6)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == confirmedLow.updatedAt)
|
|
#expect(store.planUtilizationHistoryRevision == acceptedRevision)
|
|
#expect(recorder.usedPercents == [0.6])
|
|
}
|
|
|
|
@Test
|
|
func `single refresh never compares a prior snapshot across provider owners`() async {
|
|
let suite = "CodexWeeklyResetPublicationTests-owner-transition"
|
|
let email = "owner-transition@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings._test_liveSystemCodexAccount = self.liveAccount(
|
|
email: email,
|
|
identity: .providerAccount(id: "acct-owner-transition-a"))
|
|
defer { settings._test_liveSystemCodexAccount = nil }
|
|
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(2 * 24 * 60 * 60)
|
|
let nextBoundary = priorBoundary.addingTimeInterval(7 * 24 * 60 * 60)
|
|
let prior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 82,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let newOwnerLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.3,
|
|
weeklyReset: nextBoundary,
|
|
updatedAt: now.addingTimeInterval(-20))
|
|
let confirmedNewOwnerLow = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.6,
|
|
weeklyReset: nextBoundary.addingTimeInterval(30),
|
|
updatedAt: now.addingTimeInterval(-10))
|
|
let loader = SequencedCodexSnapshotLoader(steps: [
|
|
.success(newOwnerLow),
|
|
.success(confirmedNewOwnerLow),
|
|
])
|
|
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: email,
|
|
identity: .providerAccount(id: "acct-owner-transition-b"))
|
|
self.installContextualCodexProvider(on: store) { _ in try await loader.load() }
|
|
let recorder = CodexWeeklyPublicationEventRecorder(email: email)
|
|
defer { recorder.invalidate() }
|
|
|
|
await store.refreshProvider(.codex, allowDisabled: true)
|
|
|
|
#expect(await loader.callCount == 2)
|
|
#expect(store.snapshots[.codex]?.updatedAt == confirmedNewOwnerLow.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 0.6)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == confirmedNewOwnerLow.updatedAt)
|
|
#expect(recorder.usedPercents.isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func `stacked refresh rejects a response explicitly owned by another member`() async throws {
|
|
let suite = "CodexWeeklyResetPublicationTests-stacked-response-email-mismatch"
|
|
let targetID = try #require(UUID(uuidString: "11111111-2222-3333-4444-555555555555"))
|
|
let siblingID = try #require(UUID(uuidString: "66666666-7777-8888-9999-AAAAAAAAAAAA"))
|
|
let targetHome = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-stacked-mismatch-target-\(UUID().uuidString)", isDirectory: true)
|
|
let siblingHome = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-stacked-mismatch-sibling-\(UUID().uuidString)", isDirectory: true)
|
|
let target = try self.makeManagedCodexWeeklyPublicationAccount(
|
|
id: targetID,
|
|
email: "target-member@example.com",
|
|
workspaceID: "shared-provider-workspace",
|
|
workspaceLabel: "Target Member",
|
|
homeURL: targetHome)
|
|
let sibling = try self.makeManagedCodexWeeklyPublicationAccount(
|
|
id: siblingID,
|
|
email: "sibling-member@example.com",
|
|
workspaceID: "sibling-provider-workspace",
|
|
workspaceLabel: "Sibling Member",
|
|
homeURL: siblingHome)
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.multiAccountMenuLayout = .stacked
|
|
let storeURL = try self.makeManagedAccountStoreURL(accounts: [target, sibling])
|
|
defer {
|
|
settings._test_managedCodexAccountStoreURL = nil
|
|
try? FileManager.default.removeItem(at: storeURL)
|
|
try? FileManager.default.removeItem(at: targetHome)
|
|
try? FileManager.default.removeItem(at: siblingHome)
|
|
}
|
|
settings._test_managedCodexAccountStoreURL = storeURL
|
|
settings.codexActiveSource = .managedAccount(id: targetID)
|
|
|
|
let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: [])
|
|
let store = self.makeCodexWeeklyPublicationStore(
|
|
settings: settings,
|
|
suite: suite,
|
|
snapshotStore: snapshotStore)
|
|
let now = Date()
|
|
let targetMismatch = self.codexWeeklySnapshot(
|
|
email: "another-member@example.com",
|
|
weeklyUsedPercent: 64,
|
|
weeklyReset: now.addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: now)
|
|
let siblingSnapshot = self.codexWeeklySnapshot(
|
|
email: sibling.email,
|
|
weeklyUsedPercent: 22,
|
|
weeklyReset: now.addingTimeInterval(2 * 24 * 60 * 60),
|
|
updatedAt: now)
|
|
self.installContextualCodexProvider(on: store) { context in
|
|
let isTarget = context.env["CODEX_HOME"] == targetHome.path
|
|
return isTarget ? targetMismatch : siblingSnapshot
|
|
}
|
|
|
|
await store.refreshCodexVisibleAccountsForMenu()
|
|
|
|
#expect(store.snapshots[.codex] == nil)
|
|
#expect(!store.codexAccountSnapshots.contains { $0.account.storedAccountID == targetID })
|
|
#expect(store.codexAccountSnapshots.contains { $0.account.storedAccountID == siblingID })
|
|
#expect(!snapshotStore.storedSnapshots.contains { $0.account.storedAccountID == targetID })
|
|
#expect(snapshotStore.storedSnapshots.contains { $0.account.storedAccountID == siblingID })
|
|
}
|
|
|
|
@Test
|
|
func `stacked refresh never publishes or persists an unconfirmed account reset`() async throws {
|
|
let suite = "CodexWeeklyResetPublicationTests-stacked"
|
|
let email = "shared-stacked@example.com"
|
|
let settings = self.makeSettingsStore(suite: suite)
|
|
settings.refreshFrequency = .manual
|
|
settings.codexCookieSource = .off
|
|
settings.multiAccountMenuLayout = .stacked
|
|
|
|
let suspiciousID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-303030303030"))
|
|
let siblingID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-313131313131"))
|
|
let suspiciousHome = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-weekly-suspicious-\(UUID().uuidString)", isDirectory: true)
|
|
let siblingHome = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-weekly-sibling-\(UUID().uuidString)", isDirectory: true)
|
|
let suspiciousAccount = try self.makeManagedCodexWeeklyPublicationAccount(
|
|
id: suspiciousID,
|
|
email: email,
|
|
workspaceID: "acct-stacked-suspicious",
|
|
workspaceLabel: "Suspicious Workspace",
|
|
homeURL: suspiciousHome)
|
|
let siblingAccount = try self.makeManagedCodexWeeklyPublicationAccount(
|
|
id: siblingID,
|
|
email: email,
|
|
workspaceID: "acct-stacked-sibling",
|
|
workspaceLabel: "Sibling Workspace",
|
|
homeURL: siblingHome)
|
|
let managedStoreURL = try self.makeManagedAccountStoreURL(accounts: [suspiciousAccount, siblingAccount])
|
|
defer {
|
|
settings._test_managedCodexAccountStoreURL = nil
|
|
try? FileManager.default.removeItem(at: managedStoreURL)
|
|
try? FileManager.default.removeItem(at: suspiciousHome)
|
|
try? FileManager.default.removeItem(at: siblingHome)
|
|
}
|
|
settings._test_managedCodexAccountStoreURL = managedStoreURL
|
|
settings.codexActiveSource = .managedAccount(id: suspiciousID)
|
|
|
|
let visibleAccounts = settings.codexVisibleAccountProjection.visibleAccounts
|
|
#expect(visibleAccounts.count == 2)
|
|
let suspiciousVisible = try #require(visibleAccounts.first {
|
|
$0.workspaceAccountID == "acct-stacked-suspicious"
|
|
})
|
|
let siblingVisible = try #require(visibleAccounts.first {
|
|
$0.workspaceAccountID == "acct-stacked-sibling"
|
|
})
|
|
let now = Date()
|
|
let priorBoundary = now.addingTimeInterval(3 * 24 * 60 * 60)
|
|
let nextBoundary = priorBoundary.addingTimeInterval(7 * 24 * 60 * 60)
|
|
let suspiciousPriorAccount = CodexVisibleAccount(
|
|
id: "prior-email-derived-row",
|
|
email: email,
|
|
workspaceLabel: suspiciousVisible.workspaceLabel,
|
|
workspaceAccountID: suspiciousVisible.workspaceAccountID,
|
|
authFingerprint: "prior-auth-fingerprint",
|
|
storedAccountID: suspiciousVisible.storedAccountID,
|
|
selectionSource: suspiciousVisible.selectionSource,
|
|
isActive: suspiciousVisible.isActive,
|
|
isLive: suspiciousVisible.isLive,
|
|
canReauthenticate: suspiciousVisible.canReauthenticate,
|
|
canRemove: suspiciousVisible.canRemove)
|
|
let suspiciousPrior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 84,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let siblingPrior = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 62,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-60))
|
|
let priorRows = [
|
|
CodexAccountUsageSnapshot(
|
|
account: suspiciousPriorAccount,
|
|
snapshot: suspiciousPrior,
|
|
error: nil,
|
|
sourceLabel: "cached-suspicious"),
|
|
CodexAccountUsageSnapshot(
|
|
account: siblingVisible,
|
|
snapshot: siblingPrior,
|
|
error: nil,
|
|
sourceLabel: "cached-sibling"),
|
|
]
|
|
let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: priorRows)
|
|
let store = self.makeCodexWeeklyPublicationStore(
|
|
settings: settings,
|
|
suite: suite,
|
|
snapshotStore: snapshotStore)
|
|
store.codexAccountSnapshots = priorRows
|
|
let priorRevision = await self.seedCodexWeeklyPublicationState(
|
|
store: store,
|
|
settings: settings,
|
|
snapshot: suspiciousPrior,
|
|
error: nil)
|
|
|
|
let suspiciousInitial = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.1,
|
|
weeklyReset: nextBoundary,
|
|
updatedAt: now.addingTimeInterval(-40))
|
|
let suspiciousRejected = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 0.4,
|
|
weeklyReset: nil,
|
|
updatedAt: now.addingTimeInterval(-20))
|
|
let siblingUpdated = self.codexWeeklySnapshot(
|
|
email: email,
|
|
weeklyUsedPercent: 63,
|
|
weeklyReset: priorBoundary,
|
|
updatedAt: now.addingTimeInterval(-20))
|
|
let suspiciousLoader = SequencedCodexSnapshotLoader(steps: [
|
|
.success(suspiciousInitial),
|
|
.success(suspiciousRejected, gated: true),
|
|
])
|
|
let siblingLoader = SequencedCodexSnapshotLoader(steps: [.success(siblingUpdated)])
|
|
let suspiciousHomePath = suspiciousHome.path
|
|
let siblingHomePath = siblingHome.path
|
|
self.installContextualCodexProvider(on: store) { context in
|
|
switch context.env["CODEX_HOME"] {
|
|
case suspiciousHomePath:
|
|
try await suspiciousLoader.load()
|
|
case siblingHomePath:
|
|
try await siblingLoader.load()
|
|
default:
|
|
throw TestRefreshError(message: "Unexpected CODEX_HOME routing")
|
|
}
|
|
}
|
|
let currentRecorder = CodexWeeklyPublicationEventRecorder(email: email)
|
|
defer { currentRecorder.invalidate() }
|
|
|
|
let refreshTask = Task { await store.refreshCodexVisibleAccountsForMenu() }
|
|
#expect(await suspiciousLoader.waitUntilCallCount(2))
|
|
#expect(await siblingLoader.waitUntilCompletedCallCount(1))
|
|
|
|
try self.expectBlockedStackedResetState(
|
|
store: store,
|
|
snapshotStore: snapshotStore,
|
|
prior: suspiciousPrior,
|
|
historyRevision: priorRevision,
|
|
recorder: currentRecorder)
|
|
|
|
await suspiciousLoader.release(call: 2)
|
|
await refreshTask.value
|
|
|
|
#expect(await suspiciousLoader.callCount == 2)
|
|
#expect(await siblingLoader.callCount == 1)
|
|
try self.expectFinalStackedResetState(
|
|
store: store,
|
|
snapshotStore: snapshotStore,
|
|
expectation: FinalStackedResetExpectation(
|
|
targetAccount: suspiciousVisible,
|
|
siblingAccount: siblingVisible,
|
|
targetPrior: suspiciousPrior,
|
|
siblingUpdated: siblingUpdated,
|
|
historyRevision: priorRevision),
|
|
recorder: currentRecorder)
|
|
}
|
|
|
|
private func expectBlockedStackedResetState(
|
|
store: UsageStore,
|
|
snapshotStore: RecordingCodexAccountUsageSnapshotStore,
|
|
prior: UsageSnapshot,
|
|
historyRevision: Int,
|
|
recorder: CodexWeeklyPublicationEventRecorder) throws
|
|
{
|
|
let target = try #require(store.codexAccountSnapshots.first {
|
|
$0.account.workspaceAccountID == "acct-stacked-suspicious"
|
|
})
|
|
#expect(target.snapshot?.updatedAt == prior.updatedAt)
|
|
#expect(target.snapshot?.secondary?.usedPercent == 84)
|
|
#expect(target.sourceLabel == "cached-suspicious")
|
|
#expect(snapshotStore.storedSnapshots.isEmpty)
|
|
#expect(store.snapshots[.codex]?.updatedAt == prior.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 84)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == prior.updatedAt)
|
|
#expect(store.errors[.codex] == nil)
|
|
#expect(store.lastSourceLabels[.codex] == "prior-source")
|
|
#expect(store.lastFetchAttempts[.codex]?.first?.strategyID == "prior-strategy")
|
|
#expect(store.planUtilizationHistoryRevision == historyRevision)
|
|
#expect(recorder.usedPercents.isEmpty)
|
|
}
|
|
|
|
private func expectFinalStackedResetState(
|
|
store: UsageStore,
|
|
snapshotStore: RecordingCodexAccountUsageSnapshotStore,
|
|
expectation: FinalStackedResetExpectation,
|
|
recorder: CodexWeeklyPublicationEventRecorder) throws
|
|
{
|
|
let target = try #require(store.codexAccountSnapshots.first {
|
|
$0.account.workspaceAccountID == "acct-stacked-suspicious"
|
|
})
|
|
#expect(target.account.id == expectation.targetAccount.id)
|
|
#expect(target.account.email == expectation.targetAccount.email)
|
|
#expect(target.snapshot?.accountEmail(for: .codex) == expectation.targetAccount.email)
|
|
#expect(target.snapshot?.updatedAt == expectation.targetPrior.updatedAt)
|
|
#expect(target.snapshot?.secondary?.usedPercent == 84)
|
|
#expect(target.error == nil)
|
|
#expect(target.sourceLabel == "cached-suspicious")
|
|
#expect(!store.codexAccountSnapshots.contains {
|
|
$0.account.workspaceAccountID == "acct-stacked-suspicious"
|
|
&& $0.snapshot?.secondary?.usedPercent == 0.1
|
|
})
|
|
let persistedTarget = try #require(snapshotStore.storedSnapshots.first {
|
|
$0.account.workspaceAccountID == "acct-stacked-suspicious"
|
|
})
|
|
#expect(persistedTarget.snapshot?.updatedAt == expectation.targetPrior.updatedAt)
|
|
#expect(persistedTarget.snapshot?.secondary?.usedPercent == 84)
|
|
#expect(persistedTarget.error == nil)
|
|
#expect(persistedTarget.sourceLabel == "cached-suspicious")
|
|
let sibling = try #require(store.codexAccountSnapshots.first {
|
|
$0.account.workspaceAccountID == "acct-stacked-sibling"
|
|
})
|
|
#expect(sibling.snapshot?.updatedAt == expectation.siblingUpdated.updatedAt)
|
|
#expect(sibling.snapshot?.secondary?.usedPercent == 63)
|
|
#expect(snapshotStore.storedSnapshots.count == 2)
|
|
#expect(Set(snapshotStore.storedSnapshots.map(\.id)) == Set([
|
|
expectation.targetAccount.id,
|
|
expectation.siblingAccount.id,
|
|
]))
|
|
#expect(!snapshotStore.storedSnapshots.contains {
|
|
$0.account.workspaceAccountID == "acct-stacked-suspicious"
|
|
&& $0.snapshot?.secondary?.usedPercent == 0.1
|
|
})
|
|
#expect(store.snapshots[.codex]?.updatedAt == expectation.targetPrior.updatedAt)
|
|
#expect(store.snapshots[.codex]?.secondary?.usedPercent == 84)
|
|
#expect(store.snapshots[.codex]?.accountEmail(for: .codex) == expectation.targetAccount.email)
|
|
#expect(store.lastKnownResetSnapshots[.codex]?.updatedAt == expectation.targetPrior.updatedAt)
|
|
#expect(
|
|
store.lastKnownResetSnapshots[.codex]?.accountEmail(for: .codex) == expectation.targetAccount.email)
|
|
#expect(store.errors[.codex] == nil)
|
|
#expect(store.lastSourceLabels[.codex] == "prior-source")
|
|
#expect(store.lastFetchAttempts[.codex]?.first?.strategyID == "prior-strategy")
|
|
#expect(store.planUtilizationHistoryRevision == expectation.historyRevision)
|
|
#expect(recorder.usedPercents.isEmpty)
|
|
}
|
|
}
|
|
|
|
private struct FinalStackedResetExpectation {
|
|
let targetAccount: CodexVisibleAccount
|
|
let siblingAccount: CodexVisibleAccount
|
|
let targetPrior: UsageSnapshot
|
|
let siblingUpdated: UsageSnapshot
|
|
let historyRevision: Int
|
|
}
|
|
|
|
enum CodexRejectedConfirmationCase: String, CaseIterable, Sendable {
|
|
case differentMember
|
|
case error
|
|
case missingBoundary
|
|
case mismatchedBoundary
|
|
}
|
|
|
|
private final class CodexWeeklyPublicationEventRecorder: @unchecked Sendable {
|
|
private let email: String
|
|
private let lock = NSLock()
|
|
private var observations: [Double] = []
|
|
private var token: NSObjectProtocol?
|
|
|
|
init(email: String) {
|
|
self.email = email
|
|
self.token = NotificationCenter.default.addObserver(
|
|
forName: .codexbarWeeklyLimitReset,
|
|
object: nil,
|
|
queue: nil)
|
|
{ [weak self] notification in
|
|
guard let self,
|
|
let event = notification.object as? WeeklyLimitResetEvent
|
|
else {
|
|
return
|
|
}
|
|
let usedPercent = MainActor.assumeIsolated { () -> Double? in
|
|
guard event.provider == .codex, event.accountLabel == self.email else { return nil }
|
|
return event.usedPercent
|
|
}
|
|
guard let usedPercent else { return }
|
|
self.lock.lock()
|
|
self.observations.append(usedPercent)
|
|
self.lock.unlock()
|
|
}
|
|
}
|
|
|
|
var count: Int {
|
|
self.lock.lock()
|
|
defer { self.lock.unlock() }
|
|
return self.observations.count
|
|
}
|
|
|
|
var usedPercents: [Double] {
|
|
self.lock.lock()
|
|
defer { self.lock.unlock() }
|
|
return self.observations
|
|
}
|
|
|
|
func invalidate() {
|
|
guard let token else { return }
|
|
NotificationCenter.default.removeObserver(token)
|
|
self.token = nil
|
|
}
|
|
|
|
deinit {
|
|
self.invalidate()
|
|
}
|
|
}
|