Refresh Token Handling
Nucleic-Session: F3D68A19-D251-48C6-AFAA-9BDC68FB88BC Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import NucleicCore
|
||||
|
||||
/// Locks the newest-wins rule that keeps a sandboxed Codex login coherent with the host's
|
||||
/// `~/.codex/auth.json` (LOGIN SYNC). Only the I/O-free core is exercised — the real file copy
|
||||
/// touches the developer's home — so `CodexAuthFile` splits the decision (`shouldReplace`) and the
|
||||
/// parsing (`lastRefresh`/`isValidLogin`) out for exactly this.
|
||||
struct CodexAuthFileTests {
|
||||
/// An `auth.json` like the one `codex` writes, with the given `last_refresh` and refresh token.
|
||||
private static func login(lastRefresh: String, refresh: String = "ref", apiKey: String = "") -> String {
|
||||
"""
|
||||
{"OPENAI_API_KEY":"\(apiKey)","tokens":{"id_token":"id","access_token":"acc",\
|
||||
"refresh_token":"\(refresh)"},"last_refresh":"\(lastRefresh)"}
|
||||
"""
|
||||
}
|
||||
|
||||
// MARK: - Parsing
|
||||
|
||||
@Test func parsesLastRefreshWithAndWithoutFractionalSeconds() {
|
||||
// Codex emits fractional seconds; a re-login may not. Both parse, and the fractional instant
|
||||
// sorts strictly after the whole-second one at the same minute.
|
||||
let frac = Self.lastRefresh(Self.login(lastRefresh: "2026-07-09T10:56:59.068487Z"))
|
||||
let plain = Self.lastRefresh(Self.login(lastRefresh: "2026-07-09T10:56:59Z"))
|
||||
#expect(frac != nil && plain != nil)
|
||||
#expect(frac! > plain!)
|
||||
}
|
||||
|
||||
private static func lastRefresh(_ json: String) -> Double? { CodexAuthFile.lastRefresh(json) }
|
||||
|
||||
@Test func validatesLoginRequiresNonEmptyRefreshToken() {
|
||||
#expect(CodexAuthFile.isValidLogin(Self.login(lastRefresh: "2026-07-09T10:56:59Z")))
|
||||
// API-key-only auth.json (no tokens) has no refresh lineage to reconcile.
|
||||
#expect(!CodexAuthFile.isValidLogin(#"{"OPENAI_API_KEY":"sk-abc","tokens":{}}"#))
|
||||
#expect(!CodexAuthFile.isValidLogin(#"{"tokens":{"refresh_token":""}}"#))
|
||||
#expect(!CodexAuthFile.isValidLogin("not json"))
|
||||
#expect(CodexAuthFile.lastRefresh("not json") == nil)
|
||||
}
|
||||
|
||||
// MARK: - Newest-wins decision
|
||||
|
||||
@Test func replacesWhenCandidateIsStrictlyNewer() {
|
||||
let current = Self.login(lastRefresh: "2026-07-09T10:00:00Z", refresh: "old")
|
||||
let candidate = Self.login(lastRefresh: "2026-07-09T11:00:00Z", refresh: "rotated")
|
||||
#expect(CodexAuthFile.shouldReplace(candidate: candidate, current: current))
|
||||
}
|
||||
|
||||
@Test func skipsWhenCandidateIsOlderOrEqual() {
|
||||
let current = Self.login(lastRefresh: "2026-07-09T11:00:00Z")
|
||||
#expect(!CodexAuthFile.shouldReplace(
|
||||
candidate: Self.login(lastRefresh: "2026-07-09T10:00:00Z"), current: current))
|
||||
// Equal timestamp is not newer — re-seeding the same login must not thrash the file.
|
||||
#expect(!CodexAuthFile.shouldReplace(
|
||||
candidate: Self.login(lastRefresh: "2026-07-09T11:00:00Z"), current: current))
|
||||
}
|
||||
|
||||
@Test func skipsIdenticalCredential() {
|
||||
let same = Self.login(lastRefresh: "2026-07-09T11:00:00Z")
|
||||
#expect(!CodexAuthFile.shouldReplace(candidate: same, current: same))
|
||||
}
|
||||
|
||||
@Test func skipsInvalidOrUndatedCandidate() {
|
||||
let current = Self.login(lastRefresh: "2026-07-09T10:00:00Z")
|
||||
// A truncated/half-written file, or one missing last_refresh, must never be propagated even
|
||||
// though it might "look" newer.
|
||||
#expect(!CodexAuthFile.shouldReplace(candidate: #"{"tokens":{"refresh_token":"r"}}"#, current: current))
|
||||
#expect(!CodexAuthFile.shouldReplace(candidate: "not json", current: current))
|
||||
}
|
||||
|
||||
@Test func seedsWhenDestinationIsMissingOrUnparseable() {
|
||||
// Unlike the Claude Keychain (which never fabricates), the Codex path is a file copy used to
|
||||
// seed the container: a nil destination (not yet created) or an unparseable one is stale, so a
|
||||
// valid candidate wins. This is what pushes the host login into a fresh container home.
|
||||
let candidate = Self.login(lastRefresh: "2026-07-09T11:00:00Z")
|
||||
#expect(CodexAuthFile.shouldReplace(candidate: candidate, current: nil))
|
||||
#expect(CodexAuthFile.shouldReplace(candidate: candidate, current: "not json"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user