/** * User model unit tests */ import { assertEquals, assertExists, assertRejects } from 'jsr:@std/assert'; import { describe, it, beforeAll, afterAll, beforeEach } from 'jsr:@std/testing/bdd'; import { setupTestDb, teardownTestDb, cleanupTestDb } from '../../helpers/index.ts'; import { User } from '../../../ts/models/user.ts'; describe('User Model', () => { beforeAll(async () => { await setupTestDb(); }); afterAll(async () => { await teardownTestDb(); }); beforeEach(async () => { await cleanupTestDb(); }); describe('createUser', () => { it('should create a user with valid data', async () => { const passwordHash = await User.hashPassword('testpassword'); const user = await User.createUser({ email: 'test@example.com', username: 'testuser', passwordHash, displayName: 'Test User', }); assertExists(user.id); assertEquals(user.email, 'test@example.com'); assertEquals(user.username, 'testuser'); assertEquals(user.displayName, 'Test User'); assertEquals(user.status, 'pending_verification'); assertEquals(user.emailVerified, false); assertEquals(user.isPlatformAdmin, false); }); it('should lowercase email and username', async () => { const passwordHash = await User.hashPassword('testpassword'); const user = await User.createUser({ email: 'TEST@EXAMPLE.COM', username: 'TestUser', passwordHash, }); assertEquals(user.email, 'test@example.com'); assertEquals(user.username, 'testuser'); }); it('should use username as displayName if not provided', async () => { const passwordHash = await User.hashPassword('testpassword'); const user = await User.createUser({ email: 'test2@example.com', username: 'testuser2', passwordHash, }); assertEquals(user.displayName, 'testuser2'); }); }); describe('findByEmail', () => { it('should find user by email (case-insensitive)', async () => { const passwordHash = await User.hashPassword('testpassword'); await User.createUser({ email: 'findme@example.com', username: 'findme', passwordHash, }); const found = await User.findByEmail('FINDME@example.com'); assertExists(found); assertEquals(found.email, 'findme@example.com'); }); it('should return null for non-existent email', async () => { const found = await User.findByEmail('nonexistent@example.com'); assertEquals(found, null); }); }); describe('findByUsername', () => { it('should find user by username (case-insensitive)', async () => { const passwordHash = await User.hashPassword('testpassword'); await User.createUser({ email: 'user@example.com', username: 'findbyname', passwordHash, }); const found = await User.findByUsername('FINDBYNAME'); assertExists(found); assertEquals(found.username, 'findbyname'); }); }); describe('findById', () => { it('should find user by ID', async () => { const passwordHash = await User.hashPassword('testpassword'); const created = await User.createUser({ email: 'byid@example.com', username: 'byid', passwordHash, }); const found = await User.findById(created.id); assertExists(found); assertEquals(found.id, created.id); }); }); describe('password hashing', () => { it('should hash password with salt', async () => { const hash = await User.hashPassword('mypassword'); assertExists(hash); assertEquals(hash.includes(':'), true); const [salt, _hashPart] = hash.split(':'); assertEquals(salt.length, 32); // 16 bytes = 32 hex chars }); it('should produce different hashes for same password', async () => { const hash1 = await User.hashPassword('samepassword'); const hash2 = await User.hashPassword('samepassword'); // Different salts should produce different hashes assertEquals(hash1 !== hash2, true); }); }); describe('verifyPassword', () => { it('should verify correct password', async () => { const passwordHash = await User.hashPassword('correctpassword'); const user = await User.createUser({ email: 'verify@example.com', username: 'verifyuser', passwordHash, }); const isValid = await user.verifyPassword('correctpassword'); assertEquals(isValid, true); }); it('should reject incorrect password', async () => { const passwordHash = await User.hashPassword('correctpassword'); const user = await User.createUser({ email: 'reject@example.com', username: 'rejectuser', passwordHash, }); const isValid = await user.verifyPassword('wrongpassword'); assertEquals(isValid, false); }); it('should reject empty password', async () => { const passwordHash = await User.hashPassword('correctpassword'); const user = await User.createUser({ email: 'empty@example.com', username: 'emptyuser', passwordHash, }); const isValid = await user.verifyPassword(''); assertEquals(isValid, false); }); }); describe('isActive', () => { it('should return true for active status', async () => { const passwordHash = await User.hashPassword('test'); const user = await User.createUser({ email: 'active@example.com', username: 'activeuser', passwordHash, }); user.status = 'active'; await user.save(); assertEquals(user.isActive, true); }); it('should return false for suspended status', async () => { const passwordHash = await User.hashPassword('test'); const user = await User.createUser({ email: 'suspended@example.com', username: 'suspendeduser', passwordHash, }); user.status = 'suspended'; assertEquals(user.isActive, false); }); }); describe('isPlatformAdmin', () => { it('should default to false', async () => { const passwordHash = await User.hashPassword('test'); const user = await User.createUser({ email: 'notadmin@example.com', username: 'notadmin', passwordHash, }); assertEquals(user.isPlatformAdmin, false); assertEquals(user.isSystemAdmin, false); }); it('should be settable to true', async () => { const passwordHash = await User.hashPassword('test'); const user = await User.createUser({ email: 'admin@example.com', username: 'adminuser', passwordHash, }); user.isPlatformAdmin = true; await user.save(); const found = await User.findById(user.id); assertEquals(found!.isPlatformAdmin, true); assertEquals(found!.isSystemAdmin, true); }); }); });