import { createApp } from 'vue';
|
|
import { defineStore } from 'pinia';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { useAccessStore } from '../modules/access';
|
import { initStores, withSessionAuthLogin } from '../setup';
|
|
const TestApp = {};
|
|
const usePreferences = defineStore('test-preferences', {
|
state: () => ({ theme: 'light' }),
|
persist: true,
|
});
|
|
function memoryStorage(): Storage {
|
const data = new Map<string, string>();
|
return {
|
get length() {
|
return data.size;
|
},
|
clear: () => data.clear(),
|
getItem: (key) => data.get(key) ?? null,
|
key: (index) => [...data.keys()][index] ?? null,
|
removeItem: (key) => {
|
data.delete(key);
|
},
|
setItem: (key, value) => {
|
data.set(key, value);
|
},
|
};
|
}
|
|
beforeEach(() => {
|
vi.stubGlobal('localStorage', memoryStorage());
|
vi.stubGlobal('sessionStorage', memoryStorage());
|
});
|
|
afterEach(() => {
|
vi.unstubAllEnvs();
|
localStorage.clear();
|
sessionStorage.clear();
|
vi.unstubAllGlobals();
|
});
|
|
describe.each([true, false])('pinia session auth (development=%s)', (development) => {
|
it('hydrates real access store from session storage and preserves unrelated persistence', async () => {
|
vi.stubEnv('DEV', development);
|
vi.stubGlobal('BroadcastChannel', undefined);
|
const app = createApp(TestApp);
|
const pinia = await initStores(app, { namespace: 'test', sessionAuth: true });
|
const access = useAccessStore(pinia);
|
access.setAccessToken('only-session-token');
|
access.setRefreshToken('only-session-refresh');
|
access.$persist();
|
const preferences = usePreferences(pinia);
|
preferences.theme = 'dark';
|
preferences.$persist();
|
expect(localStorage.getItem('test-core-access')).toBeNull();
|
expect(localStorage.getItem('test-test-preferences')).toBeTruthy();
|
const persisted = Array.from({ length: localStorage.length }, (_, index) => localStorage.getItem(localStorage.key(index)!)).join('');
|
expect(persisted).not.toContain('only-session-token');
|
const saved = sessionStorage.getItem('test-core-access-session');
|
expect(saved).toBeTruthy();
|
if (!development) expect(saved).not.toContain('only-session-token');
|
|
const refreshed = await initStores(createApp(TestApp), { namespace: 'test', sessionAuth: true });
|
expect(useAccessStore(refreshed).accessToken).toBe('only-session-token');
|
expect(useAccessStore(refreshed).refreshToken).toBe('only-session-refresh');
|
expect(usePreferences(refreshed).theme).toBe('dark');
|
|
sessionStorage.clear();
|
const reopened = await initStores(createApp(TestApp), { namespace: 'test', sessionAuth: true });
|
expect(useAccessStore(reopened).accessToken).toBeNull();
|
expect(usePreferences(reopened).theme).toBe('dark');
|
});
|
|
it('preserves shared sub-application token scope', async () => {
|
vi.stubEnv('DEV', development);
|
vi.stubGlobal('BroadcastChannel', undefined);
|
const first = await initStores(createApp(TestApp), { namespace: 'test_first', isCommonToken: true, sessionAuth: true });
|
const access = useAccessStore(first);
|
access.setAccessToken('shared-app-token');
|
access.$persist();
|
const second = await initStores(createApp(TestApp), { namespace: 'test_second', isCommonToken: true, sessionAuth: true });
|
expect(useAccessStore(second).accessToken).toBe('shared-app-token');
|
});
|
|
it('does not change applications that do not opt in', async () => {
|
vi.stubEnv('DEV', development);
|
const pinia = await initStores(createApp(TestApp), { namespace: 'legacy' });
|
const access = useAccessStore(pinia);
|
access.setAccessToken('persistent-token');
|
access.$persist();
|
expect(localStorage.getItem('legacy-core-access')).toBeTruthy();
|
expect(sessionStorage.length).toBe(0);
|
});
|
});
|
|
it('serializes competing password submissions and reuses the completed session', async () => {
|
vi.stubGlobal('BroadcastChannel', undefined);
|
let queue = Promise.resolve();
|
vi.stubGlobal('navigator', {
|
locks: {
|
request: (_name: string, run: () => Promise<void>) => {
|
const result = queue.then(run);
|
queue = result.catch(() => {});
|
return result;
|
},
|
},
|
});
|
const pinia = await initStores(createApp(TestApp), { namespace: 'mutex', sessionAuth: true });
|
const access = useAccessStore(pinia);
|
const login = vi.fn(async (token: null | string) => {
|
if (token) return 'reused';
|
access.setAccessToken('one-login');
|
access.$persist();
|
return 'created';
|
});
|
expect(await Promise.all([withSessionAuthLogin(login), withSessionAuthLogin(login)])).toEqual(['created', 'reused']);
|
expect(login.mock.calls.map(([token]) => token)).toEqual([null, 'one-login']);
|
});
|