133 lines
5.4 KiB
Swift
133 lines
5.4 KiB
Swift
import CodexBarCore
|
|
import Foundation
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
@Suite(.serialized)
|
|
struct CodexSystemAccountObserverTests {
|
|
@Test
|
|
func `observer reads ambient CODEX_HOME when present`() throws {
|
|
let home = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
defer { try? FileManager.default.removeItem(at: home) }
|
|
try Self.writeCodexAuthFile(
|
|
homeURL: home,
|
|
email: " LIVE@Example.com ",
|
|
plan: "pro",
|
|
accountId: "account-live")
|
|
|
|
let observer = DefaultCodexSystemAccountObserver()
|
|
let account = try observer.loadSystemAccount(environment: ["CODEX_HOME": home.path])
|
|
|
|
#expect(account?.email == "live@example.com")
|
|
#expect(account?.codexHomePath == home.path)
|
|
#expect(account?.identity == .providerAccount(id: "account-live"))
|
|
}
|
|
|
|
@Test
|
|
func `observer falls back to nil when ambient home has no readable email`() throws {
|
|
let home = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
defer { try? FileManager.default.removeItem(at: home) }
|
|
try FileManager.default.createDirectory(at: home, withIntermediateDirectories: true)
|
|
|
|
let observer = DefaultCodexSystemAccountObserver()
|
|
let account = try observer.loadSystemAccount(environment: ["CODEX_HOME": home.path])
|
|
|
|
#expect(account == nil)
|
|
}
|
|
|
|
@Test
|
|
func `observer records observation timestamp`() throws {
|
|
let home = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
defer { try? FileManager.default.removeItem(at: home) }
|
|
try Self.writeCodexAuthFile(homeURL: home, email: "user@example.com", plan: "team")
|
|
|
|
let before = Date()
|
|
let observer = DefaultCodexSystemAccountObserver()
|
|
let account = try observer.loadSystemAccount(environment: ["CODEX_HOME": home.path])
|
|
let observed = try #require(account)
|
|
|
|
#expect(observed.observedAt >= before)
|
|
}
|
|
|
|
@Test
|
|
func `observer preserves provider account identity from scoped auth`() throws {
|
|
let home = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
defer { try? FileManager.default.removeItem(at: home) }
|
|
try Self.writeCodexAuthFile(
|
|
homeURL: home,
|
|
email: "user@example.com",
|
|
plan: "team",
|
|
accountId: "account-live-123")
|
|
|
|
let observer = DefaultCodexSystemAccountObserver()
|
|
let account = try #require(try observer.loadSystemAccount(environment: ["CODEX_HOME": home.path]))
|
|
|
|
#expect(account.identity == .providerAccount(id: "account-live-123"))
|
|
}
|
|
|
|
@Test
|
|
func `observer uses cached workspace label for provider account`() throws {
|
|
let home = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
let cacheURL = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-openai-workspaces-\(UUID().uuidString).json")
|
|
defer {
|
|
try? FileManager.default.removeItem(at: home)
|
|
try? FileManager.default.removeItem(at: cacheURL)
|
|
}
|
|
try Self.writeCodexAuthFile(
|
|
homeURL: home,
|
|
email: "user@example.com",
|
|
plan: "team",
|
|
accountId: "account-live-123")
|
|
|
|
try CodexOpenAIWorkspaceIdentityCache.withFileURLOverrideForTesting(cacheURL) {
|
|
try CodexOpenAIWorkspaceIdentityCache().store(CodexOpenAIWorkspaceIdentity(
|
|
workspaceAccountID: "account-live-123",
|
|
workspaceLabel: "Team Alpha"))
|
|
|
|
let observer = DefaultCodexSystemAccountObserver()
|
|
let account = try #require(try observer.loadSystemAccount(environment: ["CODEX_HOME": home.path]))
|
|
|
|
#expect(account.workspaceAccountID == "account-live-123")
|
|
#expect(account.workspaceLabel == "Team Alpha")
|
|
}
|
|
}
|
|
|
|
private static func writeCodexAuthFile(
|
|
homeURL: URL,
|
|
email: String,
|
|
plan: String,
|
|
accountId: String? = nil) throws
|
|
{
|
|
try FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true)
|
|
var tokens: [String: Any] = [
|
|
"accessToken": "access-token",
|
|
"refreshToken": "refresh-token",
|
|
"idToken": Self.fakeJWT(email: email, plan: plan),
|
|
]
|
|
if let accountId {
|
|
tokens["accountId"] = accountId
|
|
}
|
|
let auth = ["tokens": tokens]
|
|
let data = try JSONSerialization.data(withJSONObject: auth)
|
|
try data.write(to: homeURL.appendingPathComponent("auth.json"))
|
|
}
|
|
|
|
private static func fakeJWT(email: String, plan: String) -> String {
|
|
let header = (try? JSONSerialization.data(withJSONObject: ["alg": "none"])) ?? Data()
|
|
let payload = (try? JSONSerialization.data(withJSONObject: [
|
|
"email": email,
|
|
"chatgpt_plan_type": plan,
|
|
])) ?? Data()
|
|
|
|
func base64URL(_ data: Data) -> String {
|
|
data.base64EncodedString()
|
|
.replacingOccurrences(of: "=", with: "")
|
|
.replacingOccurrences(of: "+", with: "-")
|
|
.replacingOccurrences(of: "/", with: "_")
|
|
}
|
|
|
|
return "\(base64URL(header)).\(base64URL(payload))."
|
|
}
|
|
}
|