Skip to content

Commit 0c8cc4d

Browse files
committed
Cover idle getter token adoption and same-port DCR rotation
Idle sibling tokens must still land on a session that already has matching live DCR, without going through apply. Same-port saveClient must replace this session's client even after a sibling wrote a different port.
1 parent e6cfdc0 commit 0c8cc4d

1 file changed

Lines changed: 72 additions & 0 deletions

File tree

src/mcp/oauth-provider.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,78 @@ describe("createOAuthProvider", () => {
407407
]);
408408
});
409409

410+
test("idle tokens getter adopts a sibling's completed auth without rewriting matching DCR", async () => {
411+
const home = await tempHome();
412+
const a = await createOAuthProvider({
413+
serverName: "linear",
414+
serverURL: linear.serverURL,
415+
redirectUrl: "http://127.0.0.1:62000/callback",
416+
onAuthURL: () => undefined,
417+
home,
418+
});
419+
await saveClient(a, clientInfo(62000));
420+
await a.saveTokens({
421+
access_token: "tok-a",
422+
token_type: "bearer",
423+
expires_in: 3600,
424+
refresh_token: "ref-a",
425+
});
426+
427+
const b = await createOAuthProvider({
428+
serverName: "linear",
429+
serverURL: linear.serverURL,
430+
redirectUrl: "http://127.0.0.1:60435/callback",
431+
onAuthURL: () => undefined,
432+
home,
433+
});
434+
await saveClient(b, clientInfo(60435));
435+
await b.saveTokens({
436+
access_token: "tok-b",
437+
token_type: "bearer",
438+
expires_in: 3600,
439+
refresh_token: "ref-b",
440+
});
441+
442+
expect((await syncValue(a.tokens()))?.access_token).toBe("tok-b");
443+
const disk = await loadAuthState(linear, home);
444+
expect(disk.tokens?.access_token).toBe("tok-b");
445+
expect(disk.clientInformation?.client_id).toBe("client-on-60435");
446+
expect(disk.clientInformation?.redirect_uris).toEqual(["http://127.0.0.1:60435/callback"]);
447+
});
448+
449+
test("same-port DCR rotation after a different-port sibling saveClient keeps the new client", async () => {
450+
const home = await tempHome();
451+
const v1 = { ...clientInfo(62000), client_id: "client-on-62000-v1" };
452+
const v2 = { ...clientInfo(62000), client_id: "client-on-62000-v2" };
453+
const a = await createOAuthProvider({
454+
serverName: "linear",
455+
serverURL: linear.serverURL,
456+
redirectUrl: "http://127.0.0.1:62000/callback",
457+
onAuthURL: () => undefined,
458+
home,
459+
});
460+
await saveClient(a, v1);
461+
462+
const b = await createOAuthProvider({
463+
serverName: "linear",
464+
serverURL: linear.serverURL,
465+
redirectUrl: "http://127.0.0.1:60435/callback",
466+
onAuthURL: () => undefined,
467+
home,
468+
});
469+
await saveClient(b, clientInfo(60435));
470+
await saveClient(a, v2);
471+
472+
const info = await syncValue(a.clientInformation());
473+
expect(info?.client_id).toBe("client-on-62000-v2");
474+
expect(info && "redirect_uris" in info ? info.redirect_uris : undefined).toEqual([
475+
"http://127.0.0.1:62000/callback",
476+
]);
477+
const disk = await loadAuthState(linear, home);
478+
expect(disk.clientInformation?.client_id).toBe("client-on-62000-v2");
479+
expect(disk.clientInformation?.redirect_uris).toEqual(["http://127.0.0.1:62000/callback"]);
480+
});
481+
410482
test("sync getters fall back to the in-memory mirror when the auth file disappears", async () => {
411483
const home = await tempHome();
412484
const provider = await createOAuthProvider({

0 commit comments

Comments
 (0)