diff --git a/apps/extension/src/storage/persistence.ts b/apps/extension/src/storage/persistence.ts index 8973421..2ae38ff 100644 --- a/apps/extension/src/storage/persistence.ts +++ b/apps/extension/src/storage/persistence.ts @@ -12,21 +12,21 @@ export const readPersistedState = async (): Promise<{ typeof tokenPayload[TOKEN_KEY] === "string" ? (tokenPayload[TOKEN_KEY] as string) : null; - return { local: state, sessionToken }; + const localToken = typeof state?.githubToken === "string" ? state.githubToken : null; + return { local: state, sessionToken: sessionToken ?? localToken }; }; export const writePersistedState = async (state: StoredState): Promise => { - await chrome.storage.session.set({ - [TOKEN_KEY]: state.githubToken, - }); - - const persistableState: StoredState = { - ...state, - githubToken: null, - }; + if (state.githubToken) { + await chrome.storage.session.set({ + [TOKEN_KEY]: state.githubToken, + }); + } else { + await chrome.storage.session.remove(TOKEN_KEY); + } await chrome.storage.local.set({ - [STORAGE_KEY]: persistableState, + [STORAGE_KEY]: state, }); }; diff --git a/apps/extension/tests/unit/storage.test.ts b/apps/extension/tests/unit/storage.test.ts new file mode 100644 index 0000000..7e644a0 --- /dev/null +++ b/apps/extension/tests/unit/storage.test.ts @@ -0,0 +1,94 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { clearAuthState, getStoredState, saveStoredState, type StoredState } from "@/storage"; +import { STORAGE_KEY, TOKEN_KEY } from "@/storage/authSession"; + +type MemoryStorageArea = { + data: Record; + get: (key: string) => Promise>; + remove: (key: string) => Promise; + set: (items: Record) => Promise; +}; + +const createMemoryStorageArea = (): MemoryStorageArea => { + const data: Record = {}; + return { + data, + get: async (key: string) => ({ [key]: data[key] }), + remove: async (key: string) => { + delete data[key]; + }, + set: async (items: Record) => { + Object.assign(data, items); + }, + }; +}; + +const buildAuthenticatedState = (): StoredState => ({ + githubToken: "persisted-token", + auth: { + isAuthenticated: true, + username: "oliviermaghe", + method: "web", + }, + settings: { + threshold: 95, + selectedRepoFullName: "MarcoAntolini/CSSHub", + selectedBranch: "main", + systemNotificationsEnabled: true, + repositoryReadmeMode: "managed-section", + }, + lastSubmission: null, + lastSubmissionAccepted: null, + lastIngestion: null, + recentEvents: [], + lastSubmissionFingerprint: null, +}); + +describe("extension storage auth persistence", () => { + let local: MemoryStorageArea; + let session: MemoryStorageArea; + + beforeEach(() => { + local = createMemoryStorageArea(); + session = createMemoryStorageArea(); + vi.stubGlobal("chrome", { + storage: { + local, + session, + }, + }); + }); + + it("keeps GitHub auth after browser session storage is cleared", async () => { + await saveStoredState(buildAuthenticatedState()); + for (const key of Object.keys(session.data)) { + delete session.data[key]; + } + + const state = await getStoredState(); + const persisted = local.data[STORAGE_KEY] as StoredState; + + expect(persisted.githubToken).toBe("persisted-token"); + expect(state.githubToken).toBe("persisted-token"); + expect(state.auth).toEqual({ + isAuthenticated: true, + username: "oliviermaghe", + method: "web", + }); + }); + + it("clears the durable GitHub token on explicit disconnect", async () => { + await saveStoredState(buildAuthenticatedState()); + + await clearAuthState(); + + const persisted = local.data[STORAGE_KEY] as StoredState; + expect(persisted.githubToken).toBeNull(); + expect(session.data[TOKEN_KEY]).toBeUndefined(); + expect(persisted.auth).toEqual({ + isAuthenticated: false, + username: null, + method: null, + }); + }); +});