Skip to content

Commit 9ad58dd

Browse files
committed
test(cli): cover oauth auth epoch continuity
1 parent 1ff461f commit 9ad58dd

4 files changed

Lines changed: 117 additions & 7 deletions

File tree

src/agents/cli-auth-epoch.test.ts

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,50 @@ describe("resolveCliAuthEpoch", () => {
3030
).resolves.toBeUndefined();
3131
});
3232

33-
it("changes when claude cli credentials change", async () => {
33+
it("keeps claude cli oauth epochs stable across access-token refreshes", async () => {
3434
let access = "access-a";
35+
let expires = 1;
3536
setCliAuthEpochTestDeps({
3637
readClaudeCliCredentialsCached: () => ({
3738
type: "oauth",
3839
provider: "anthropic",
3940
access,
4041
refresh: "refresh",
41-
expires: 1,
42+
expires,
4243
}),
4344
});
4445

4546
const first = await resolveCliAuthEpoch({ provider: "claude-cli" });
4647
access = "access-b";
48+
expires = 2;
49+
const second = await resolveCliAuthEpoch({ provider: "claude-cli" });
50+
51+
expect(first).toBeDefined();
52+
expect(second).toBe(first);
53+
});
54+
55+
it("changes claude cli oauth epochs when the refresh token changes", async () => {
56+
let refresh = "refresh-a";
57+
setCliAuthEpochTestDeps({
58+
readClaudeCliCredentialsCached: () => ({
59+
type: "oauth",
60+
provider: "anthropic",
61+
access: "access",
62+
refresh,
63+
expires: 1,
64+
}),
65+
});
66+
67+
const first = await resolveCliAuthEpoch({ provider: "claude-cli" });
68+
refresh = "refresh-b";
4769
const second = await resolveCliAuthEpoch({ provider: "claude-cli" });
4870

4971
expect(first).toBeDefined();
5072
expect(second).toBeDefined();
5173
expect(second).not.toBe(first);
5274
});
5375

54-
it("changes when auth profile credentials change", async () => {
76+
it("keeps oauth auth-profile epochs stable across access-token refreshes", async () => {
5577
let store: AuthProfileStore = {
5678
version: 1,
5779
profiles: {
@@ -80,6 +102,48 @@ describe("resolveCliAuthEpoch", () => {
80102
provider: "anthropic",
81103
access: "access-b",
82104
refresh: "refresh",
105+
expires: 2,
106+
},
107+
},
108+
};
109+
const second = await resolveCliAuthEpoch({
110+
provider: "google-gemini-cli",
111+
authProfileId: "anthropic:work",
112+
});
113+
114+
expect(first).toBeDefined();
115+
expect(second).toBe(first);
116+
});
117+
118+
it("changes oauth auth-profile epochs when the refresh token changes", async () => {
119+
let store: AuthProfileStore = {
120+
version: 1,
121+
profiles: {
122+
"anthropic:work": {
123+
type: "oauth",
124+
provider: "anthropic",
125+
access: "access",
126+
refresh: "refresh-a",
127+
expires: 1,
128+
},
129+
},
130+
};
131+
setCliAuthEpochTestDeps({
132+
loadAuthProfileStoreForRuntime: () => store,
133+
});
134+
135+
const first = await resolveCliAuthEpoch({
136+
provider: "google-gemini-cli",
137+
authProfileId: "anthropic:work",
138+
});
139+
store = {
140+
version: 1,
141+
profiles: {
142+
"anthropic:work": {
143+
type: "oauth",
144+
provider: "anthropic",
145+
access: "access",
146+
refresh: "refresh-b",
83147
expires: 1,
84148
},
85149
},
@@ -96,13 +160,14 @@ describe("resolveCliAuthEpoch", () => {
96160

97161
it("mixes local codex and auth-profile state", async () => {
98162
let access = "local-access-a";
163+
let localRefresh = "local-refresh-a";
99164
let refresh = "profile-refresh-a";
100165
setCliAuthEpochTestDeps({
101166
readCodexCliCredentialsCached: () => ({
102167
type: "oauth",
103168
provider: "openai-codex",
104169
access,
105-
refresh: "local-refresh",
170+
refresh: localRefresh,
106171
expires: 1,
107172
accountId: "acct-1",
108173
}),
@@ -129,17 +194,23 @@ describe("resolveCliAuthEpoch", () => {
129194
provider: "codex-cli",
130195
authProfileId: "openai:work",
131196
});
132-
refresh = "profile-refresh-b";
197+
localRefresh = "local-refresh-b";
133198
const third = await resolveCliAuthEpoch({
134199
provider: "codex-cli",
135200
authProfileId: "openai:work",
136201
});
202+
refresh = "profile-refresh-b";
203+
const fourth = await resolveCliAuthEpoch({
204+
provider: "codex-cli",
205+
authProfileId: "openai:work",
206+
});
137207

138208
expect(first).toBeDefined();
139-
expect(second).toBeDefined();
140209
expect(third).toBeDefined();
141-
expect(second).not.toBe(first);
210+
expect(fourth).toBeDefined();
211+
expect(second).toBe(first);
142212
expect(third).not.toBe(second);
213+
expect(fourth).not.toBe(third);
143214
});
144215

145216
it("can ignore local codex state when the backend is profile-owned", async () => {

src/agents/cli-runner.reliability.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function buildPreparedContext(params?: {
5555
systemPrompt: "You are a helpful assistant.",
5656
systemPromptReport: {} as PreparedCliRunContext["systemPromptReport"],
5757
bootstrapPromptWarningLines: [],
58+
authEpochVersion: 2,
5859
};
5960
}
6061

src/agents/cli-runner.spawn.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ function buildPreparedCliRunContext(params: {
111111
systemPrompt: "You are a helpful assistant.",
112112
systemPromptReport: {} as PreparedCliRunContext["systemPromptReport"],
113113
bootstrapPromptWarningLines: [],
114+
authEpochVersion: 2,
114115
};
115116
}
116117

@@ -170,6 +171,7 @@ describe("runCliAgent spawn path", () => {
170171
systemPrompt: "You are a helpful assistant.",
171172
systemPromptReport: {} as PreparedCliRunContext["systemPromptReport"],
172173
bootstrapPromptWarningLines: [],
174+
authEpochVersion: 2,
173175
};
174176
await executePreparedCliRun(context);
175177

src/agents/cli-session.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe("cli-session helpers", () => {
2020
sessionId: "cli-session-1",
2121
authProfileId: "anthropic:work",
2222
authEpoch: "auth-epoch",
23+
authEpochVersion: 2,
2324
extraSystemPromptHash: "prompt-hash",
2425
mcpConfigHash: "mcp-hash",
2526
mcpResumeHash: "mcp-resume-hash",
@@ -31,6 +32,7 @@ describe("cli-session helpers", () => {
3132
sessionId: "cli-session-1",
3233
authProfileId: "anthropic:work",
3334
authEpoch: "auth-epoch",
35+
authEpochVersion: 2,
3436
extraSystemPromptHash: "prompt-hash",
3537
mcpConfigHash: "mcp-hash",
3638
mcpResumeHash: "mcp-resume-hash",
@@ -84,6 +86,7 @@ describe("cli-session helpers", () => {
8486
sessionId: "cli-session-1",
8587
authProfileId: "anthropic:work",
8688
authEpoch: "auth-epoch-a",
89+
authEpochVersion: 2,
8790
extraSystemPromptHash: "prompt-a",
8891
mcpConfigHash: "mcp-a",
8992
};
@@ -93,6 +96,7 @@ describe("cli-session helpers", () => {
9396
binding,
9497
authProfileId: "anthropic:personal",
9598
authEpoch: "auth-epoch-a",
99+
authEpochVersion: 2,
96100
extraSystemPromptHash: "prompt-a",
97101
mcpConfigHash: "mcp-a",
98102
}),
@@ -102,6 +106,7 @@ describe("cli-session helpers", () => {
102106
binding,
103107
authProfileId: "anthropic:work",
104108
authEpoch: "auth-epoch-b",
109+
authEpochVersion: 2,
105110
extraSystemPromptHash: "prompt-a",
106111
mcpConfigHash: "mcp-a",
107112
}),
@@ -111,6 +116,7 @@ describe("cli-session helpers", () => {
111116
binding,
112117
authProfileId: "anthropic:work",
113118
authEpoch: "auth-epoch-a",
119+
authEpochVersion: 2,
114120
extraSystemPromptHash: "prompt-b",
115121
mcpConfigHash: "mcp-a",
116122
}),
@@ -120,17 +126,40 @@ describe("cli-session helpers", () => {
120126
binding,
121127
authProfileId: "anthropic:work",
122128
authEpoch: "auth-epoch-a",
129+
authEpochVersion: 2,
123130
extraSystemPromptHash: "prompt-a",
124131
mcpConfigHash: "mcp-b",
125132
}),
126133
).toEqual({ invalidatedReason: "mcp" });
127134
});
128135

136+
it("accepts unversioned auth epochs for binding upgrades", () => {
137+
const binding = {
138+
sessionId: "cli-session-1",
139+
authProfileId: "anthropic:work",
140+
authEpoch: "previous-auth-epoch",
141+
extraSystemPromptHash: "prompt-a",
142+
mcpConfigHash: "mcp-a",
143+
};
144+
145+
expect(
146+
resolveCliSessionReuse({
147+
binding,
148+
authProfileId: "anthropic:work",
149+
authEpoch: "auth-epoch-a",
150+
authEpochVersion: 2,
151+
extraSystemPromptHash: "prompt-a",
152+
mcpConfigHash: "mcp-a",
153+
}),
154+
).toEqual({ sessionId: "cli-session-1" });
155+
});
156+
129157
it("does not treat model changes as a session mismatch", () => {
130158
const binding = {
131159
sessionId: "cli-session-1",
132160
authProfileId: "anthropic:work",
133161
authEpoch: "auth-epoch-a",
162+
authEpochVersion: 2,
134163
extraSystemPromptHash: "prompt-a",
135164
mcpConfigHash: "mcp-a",
136165
};
@@ -140,6 +169,7 @@ describe("cli-session helpers", () => {
140169
binding,
141170
authProfileId: "anthropic:work",
142171
authEpoch: "auth-epoch-a",
172+
authEpochVersion: 2,
143173
extraSystemPromptHash: "prompt-a",
144174
mcpConfigHash: "mcp-a",
145175
}),
@@ -151,6 +181,7 @@ describe("cli-session helpers", () => {
151181
sessionId: "cli-session-1",
152182
authProfileId: "anthropic:work",
153183
authEpoch: "auth-epoch-a",
184+
authEpochVersion: 2,
154185
extraSystemPromptHash: "prompt-a",
155186
mcpConfigHash: "mcp-config-a",
156187
mcpResumeHash: "mcp-resume-a",
@@ -161,6 +192,7 @@ describe("cli-session helpers", () => {
161192
binding,
162193
authProfileId: "anthropic:work",
163194
authEpoch: "auth-epoch-a",
195+
authEpochVersion: 2,
164196
extraSystemPromptHash: "prompt-a",
165197
mcpConfigHash: "mcp-config-b",
166198
mcpResumeHash: "mcp-resume-a",
@@ -171,6 +203,7 @@ describe("cli-session helpers", () => {
171203
binding,
172204
authProfileId: "anthropic:work",
173205
authEpoch: "auth-epoch-a",
206+
authEpochVersion: 2,
174207
extraSystemPromptHash: "prompt-a",
175208
mcpConfigHash: "mcp-config-a",
176209
mcpResumeHash: "mcp-resume-b",
@@ -183,6 +216,7 @@ describe("cli-session helpers", () => {
183216
sessionId: "cli-session-1",
184217
authProfileId: "anthropic:work",
185218
authEpoch: "auth-epoch-a",
219+
authEpochVersion: 2,
186220
extraSystemPromptHash: "prompt-a",
187221
mcpConfigHash: "mcp-config-a",
188222
};
@@ -192,6 +226,7 @@ describe("cli-session helpers", () => {
192226
binding,
193227
authProfileId: "anthropic:work",
194228
authEpoch: "auth-epoch-a",
229+
authEpochVersion: 2,
195230
extraSystemPromptHash: "prompt-a",
196231
mcpConfigHash: "mcp-config-a",
197232
mcpResumeHash: "mcp-resume-a",
@@ -202,6 +237,7 @@ describe("cli-session helpers", () => {
202237
binding,
203238
authProfileId: "anthropic:work",
204239
authEpoch: "auth-epoch-a",
240+
authEpochVersion: 2,
205241
extraSystemPromptHash: "prompt-a",
206242
mcpConfigHash: "mcp-config-b",
207243
mcpResumeHash: "mcp-resume-a",

0 commit comments

Comments
 (0)