test(ui): add e2e and integration tests
This commit is contained in:
205
tests/integration/fullstack.test.js
Normal file
205
tests/integration/fullstack.test.js
Normal file
@@ -0,0 +1,205 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user