import { CROSS_TAB_CHANNEL_NAME, CROSS_TAB_STORAGE_KEY, CROSS_TAB_ROLE_CHANNEL_NAME, CROSS_TAB_ROLE_STORAGE_KEY, CrossTabInvalidationMessage, CrossTabRoleChangeMessage, applyRoleChangeToUser, encodeInvalidationMessage, encodeRoleChangeMessage, isCrossTabInvalidationMessage, isCrossTabRoleChangeMessage, isStorageInvalidationEvent, isStorageRoleChangeEvent, } from '../../frontend/src/app/core/services/auth-session-events.pure'; describe('auth-session-events.pure', () => { describe('encodeInvalidationMessage', () => { it('produces a session-invalidated message with origin and timestamp', () => { const before = Date.now(); const msg = encodeInvalidationMessage('tab-A'); const after = Date.now(); expect(msg.type).toBe('session-invalidated'); expect(msg.origin).toBe('tab-A'); expect(msg.ts).toBeGreaterThanOrEqual(before); expect(msg.ts).toBeLessThanOrEqual(after); }); }); describe('isCrossTabInvalidationMessage', () => { it('accepts a properly shaped message', () => { const msg: CrossTabInvalidationMessage = { type: 'session-invalidated', origin: 'x', ts: 1, }; expect(isCrossTabInvalidationMessage(msg)).toBe(true); }); it('rejects non-objects', () => { expect(isCrossTabInvalidationMessage(null)).toBe(false); expect(isCrossTabInvalidationMessage(undefined)).toBe(false); expect(isCrossTabInvalidationMessage('string')).toBe(false); expect(isCrossTabInvalidationMessage(42)).toBe(false); }); it('rejects objects with wrong type or missing fields', () => { expect(isCrossTabInvalidationMessage({ type: 'other', origin: 'a', ts: 1 })).toBe(false); expect(isCrossTabInvalidationMessage({ type: 'session-invalidated', origin: 1, ts: 1 })).toBe( false, ); expect(isCrossTabInvalidationMessage({ type: 'session-invalidated', origin: 'a' })).toBe( false, ); expect( isCrossTabInvalidationMessage({ type: 'session-invalidated', origin: 'a', ts: 'now' }), ).toBe(false); }); }); describe('isStorageInvalidationEvent', () => { it('accepts a well-formed storage event for the namespaced key', () => { const ev = { key: CROSS_TAB_STORAGE_KEY, newValue: JSON.stringify(encodeInvalidationMessage('tab-X')), }; expect(isStorageInvalidationEvent(ev)).toBe(true); }); it('rejects events with a different key', () => { const ev = { key: 'other.key', newValue: JSON.stringify(encodeInvalidationMessage('tab-X')), }; expect(isStorageInvalidationEvent(ev)).toBe(false); }); it('rejects events with no newValue (cleared key)', () => { const ev = { key: CROSS_TAB_STORAGE_KEY, newValue: null }; expect(isStorageInvalidationEvent(ev)).toBe(false); }); it('rejects malformed JSON payloads', () => { const ev = { key: CROSS_TAB_STORAGE_KEY, newValue: 'not-json' }; expect(isStorageInvalidationEvent(ev)).toBe(false); }); it('rejects nullish events', () => { expect(isStorageInvalidationEvent(null)).toBe(false); expect(isStorageInvalidationEvent(undefined)).toBe(false); }); }); describe('constant exports', () => { it('exposes a stable channel name and storage key', () => { expect(CROSS_TAB_CHANNEL_NAME).toBe('hipctf.auth.v1'); expect(CROSS_TAB_STORAGE_KEY).toBe('hipctf.auth.invalidate.v1'); expect(CROSS_TAB_ROLE_CHANNEL_NAME).toBe('hipctf.auth.role.v1'); expect(CROSS_TAB_ROLE_STORAGE_KEY).toBe('hipctf.role.invalidate.v1'); }); }); describe('encodeRoleChangeMessage', () => { it('produces a role-changed message with userId and newRole', () => { const before = Date.now(); const msg = encodeRoleChangeMessage('tab-A', 'user-1', 'player'); const after = Date.now(); expect(msg.type).toBe('role-changed'); expect(msg.origin).toBe('tab-A'); expect(msg.userId).toBe('user-1'); expect(msg.newRole).toBe('player'); expect(msg.ts).toBeGreaterThanOrEqual(before); expect(msg.ts).toBeLessThanOrEqual(after); }); }); describe('isCrossTabRoleChangeMessage', () => { it('accepts a properly shaped message', () => { const msg: CrossTabRoleChangeMessage = { type: 'role-changed', origin: 'x', ts: 1, userId: 'u1', newRole: 'player', }; expect(isCrossTabRoleChangeMessage(msg)).toBe(true); }); it('rejects a session-invalidated message (does not double-fire)', () => { const msg: CrossTabInvalidationMessage = { type: 'session-invalidated', origin: 'x', ts: 1 }; expect(isCrossTabRoleChangeMessage(msg)).toBe(false); }); it('rejects objects with wrong/missing fields', () => { expect(isCrossTabRoleChangeMessage({ type: 'role-changed', origin: 'a', ts: 1, userId: 'u', newRole: 'super' })).toBe( false, ); expect(isCrossTabRoleChangeMessage({ type: 'role-changed', origin: 'a', ts: 1, userId: 1, newRole: 'admin' })).toBe( false, ); expect(isCrossTabRoleChangeMessage({ type: 'role-changed', origin: 'a', ts: 'now', userId: 'u', newRole: 'admin' })).toBe( false, ); }); it('rejects non-objects', () => { expect(isCrossTabRoleChangeMessage(null)).toBe(false); expect(isCrossTabRoleChangeMessage(undefined)).toBe(false); expect(isCrossTabRoleChangeMessage('string')).toBe(false); }); }); describe('isStorageRoleChangeEvent', () => { it('accepts a well-formed storage event for the role key', () => { const ev = { key: CROSS_TAB_ROLE_STORAGE_KEY, newValue: JSON.stringify(encodeRoleChangeMessage('tab-X', 'u1', 'player')), }; expect(isStorageRoleChangeEvent(ev)).toBe(true); }); it('rejects events with a different key', () => { const ev = { key: 'something.else', newValue: JSON.stringify(encodeRoleChangeMessage('tab-X', 'u1', 'player')), }; expect(isStorageRoleChangeEvent(ev)).toBe(false); }); it('rejects malformed JSON payloads', () => { const ev = { key: CROSS_TAB_ROLE_STORAGE_KEY, newValue: 'not-json' }; expect(isStorageRoleChangeEvent(ev)).toBe(false); }); it('rejects nullish events', () => { expect(isStorageRoleChangeEvent(null)).toBe(false); expect(isStorageRoleChangeEvent(undefined)).toBe(false); }); }); describe('applyRoleChangeToUser', () => { it('returns a new user with the updated role when they differ', () => { const u = { id: 'u1', role: 'admin' as const }; const out = applyRoleChangeToUser(u, 'player'); expect(out).toEqual({ id: 'u1', role: 'player' }); expect(out).not.toBe(u); }); it('returns the same user object (no-op) when role already matches', () => { const u = { id: 'u1', role: 'player' as const }; const out = applyRoleChangeToUser(u, 'player'); expect(out).toBe(u); }); it('returns the input untouched for null users', () => { expect(applyRoleChangeToUser(null, 'admin')).toBeNull(); }); }); });