216 lines
8.9 KiB
Swift
216 lines
8.9 KiB
Swift
import CodexBarCore
|
|
import Foundation
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
@Suite(.serialized)
|
|
@MainActor
|
|
struct CodexLimitResetOwnerKeyTests {
|
|
@Test
|
|
func `limit reset owner stays stable for the same provider workspace and email`() throws {
|
|
let store = self.makeLimitResetOwnerStore(suffix: "provider-stability")
|
|
let original = self.limitResetVisibleAccount(
|
|
id: "original-row",
|
|
email: " Person-One@Example.Test ",
|
|
workspaceLabel: "Fixture Team",
|
|
workspaceAccountID: "workspace-fixture-stable",
|
|
authFingerprint: "auth-fixture-old")
|
|
let relabeled = self.limitResetVisibleAccount(
|
|
id: "relabeled-row",
|
|
email: "person-one@example.test",
|
|
workspaceLabel: "Renamed Fixture Team",
|
|
workspaceAccountID: " workspace-fixture-stable ",
|
|
authFingerprint: "auth-fixture-new")
|
|
|
|
let originalKey = try #require(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: original,
|
|
visibleAccounts: [original]))
|
|
let relabeledKey = try #require(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: relabeled,
|
|
visibleAccounts: [relabeled]))
|
|
|
|
#expect(originalKey == relabeledKey)
|
|
self.expectOpaqueLimitResetOwnerKey(
|
|
originalKey,
|
|
excludes: ["workspace-fixture-stable", "person-one@example.test"])
|
|
}
|
|
|
|
@Test
|
|
func `different emails in the same provider workspace use different owner keys`() throws {
|
|
let store = self.makeLimitResetOwnerStore(suffix: "provider-member-distinct")
|
|
let first = self.limitResetVisibleAccount(
|
|
id: "first-member",
|
|
email: "first-member@example.test",
|
|
workspaceAccountID: "workspace-fixture-shared")
|
|
let second = self.limitResetVisibleAccount(
|
|
id: "second-member",
|
|
email: "second-member@example.test",
|
|
workspaceAccountID: "workspace-fixture-shared")
|
|
|
|
let firstKey = try #require(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: first,
|
|
visibleAccounts: [first, second]))
|
|
let secondKey = try #require(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: second,
|
|
visibleAccounts: [first, second]))
|
|
|
|
#expect(firstKey != secondKey)
|
|
}
|
|
|
|
@Test
|
|
func `different provider workspaces with the same email use different owner keys`() throws {
|
|
let store = self.makeLimitResetOwnerStore(suffix: "provider-distinct")
|
|
let first = self.limitResetVisibleAccount(
|
|
id: "first-row",
|
|
email: "shared-person@example.test",
|
|
workspaceAccountID: "workspace-fixture-one")
|
|
let second = self.limitResetVisibleAccount(
|
|
id: "second-row",
|
|
email: "shared-person@example.test",
|
|
workspaceAccountID: "workspace-fixture-two")
|
|
let visibleAccounts = [first, second]
|
|
|
|
let firstKey = try #require(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: first,
|
|
visibleAccounts: visibleAccounts))
|
|
let secondKey = try #require(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: second,
|
|
visibleAccounts: visibleAccounts))
|
|
|
|
#expect(firstKey != secondKey)
|
|
self.expectOpaqueLimitResetOwnerKey(firstKey, excludes: ["workspace-fixture-one", "shared-person@example.test"])
|
|
self.expectOpaqueLimitResetOwnerKey(
|
|
secondKey,
|
|
excludes: ["workspace-fixture-two", "shared-person@example.test"])
|
|
}
|
|
|
|
@Test
|
|
func `email only owner fails closed even for one visible row`() {
|
|
let store = self.makeLimitResetOwnerStore(suffix: "email-unique")
|
|
let account = self.limitResetVisibleAccount(
|
|
id: "email-row-original",
|
|
email: " Unique-Person@Example.Test ",
|
|
workspaceLabel: "Fixture Personal",
|
|
authFingerprint: "auth-fixture-old")
|
|
|
|
#expect(store.codexLimitResetOwnerKey(forVisibleAccount: account, visibleAccounts: [account]) == nil)
|
|
#expect(CodexLimitResetOwnerKey(
|
|
identity: .emailOnly(normalizedEmail: "unique-person@example.test"),
|
|
accountEmail: "unique-person@example.test") == nil)
|
|
}
|
|
|
|
@Test
|
|
func `duplicate email only rows fail closed`() {
|
|
let store = self.makeLimitResetOwnerStore(suffix: "email-ambiguous")
|
|
let first = self.limitResetVisibleAccount(
|
|
id: "email-row-one",
|
|
email: "ambiguous-person@example.test")
|
|
let second = self.limitResetVisibleAccount(
|
|
id: "email-row-two",
|
|
email: " Ambiguous-Person@Example.Test ")
|
|
let visibleAccounts = [first, second]
|
|
|
|
#expect(store.codexLimitResetOwnerKey(forVisibleAccount: first, visibleAccounts: visibleAccounts) == nil)
|
|
#expect(store.codexLimitResetOwnerKey(forVisibleAccount: second, visibleAccounts: visibleAccounts) == nil)
|
|
}
|
|
|
|
@Test
|
|
func `provider row wins its own identity while same email fallback fails closed`() throws {
|
|
let store = self.makeLimitResetOwnerStore(suffix: "mixed-identity")
|
|
let providerBacked = self.limitResetVisibleAccount(
|
|
id: "provider-row",
|
|
email: "mixed-person@example.test",
|
|
workspaceAccountID: "workspace-fixture-provider")
|
|
let emailOnly = self.limitResetVisibleAccount(
|
|
id: "email-row",
|
|
email: "mixed-person@example.test")
|
|
let visibleAccounts = [providerBacked, emailOnly]
|
|
|
|
let providerKey = try #require(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: providerBacked,
|
|
visibleAccounts: visibleAccounts))
|
|
|
|
#expect(providerKey == CodexLimitResetOwnerKey(
|
|
identity: .providerAccount(id: "workspace-fixture-provider"),
|
|
accountEmail: "mixed-person@example.test"))
|
|
#expect(store.codexLimitResetOwnerKey(forVisibleAccount: emailOnly, visibleAccounts: visibleAccounts) == nil)
|
|
}
|
|
|
|
@Test
|
|
func `guard and visible row normalize the same provider owner`() throws {
|
|
let store = self.makeLimitResetOwnerStore(suffix: "provider-normalization")
|
|
let account = self.limitResetVisibleAccount(
|
|
id: "provider-row",
|
|
email: "provider-person@example.test",
|
|
workspaceAccountID: " workspace-fixture-mixed-case ")
|
|
let guardValue = CodexAccountScopedRefreshGuard(
|
|
source: account.selectionSource,
|
|
identity: .providerAccount(id: " WORKSPACE-FIXTURE-MIXED-CASE "),
|
|
accountKey: account.email)
|
|
|
|
let visibleKey = try #require(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: account,
|
|
visibleAccounts: [account]))
|
|
let guardKey = try #require(store.codexLimitResetOwnerKey(
|
|
expectedGuard: guardValue,
|
|
visibleAccounts: [account]))
|
|
|
|
#expect(visibleKey == guardKey)
|
|
}
|
|
|
|
@Test
|
|
func `unresolved owner identity fails closed`() {
|
|
let store = self.makeLimitResetOwnerStore(suffix: "unresolved")
|
|
let guardValue = CodexAccountScopedRefreshGuard(
|
|
source: .liveSystem,
|
|
identity: .unresolved,
|
|
accountKey: nil)
|
|
let unresolvedRow = self.limitResetVisibleAccount(id: "unresolved-row", email: " ")
|
|
|
|
#expect(store.codexLimitResetOwnerKey(expectedGuard: guardValue, visibleAccounts: []) == nil)
|
|
#expect(store.codexLimitResetOwnerKey(
|
|
forVisibleAccount: unresolvedRow,
|
|
visibleAccounts: [unresolvedRow]) == nil)
|
|
}
|
|
|
|
private func makeLimitResetOwnerStore(suffix: String) -> UsageStore {
|
|
let support = CodexAccountScopedRefreshTests()
|
|
let settings = support.makeSettingsStore(suite: "CodexLimitResetOwnerKeyTests-\(suffix)")
|
|
return support.makeUsageStore(settings: settings)
|
|
}
|
|
|
|
private func limitResetVisibleAccount(
|
|
id: String,
|
|
email: String,
|
|
workspaceLabel: String? = nil,
|
|
workspaceAccountID: String? = nil,
|
|
authFingerprint: String? = nil) -> CodexVisibleAccount
|
|
{
|
|
CodexVisibleAccount(
|
|
id: id,
|
|
email: email,
|
|
workspaceLabel: workspaceLabel,
|
|
workspaceAccountID: workspaceAccountID,
|
|
authFingerprint: authFingerprint,
|
|
storedAccountID: nil,
|
|
selectionSource: .profileHome(path: "/tmp/\(id)"),
|
|
isActive: false,
|
|
isLive: false,
|
|
canReauthenticate: true,
|
|
canRemove: true)
|
|
}
|
|
|
|
private func expectOpaqueLimitResetOwnerKey(
|
|
_ key: CodexLimitResetOwnerKey,
|
|
excludes cleartextValues: [String],
|
|
sourceLocation: SourceLocation = #_sourceLocation)
|
|
{
|
|
#expect(
|
|
key.rawValue.range(of: #"^[0-9a-f]{64}$"#, options: .regularExpression) != nil,
|
|
sourceLocation: sourceLocation)
|
|
for cleartextValue in cleartextValues {
|
|
#expect(!key.rawValue.contains(cleartextValue), sourceLocation: sourceLocation)
|
|
}
|
|
}
|
|
}
|