import { OAuthToken } from '../../types' import { Session, UNAUTHENTICATED_SESSION_ID } from '../session' import { SessionManager } from '../session_manager' const createToken = (accessToken: string): OAuthToken => ({ access_token: accessToken, refresh_token: 'refresh_token', token_type: 'Bearer', created_at: Date.now(), expires_in: 3600, scope: 'read write', }) describe('SessionManager', () => { describe('hydrate', () => { it('should create a SessionManager from a JSON string', () => { const managerString = JSON.stringify({ sessions: { '12345678': JSON.stringify({ env: 'production', token: createToken('token-12345678') }), }, currentSessionId: '12345678', }) const manager = SessionManager.hydrate(managerString) expect(manager.currentSessionId).toBe('12345678') expect(manager.sessions).toHaveProperty('12345678') expect(manager.currentSession.isAuthenticated).toBe(true) }) it('should return a default SessionManager when given invalid JSON', () => { const manager = SessionManager.hydrate('invalid json') expect(manager.currentSessionId).toBe(UNAUTHENTICATED_SESSION_ID) expect(manager.sessions).toEqual({}) expect(manager.currentSession.isAuthenticated).toBe(false) }) it('should return a default SessionManager when given empty string', () => { const manager = SessionManager.hydrate('') expect(manager.currentSessionId).toBe(UNAUTHENTICATED_SESSION_ID) expect(manager.sessions).toEqual({}) }) }) describe('currentSession', () => { it('should return the current session when it exists', () => { const session = new Session({ token: createToken('token-12345678') }) const managerString = JSON.stringify({ sessions: { '12345678': session.toString() }, currentSessionId: '12345678', }) const manager = SessionManager.hydrate(managerString) const currentSession = manager.currentSession expect(currentSession.isAuthenticated).toBe(true) expect(currentSession.token?.access_token).toBe('token-12345678') }) it('should return an unauthenticated session when currentSessionId does not exist', () => { const managerString = JSON.stringify({ sessions: {}, currentSessionId: 'nonexistent', }) const manager = SessionManager.hydrate(managerString) const currentSession = manager.currentSession expect(currentSession.isAuthenticated).toBe(false) expect(currentSession.id).toBe(UNAUTHENTICATED_SESSION_ID) }) it('should return an unauthenticated session when currentSessionId is unauthenticated', () => { const manager = SessionManager.hydrate( JSON.stringify({ sessions: {}, currentSessionId: UNAUTHENTICATED_SESSION_ID }) ) const currentSession = manager.currentSession expect(currentSession.isAuthenticated).toBe(false) expect(currentSession.id).toBe(UNAUTHENTICATED_SESSION_ID) }) }) describe('setEnv', () => { it('should update the environment of the current session', () => { const session = new Session({ env: 'production', token: createToken('token-12345678') }) const managerString = JSON.stringify({ sessions: { '12345678': session.toString() }, currentSessionId: '12345678', }) const manager = SessionManager.hydrate(managerString) manager.setEnv('staging') expect(manager.currentSession.env).toBe('staging') expect(manager.currentSession.token?.access_token).toBe('token-12345678') }) it('should create and store a session when currentSessionId is unauthenticated', () => { const manager = SessionManager.hydrate( JSON.stringify({ sessions: {}, currentSessionId: UNAUTHENTICATED_SESSION_ID }) ) manager.setEnv('development') expect(manager.currentSession.env).toBe('development') expect(manager.currentSession.id).toBe(UNAUTHENTICATED_SESSION_ID) expect(manager.sessions).toHaveProperty(UNAUTHENTICATED_SESSION_ID) }) }) describe('upsertSession', () => { it('should create a new session when it does not exist', () => { const manager = SessionManager.hydrate( JSON.stringify({ sessions: {}, currentSessionId: UNAUTHENTICATED_SESSION_ID }) ) const newSession = new Session({ token: createToken('token-87654321') }) manager.upsertSession(newSession) expect(manager.sessions).toHaveProperty('87654321') expect(manager.currentSessionId).toBe('87654321') expect(manager.currentSession.token?.access_token).toBe('token-87654321') }) it('should update an existing session', () => { const session1 = new Session({ env: 'production', token: createToken('token-12345678') }) const managerString = JSON.stringify({ sessions: { '12345678': session1.toString() }, currentSessionId: '12345678', }) const manager = SessionManager.hydrate(managerString) const updatedSession = new Session({ env: 'staging', token: createToken('token-12345678') }) manager.upsertSession(updatedSession) expect(manager.sessions).toHaveProperty('12345678') expect(manager.currentSession.env).toBe('staging') expect(manager.currentSessionId).toBe('12345678') }) it('should update currentSessionId to match the session ID', () => { const manager = SessionManager.hydrate( JSON.stringify({ sessions: {}, currentSessionId: UNAUTHENTICATED_SESSION_ID }) ) const session = new Session({ token: createToken('token-11111111') }) manager.upsertSession(session) expect(manager.currentSessionId).toBe('11111111') }) }) describe('removeSession', () => { it('should remove a session from sessions', () => { const session1 = new Session({ token: createToken('token-11111111') }) const session2 = new Session({ token: createToken('token-22222222') }) const managerString = JSON.stringify({ sessions: { '11111111': session1.toString(), '22222222': session2.toString(), }, currentSessionId: '11111111', }) const manager = SessionManager.hydrate(managerString) manager.removeSession('11111111') expect(manager.sessions).not.toHaveProperty('11111111') expect(manager.sessions).toHaveProperty('22222222') }) it('should set currentSessionId to unauthenticated when removing the current session', () => { const session = new Session({ token: createToken('token-12345678') }) const managerString = JSON.stringify({ sessions: { '12345678': session.toString() }, currentSessionId: '12345678', }) const manager = SessionManager.hydrate(managerString) manager.removeSession('12345678') expect(manager.currentSessionId).toBe(UNAUTHENTICATED_SESSION_ID) expect(manager.sessions).not.toHaveProperty('12345678') }) it('should not change currentSessionId when removing a different session', () => { const session1 = new Session({ token: createToken('token-11111111') }) const session2 = new Session({ token: createToken('token-22222222') }) const managerString = JSON.stringify({ sessions: { '11111111': session1.toString(), '22222222': session2.toString(), }, currentSessionId: '11111111', }) const manager = SessionManager.hydrate(managerString) manager.removeSession('22222222') expect(manager.currentSessionId).toBe('11111111') expect(manager.sessions).not.toHaveProperty('22222222') }) it('should handle removing unauthenticated session gracefully', () => { const manager = SessionManager.hydrate( JSON.stringify({ sessions: {}, currentSessionId: UNAUTHENTICATED_SESSION_ID }) ) manager.removeSession(UNAUTHENTICATED_SESSION_ID) expect(manager.currentSessionId).toBe(UNAUTHENTICATED_SESSION_ID) expect(manager.sessions).not.toHaveProperty(UNAUTHENTICATED_SESSION_ID) }) }) describe('updateCurrentToken', () => { it('should update the token on the current session', () => { const oldToken = createToken('token-11111111') const session = new Session({ token: oldToken }) const managerString = JSON.stringify({ sessions: { '11111111': session.toString() }, currentSessionId: '11111111', }) const manager = SessionManager.hydrate(managerString) const newToken = createToken('token-22222222') manager.updateCurrentToken(newToken) expect(manager.currentSession.token?.access_token).toBe('token-22222222') expect(manager.currentSessionId).toBe('22222222') }) it('should remove the old session when token changes', () => { const oldToken = createToken('token-11111111') const session = new Session({ token: oldToken }) const managerString = JSON.stringify({ sessions: { '11111111': session.toString() }, currentSessionId: '11111111', }) const manager = SessionManager.hydrate(managerString) const newToken = createToken('token-22222222') manager.updateCurrentToken(newToken) expect(manager.sessions).not.toHaveProperty('11111111') expect(manager.sessions).toHaveProperty('22222222') }) it('should create a session when updating token on unauthenticated session', () => { const manager = SessionManager.hydrate( JSON.stringify({ sessions: {}, currentSessionId: UNAUTHENTICATED_SESSION_ID }) ) const token = createToken('token-12345678') manager.updateCurrentToken(token) expect(manager.currentSession.isAuthenticated).toBe(true) expect(manager.currentSession.token?.access_token).toBe('token-12345678') expect(manager.currentSessionId).toBe('12345678') }) it('should preserve environment when updating token', () => { const session = new Session({ env: 'staging', token: createToken('token-11111111') }) const managerString = JSON.stringify({ sessions: { '11111111': session.toString() }, currentSessionId: '11111111', }) const manager = SessionManager.hydrate(managerString) const newToken = createToken('token-22222222') manager.updateCurrentToken(newToken) expect(manager.currentSession.env).toBe('staging') expect(manager.currentSession.token?.access_token).toBe('token-22222222') }) }) describe('toString', () => { it('should serialize the manager to a JSON string', () => { const session = new Session({ token: createToken('token-12345678') }) const managerString = JSON.stringify({ sessions: { '12345678': session.toString() }, currentSessionId: '12345678', }) const manager = SessionManager.hydrate(managerString) const serialized = manager.toString() const parsed = JSON.parse(serialized) expect(parsed.currentSessionId).toBe('12345678') expect(parsed.sessions).toHaveProperty('12345678') }) it('should produce a string that can be hydrated back to the same state', () => { const session1 = new Session({ token: createToken('token-11111111') }) const session2 = new Session({ token: createToken('token-22222222') }) const managerString = JSON.stringify({ sessions: { '11111111': session1.toString(), '22222222': session2.toString(), }, currentSessionId: '11111111', }) const manager = SessionManager.hydrate(managerString) const serialized = manager.toString() const rehydrated = SessionManager.hydrate(serialized) expect(rehydrated.currentSessionId).toBe(manager.currentSessionId) expect(Object.keys(rehydrated.sessions)).toEqual(Object.keys(manager.sessions)) expect(rehydrated.currentSession.token?.access_token).toBe( manager.currentSession.token?.access_token ) }) }) })