104 lines
2.7 KiB
TypeScript
104 lines
2.7 KiB
TypeScript
import { tokenStorage } from '../token';
|
|
import { logger } from '../../services/logging';
|
|
|
|
describe('tokenStorage', () => {
|
|
const STORAGE_KEY = 'meds_auth_tokens';
|
|
const originalLocalStorageDescriptor = Object.getOwnPropertyDescriptor(
|
|
window,
|
|
'localStorage'
|
|
);
|
|
|
|
beforeEach(() => {
|
|
jest.restoreAllMocks();
|
|
tokenStorage.clear();
|
|
window.localStorage.clear();
|
|
});
|
|
|
|
afterAll(() => {
|
|
if (originalLocalStorageDescriptor) {
|
|
Object.defineProperty(
|
|
window,
|
|
'localStorage',
|
|
originalLocalStorageDescriptor
|
|
);
|
|
}
|
|
});
|
|
|
|
it('persists tokens with a single storage key', () => {
|
|
const tokens = { accessToken: 'access-123', refreshToken: 'refresh-789' };
|
|
|
|
tokenStorage.save(tokens);
|
|
|
|
expect(window.localStorage.getItem(STORAGE_KEY)).toEqual(
|
|
JSON.stringify({
|
|
accessToken: 'access-123',
|
|
refreshToken: 'refresh-789',
|
|
})
|
|
);
|
|
expect(tokenStorage.getAccessToken()).toBe('access-123');
|
|
});
|
|
|
|
it('clears tokens from storage and cache', () => {
|
|
tokenStorage.save({
|
|
accessToken: 'access-123',
|
|
refreshToken: 'refresh-789',
|
|
});
|
|
|
|
tokenStorage.clear();
|
|
|
|
expect(window.localStorage.getItem(STORAGE_KEY)).toBeNull();
|
|
expect(tokenStorage.getTokens()).toBeNull();
|
|
expect(tokenStorage.getAccessToken()).toBeNull();
|
|
});
|
|
|
|
it('handles corrupted storage values by clearing and logging', () => {
|
|
window.localStorage.setItem(STORAGE_KEY, 'not json');
|
|
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
|
|
|
|
expect(tokenStorage.getTokens()).toBeNull();
|
|
|
|
expect(window.localStorage.getItem(STORAGE_KEY)).toBeNull();
|
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
'Failed to parse stored tokens, clearing cache',
|
|
'AUTH_TOKENS',
|
|
expect.any(Error)
|
|
);
|
|
});
|
|
|
|
it('falls back to in-memory storage when localStorage is unavailable', () => {
|
|
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
|
|
const error = new Error('blocked');
|
|
|
|
Object.defineProperty(window, 'localStorage', {
|
|
configurable: true,
|
|
get: () => {
|
|
throw error;
|
|
},
|
|
});
|
|
|
|
tokenStorage.clear();
|
|
tokenStorage.save({ accessToken: 'access-only' });
|
|
|
|
expect(tokenStorage.getAccessToken()).toBe('access-only');
|
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
'Token storage fallback to memory',
|
|
'AUTH_TOKENS',
|
|
error
|
|
);
|
|
|
|
if (originalLocalStorageDescriptor) {
|
|
Object.defineProperty(
|
|
window,
|
|
'localStorage',
|
|
originalLocalStorageDescriptor
|
|
);
|
|
}
|
|
});
|
|
|
|
it('throws when attempting to save without an access token', () => {
|
|
expect(() => tokenStorage.save({ accessToken: '' })).toThrow(
|
|
'Token payload must include an access token'
|
|
);
|
|
});
|
|
});
|