import { describe, it, expect } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; const ROOT_DIR = path.resolve(__dirname, '..'); describe('Project API Endpoints', () => { describe('Project API Controller', () => { it('should have project controller registered', () => { const apiV1Path = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts'); expect(fs.existsSync(apiV1Path)).toBe(true); }); it('should have GET projects route', () => { const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts'); const content = fs.readFileSync(controllerPath, 'utf-8'); expect(content).toContain('getProjects'); }); it('should have POST projects route', () => { const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts'); const content = fs.readFileSync(controllerPath, 'utf-8'); expect(content).toContain('createProject'); }); it('should use requireUser middleware', () => { const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts'); const content = fs.readFileSync(controllerPath, 'utf-8'); expect(content).toContain('requireUser()'); }); it('should not expose password fields in responses', () => { const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts'); const content = fs.readFileSync(controllerPath, 'utf-8'); expect(content).not.toMatch(/passwordSalt/); }); }); describe('Project Service', () => { it('should have findById method', () => { const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts'); const content = fs.readFileSync(servicePath, 'utf-8'); expect(content).toContain('findById'); }); it('should have delete method', () => { const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts'); const content = fs.readFileSync(servicePath, 'utf-8'); expect(content).toContain('async delete'); }); it('should have getForUser method', () => { const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts'); const content = fs.readFileSync(servicePath, 'utf-8'); expect(content).toContain('getForUser'); }); it('should populate user with password excluded', () => { const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts'); const content = fs.readFileSync(servicePath, 'utf-8'); expect(content).toContain('-passwordSalt'); }); }); describe('Frontend API Client', () => { it('should add Authorization header with token', () => { const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts'); const content = fs.readFileSync(apiPath, 'utf-8'); expect(content).toContain('Authorization'); expect(content).toContain('Bearer'); }); it('should store token in localStorage', () => { const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts'); const content = fs.readFileSync(apiPath, 'utf-8'); expect(content).toContain('dtp_auth_token'); }); it('should have getAll method', () => { const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts'); const content = fs.readFileSync(apiPath, 'utf-8'); expect(content).toContain('getAll'); }); }); describe('User Interface', () => { it('should not include password fields in User type', () => { const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts'); const content = fs.readFileSync(apiPath, 'utf-8'); const userInterfaceMatch = content.match(/export interface User/); expect(userInterfaceMatch).toBeTruthy(); }); }); });