import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import request from 'supertest'; import express from 'express'; import cors from 'cors'; import { mkdirSync, existsSync, rmSync } from 'fs'; import { join } from 'path'; import Database from 'better-sqlite3'; const testDbPath = join(process.cwd(), 'tests', 'integration', 'test.db'); let app; let db; function setupTestDB() { const dbDir = join(process.cwd(), 'tests', 'integration'); if (!existsSync(dbDir)) { mkdirSync(dbDir, { recursive: true }); } if (existsSync(testDbPath)) { rmSync(testDbPath); } db = new Database(testDbPath); db.exec(` CREATE TABLE IF NOT EXISTS cv_data ( id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); const defaultData = { personal: { name: 'Test User', title: 'Developer', email: 'test@test.com' }, experience: [], skills: {}, education: [], projects: [] }; db.prepare('INSERT INTO cv_data (id, data) VALUES (1, ?)').run(JSON.stringify(defaultData)); } function createTestApp() { const app = express(); app.use(cors()); app.use(express.json()); app.get('/api/cv', (req, res) => { const row = db.prepare('SELECT data FROM cv_data WHERE id = 1').get(); if (!row) return res.status(404).json({ error: 'CV data not found' }); res.json(JSON.parse(row.data)); }); app.put('/api/cv', (req, res) => { const data = req.body; if (!data?.personal?.name) { return res.status(400).json({ error: 'personal.name is required' }); } db.prepare('UPDATE cv_data SET data = ?, updated_at = CURRENT_TIMESTAMP WHERE id = 1') .run(JSON.stringify(data)); res.json({ success: true, message: 'CV data updated' }); }); app.get('/api/cv/export', (req, res) => { const row = db.prepare('SELECT data FROM cv_data WHERE id = 1').get(); res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', 'attachment; filename="cv.json"'); res.send(row.data); }); app.post('/api/cv/import', (req, res) => { const data = req.body; if (!data?.personal?.name) { return res.status(400).json({ error: 'personal.name is required' }); } db.prepare('UPDATE cv_data SET data = ?, updated_at = CURRENT_TIMESTAMP WHERE id = 1') .run(JSON.stringify(data)); res.json({ success: true, message: 'CV data imported' }); }); app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); return app; } describe('Integration: Full Stack CV API', () => { beforeAll(() => { setupTestDB(); app = createTestApp(); }); afterAll(() => { if (db) { db.close(); } if (existsSync(testDbPath)) { rmSync(testDbPath); } }); describe('GET /api/cv', () => { it('returns CV data from database', async () => { const response = await request(app).get('/api/cv'); expect(response.status).toBe(200); expect(response.body).toHaveProperty('personal'); expect(response.body.personal.name).toBe('Test User'); }); it('returns all required CV sections', async () => { const response = await request(app).get('/api/cv'); expect(response.body).toHaveProperty('personal'); expect(response.body).toHaveProperty('experience'); expect(response.body).toHaveProperty('skills'); expect(response.body).toHaveProperty('education'); expect(response.body).toHaveProperty('projects'); }); }); describe('PUT /api/cv', () => { it('updates CV data in database', async () => { const newData = { personal: { name: 'Updated Name', title: 'Senior Dev', email: 'updated@test.com' }, experience: [{ id: 1, role: 'New Role' }], skills: { 'Backend': ['Node.js'] }, education: [], projects: [] }; const response = await request(app) .put('/api/cv') .send(newData); expect(response.status).toBe(200); expect(response.body.success).toBe(true); const verifyResponse = await request(app).get('/api/cv'); expect(verifyResponse.body.personal.name).toBe('Updated Name'); expect(verifyResponse.body.experience).toHaveLength(1); }); it('validates required fields', async () => { const response = await request(app) .put('/api/cv') .send({ personal: {} }); expect(response.status).toBe(400); expect(response.body.error).toContain('name is required'); }); }); describe('GET /api/cv/export', () => { it('exports CV as JSON file', async () => { const response = await request(app).get('/api/cv/export'); expect(response.status).toBe(200); expect(response.headers['content-type']).toContain('application/json'); expect(response.headers['content-disposition']).toContain('cv.json'); const data = JSON.parse(response.text); expect(data).toHaveProperty('personal'); }); }); describe('POST /api/cv/import', () => { it('imports valid CV data', async () => { const importData = { personal: { name: 'Imported User', title: 'Dev', email: 'import@test.com' }, experience: [], skills: {}, education: [], projects: [] }; const response = await request(app) .post('/api/cv/import') .send(importData); expect(response.status).toBe(200); expect(response.body.success).toBe(true); const verifyResponse = await request(app).get('/api/cv'); expect(verifyResponse.body.personal.name).toBe('Imported User'); }); it('rejects invalid CV data', async () => { const response = await request(app) .post('/api/cv/import') .send({ personal: { title: 'No Name' } }); expect(response.status).toBe(400); }); }); describe('GET /health', () => { it('returns health status', async () => { const response = await request(app).get('/health'); expect(response.status).toBe(200); expect(response.body.status).toBe('ok'); expect(response.body).toHaveProperty('timestamp'); }); }); });