118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
import {
|
|
STORAGE_KEY,
|
|
readStoredSession,
|
|
writeStoredSession,
|
|
clearStoredSession,
|
|
StoredSession,
|
|
} from '../../frontend/src/app/core/services/auth.session-storage';
|
|
import { of, throwError } from 'rxjs';
|
|
|
|
function makeStorage(initial: Record<string, string> = {}): {
|
|
store: Record<string, string>;
|
|
getItem: jest.Mock;
|
|
setItem: jest.Mock;
|
|
removeItem: jest.Mock;
|
|
} {
|
|
const store = { ...initial };
|
|
return {
|
|
store,
|
|
getItem: jest.fn((k: string) => (k in store ? store[k] : null)),
|
|
setItem: jest.fn((k: string, v: string) => {
|
|
store[k] = v;
|
|
}),
|
|
removeItem: jest.fn((k: string) => {
|
|
delete store[k];
|
|
}),
|
|
};
|
|
}
|
|
|
|
const adminSession: StoredSession = {
|
|
token: 'tok-1',
|
|
user: { id: 'u1', username: 'alice', role: 'admin' },
|
|
};
|
|
|
|
describe('auth.session-storage helpers', () => {
|
|
it('readStoredSession returns null when empty', () => {
|
|
const s = makeStorage();
|
|
expect(readStoredSession(s as any)).toBeNull();
|
|
});
|
|
|
|
it('writeStoredSession + readStoredSession round-trip', () => {
|
|
const s = makeStorage();
|
|
writeStoredSession(s as any, adminSession);
|
|
expect(s.setItem).toHaveBeenCalledWith(STORAGE_KEY, JSON.stringify(adminSession));
|
|
expect(readStoredSession(s as any)).toEqual(adminSession);
|
|
});
|
|
|
|
it('clearStoredSession removes the key', () => {
|
|
const s = makeStorage({ [STORAGE_KEY]: JSON.stringify(adminSession) });
|
|
clearStoredSession(s as any);
|
|
expect(s.removeItem).toHaveBeenCalledWith(STORAGE_KEY);
|
|
expect(readStoredSession(s as any)).toBeNull();
|
|
});
|
|
|
|
it('readStoredSession returns null for malformed JSON', () => {
|
|
const s = makeStorage({ [STORAGE_KEY]: 'not-json' });
|
|
expect(readStoredSession(s as any)).toBeNull();
|
|
});
|
|
|
|
it('readStoredSession returns null for shape mismatch', () => {
|
|
const s = makeStorage({ [STORAGE_KEY]: JSON.stringify({ foo: 'bar' }) });
|
|
expect(readStoredSession(s as any)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('restoreSession refresh flow (pure)', () => {
|
|
it('successful refresh updates state and persists new token', async () => {
|
|
const stored = makeStorage({ [STORAGE_KEY]: JSON.stringify(adminSession) });
|
|
const restored = readStoredSession(stored as any);
|
|
expect(restored).toEqual(adminSession);
|
|
|
|
const httpPost = jest.fn();
|
|
|
|
const response = await new Promise<{ accessToken: string; user: any }>((resolve, reject) => {
|
|
const obs = of({ accessToken: 'fresh-tok', expiresIn: 900, user: adminSession.user });
|
|
httpPost.mockReturnValueOnce(obs);
|
|
(httpPost as any)('/api/v1/auth/refresh', {}, { withCredentials: true }).subscribe({
|
|
next: (r: unknown) => resolve(r as any),
|
|
error: (e: unknown) => reject(e),
|
|
});
|
|
});
|
|
|
|
expect(httpPost).toHaveBeenCalledWith(
|
|
'/api/v1/auth/refresh',
|
|
{},
|
|
{ withCredentials: true },
|
|
);
|
|
expect(response.accessToken).toBe('fresh-tok');
|
|
|
|
writeStoredSession(stored as any, {
|
|
token: response.accessToken,
|
|
user: response.user,
|
|
});
|
|
expect(stored.store[STORAGE_KEY]).toContain('fresh-tok');
|
|
});
|
|
|
|
it('failed refresh triggers clearStoredSession', async () => {
|
|
const stored = makeStorage({ [STORAGE_KEY]: JSON.stringify(adminSession) });
|
|
const httpPost = jest.fn();
|
|
|
|
let errored = false;
|
|
try {
|
|
await new Promise((resolve, reject) => {
|
|
httpPost.mockReturnValueOnce(throwError(() => ({ status: 401 })));
|
|
(httpPost as any)('/api/v1/auth/refresh', {}, { withCredentials: true }).subscribe({
|
|
next: () => resolve(undefined),
|
|
error: (e: unknown) => reject(e),
|
|
});
|
|
});
|
|
} catch {
|
|
errored = true;
|
|
}
|
|
expect(errored).toBe(true);
|
|
|
|
clearStoredSession(stored as any);
|
|
expect(stored.removeItem).toHaveBeenCalledWith(STORAGE_KEY);
|
|
expect(readStoredSession(stored as any)).toBeNull();
|
|
});
|
|
}); |