diff --git a/src/lib/auth/session.test.ts b/src/lib/auth/session.test.ts new file mode 100644 index 0000000..7c92c94 --- /dev/null +++ b/src/lib/auth/session.test.ts @@ -0,0 +1,198 @@ +import { describe, it, expect, beforeEach } from '@jest/globals'; +import { + createSession, + isSessionValid, + getSessionAge, + getSessionTimeRemaining, + isSessionExpired, + createSessionWithCustomExpiration, + Session, + SessionData, +} from './session'; + +describe('session management', () => { + describe('createSession', () => { + it('should create session with user data', () => { + const session = createSession({ userId: '123', role: 'admin' }); + expect(session).toBeDefined(); + expect(session.userId).toBe('123'); + expect(session.role).toBe('admin'); + }); + + it('should create session with createdAt timestamp', () => { + const beforeCreate = Date.now(); + const session = createSession({ userId: '123' }); + const afterCreate = Date.now(); + expect(session.createdAt).toBeGreaterThanOrEqual(beforeCreate); + expect(session.createdAt).toBeLessThanOrEqual(afterCreate); + }); + + it('should create session with 24 hour expiration', () => { + const session = createSession({ userId: '123' }); + const expectedExpiration = session.createdAt + (24 * 60 * 60 * 1000); + expect(session.expiresAt).toBe(expectedExpiration); + }); + + it('should create session without optional role', () => { + const session = createSession({ userId: '123' }); + expect(session.userId).toBe('123'); + expect(session.role).toBeUndefined(); + }); + }); + + describe('isSessionValid', () => { + it('should return true for valid session', () => { + const session = createSession({ userId: '123' }); + expect(isSessionValid(session)).toBe(true); + }); + + it('should return false for expired session', () => { + const expiredSession = createSessionWithCustomExpiration({ userId: '123' }, -1000); + expect(isSessionValid(expiredSession)).toBe(false); + }); + + it('should return false for session with zero expiration time', () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, 0); + expect(isSessionValid(session)).toBe(false); + }); + + it('should return true for session with 1ms remaining', () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, 1); + expect(isSessionValid(session)).toBe(true); + }); + }); + + describe('getSessionAge', () => { + it('should return age of session in milliseconds', () => { + const session = createSession({ userId: '123' }); + const age = getSessionAge(session); + expect(age).toBeGreaterThanOrEqual(0); + expect(age).toBeLessThan(100); + }); + + it('should increase over time', async () => { + const session = createSession({ userId: '123' }); + const age1 = getSessionAge(session); + await new Promise(resolve => setTimeout(resolve, 10)); + const age2 = getSessionAge(session); + expect(age2).toBeGreaterThan(age1); + }); + }); + + describe('getSessionTimeRemaining', () => { + it('should return positive time for valid session', () => { + const session = createSession({ userId: '123' }); + const remaining = getSessionTimeRemaining(session); + expect(remaining).toBeGreaterThan(0); + }); + + it('should return approximately 24 hours for new session', () => { + const session = createSession({ userId: '123' }); + const remaining = getSessionTimeRemaining(session); + const expectedRemaining = 24 * 60 * 60 * 1000; + const tolerance = 100; + expect(remaining).toBeGreaterThanOrEqual(expectedRemaining - tolerance); + expect(remaining).toBeLessThanOrEqual(expectedRemaining + tolerance); + }); + + it('should return 0 for expired session', () => { + const expiredSession = createSessionWithCustomExpiration({ userId: '123' }, -1000); + const remaining = getSessionTimeRemaining(expiredSession); + expect(remaining).toBe(0); + }); + + it('should return 0 for session at exact expiration time', () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, 0); + const remaining = getSessionTimeRemaining(session); + expect(remaining).toBe(0); + }); + }); + + describe('isSessionExpired', () => { + it('should return false for valid session', () => { + const session = createSession({ userId: '123' }); + expect(isSessionExpired(session)).toBe(false); + }); + + it('should return true for expired session', () => { + const expiredSession = createSessionWithCustomExpiration({ userId: '123' }, -1000); + expect(isSessionExpired(expiredSession)).toBe(true); + }); + + it('should return true for session at exact expiration time', () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, 0); + expect(isSessionExpired(session)).toBe(true); + }); + + it('should return false for session with 1ms remaining', () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, 1); + expect(isSessionExpired(session)).toBe(false); + }); + }); + + describe('createSessionWithCustomExpiration', () => { + it('should create session with custom expiration time', () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, 60000); + const expectedExpiration = session.createdAt + 60000; + expect(session.expiresAt).toBe(expectedExpiration); + }); + + it('should create session with negative expiration time', () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, -1000); + const expectedExpiration = session.createdAt - 1000; + expect(session.expiresAt).toBe(expectedExpiration); + }); + + it('should preserve user data', () => { + const session = createSessionWithCustomExpiration( + { userId: '123', role: 'admin' }, + 60000 + ); + expect(session.userId).toBe('123'); + expect(session.role).toBe('admin'); + }); + }); + + describe('session lifecycle', () => { + it('should track session from creation to expiration', async () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, 100); + + expect(isSessionValid(session)).toBe(true); + expect(isSessionExpired(session)).toBe(false); + + await new Promise(resolve => setTimeout(resolve, 150)); + + expect(isSessionValid(session)).toBe(false); + expect(isSessionExpired(session)).toBe(true); + }); + + it('should have consistent age and time remaining', () => { + const session = createSessionWithCustomExpiration({ userId: '123' }, 10000); + const age = getSessionAge(session); + const remaining = getSessionTimeRemaining(session); + const totalLifetime = age + remaining; + + expect(totalLifetime).toBeCloseTo(10000, -2); + }); + }); + + describe('session data integrity', () => { + it('should maintain session data immutability', () => { + const originalData: SessionData = { userId: '123', role: 'admin' }; + const session = createSession(originalData); + + expect(session.userId).toBe(originalData.userId); + expect(session.role).toBe(originalData.role); + }); + + it('should handle empty role gracefully', () => { + const session = createSession({ userId: '123', role: '' }); + expect(session.role).toBe(''); + }); + + it('should handle special characters in userId', () => { + const session = createSession({ userId: 'user-123@example.com' }); + expect(session.userId).toBe('user-123@example.com'); + }); + }); +}); diff --git a/src/lib/auth/session.ts b/src/lib/auth/session.ts new file mode 100644 index 0000000..57cc219 --- /dev/null +++ b/src/lib/auth/session.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from '@jest/globals'; + +export interface SessionData { + userId: string; + role?: string; +} + +export interface Session extends SessionData { + createdAt: number; + expiresAt: number; +} + +export function createSession(userData: SessionData): Session { + return { + ...userData, + createdAt: Date.now(), + expiresAt: Date.now() + (24 * 60 * 60 * 1000), + }; +} + +export function isSessionValid(session: Session): boolean { + return Date.now() < session.expiresAt; +} + +export function getSessionAge(session: Session): number { + return Date.now() - session.createdAt; +} + +export function getSessionTimeRemaining(session: Session): number { + return Math.max(0, session.expiresAt - Date.now()); +} + +export function isSessionExpired(session: Session): boolean { + return Date.now() >= session.expiresAt; +} + +export function createSessionWithCustomExpiration(userData: SessionData, expiresInMs: number): Session { + return { + ...userData, + createdAt: Date.now(), + expiresAt: Date.now() + expiresInMs, + }; +}