Skip to content

Commit c9a89d7

Browse files
committed
Harden shared-account regression coverage
1 parent c77b895 commit c9a89d7

File tree

4 files changed

+97
-1
lines changed

4 files changed

+97
-1
lines changed

lib/accounts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export class AccountManager {
194194
}
195195

196196
constructor(authFallback?: OAuthAuthDetails, stored?: AccountStorageV3 | null) {
197-
const fallbackAccountId = extractAccountId(authFallback?.access);
197+
const fallbackAccountId = extractAccountId(authFallback?.access)?.trim() || undefined;
198198
const fallbackAccountEmail = sanitizeEmail(extractAccountEmail(authFallback?.access));
199199

200200
if (stored && stored.accounts.length > 0) {

test/accounts.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,42 @@ describe("AccountManager", () => {
12291229
expect(account?.access).toBe(accessToken);
12301230
});
12311231

1232+
it("trims fallback accountId before matching and persisting it", () => {
1233+
const now = Date.now();
1234+
const payload = {
1235+
"https://api.openai.com/auth": {
1236+
chatgpt_account_id: " matching-account-id ",
1237+
},
1238+
};
1239+
const accessToken = `header.${Buffer.from(JSON.stringify(payload)).toString("base64")}.signature`;
1240+
1241+
const stored = {
1242+
version: 3 as const,
1243+
activeIndex: 0,
1244+
accounts: [
1245+
{
1246+
refreshToken: "stored-token",
1247+
accountId: "matching-account-id",
1248+
addedAt: now,
1249+
lastUsed: now,
1250+
},
1251+
],
1252+
};
1253+
1254+
const auth: OAuthAuthDetails = {
1255+
type: "oauth",
1256+
access: accessToken,
1257+
refresh: "new-refresh-token",
1258+
expires: now + 60_000,
1259+
};
1260+
1261+
const manager = new AccountManager(auth, stored);
1262+
expect(manager.getAccountCount()).toBe(1);
1263+
const account = manager.getCurrentAccount();
1264+
expect(account?.refreshToken).toBe("new-refresh-token");
1265+
expect(account?.accountId).toBe("matching-account-id");
1266+
});
1267+
12321268
it("ignores malformed stored rows when checking shared accountId uniqueness for fallback matching", () => {
12331269
const now = Date.now();
12341270
const payload = {

test/index.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,11 @@ describe("OpenAIOAuthPlugin persistAccountPool", () => {
20672067
expect(
20682068
mockStorage.accounts.map((account) => account.refreshToken),
20692069
).toEqual(["refresh-a", "refresh-b", "refresh-c"]);
2070+
expect(mockStorage.accounts.map((account) => account.accountId)).toEqual([
2071+
"shared-workspace",
2072+
"shared-workspace",
2073+
"shared-workspace",
2074+
]);
20702075
});
20712076

20722077
it("serializes concurrent manual logins through the storage transaction queue", async () => {

test/oc-chatgpt-import-adapter.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,61 @@ describe("oc-chatgpt import adapter", () => {
401401
);
402402
});
403403

404+
it("keeps reversed-order shared-account imports distinct when only one destination email exists", () => {
405+
const destination: AccountStorageV3 = {
406+
version: 3,
407+
activeIndex: 0,
408+
accounts: [
409+
{
410+
accountId: "shared-account",
411+
email: "alice@example.com",
412+
refreshToken: "dest-alice",
413+
addedAt: 10,
414+
lastUsed: 10,
415+
},
416+
],
417+
};
418+
419+
const source: AccountStorageV3 = {
420+
version: 3,
421+
activeIndex: 1,
422+
accounts: [
423+
{
424+
accountId: "shared-account",
425+
email: "bob@example.com",
426+
refreshToken: "source-bob",
427+
addedAt: 2,
428+
lastUsed: 2,
429+
},
430+
{
431+
accountId: "shared-account",
432+
email: "alice@example.com",
433+
refreshToken: "source-alice-older",
434+
addedAt: 1,
435+
lastUsed: 1,
436+
},
437+
],
438+
};
439+
440+
const preview = previewOcChatgptImportMerge({ source, destination });
441+
442+
expect(preview.toUpdate).toHaveLength(0);
443+
expect(preview.toAdd).toEqual([
444+
{
445+
accountId: "shared-account",
446+
email: "bob@example.com",
447+
refreshTokenLast4: "-bob",
448+
},
449+
]);
450+
expect(preview.merged.accounts).toHaveLength(2);
451+
expect(preview.merged.accounts.map((account) => account.email)).toEqual(
452+
expect.arrayContaining(["alice@example.com", "bob@example.com"]),
453+
);
454+
expect(preview.merged.accounts.map((account) => account.refreshToken)).toEqual(
455+
expect.arrayContaining(["dest-alice", "source-bob"]),
456+
);
457+
});
458+
404459
it("keeps newer destination metadata when a refresh-token fallback match is older than destination", () => {
405460
const destination: AccountStorageV3 = {
406461
version: 3,

0 commit comments

Comments
 (0)