diff --git a/backend/__tests__/api.test.js b/backend/__tests__/api.test.js new file mode 100644 index 0000000..e9e6978 --- /dev/null +++ b/backend/__tests__/api.test.js @@ -0,0 +1,166 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import request from 'supertest'; +import express from 'express'; +import cors from 'cors'; +import knex from 'knex'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import { Router } from 'express'; +import { registerToken, authMiddleware } from '../middleware/auth.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +let app; +let db; +const TEST_TOKEN = 'test-token-12345'; + +function createCVRoutes(db) { + const router = Router(); + + router.get('/', async (req, res) => { + try { + const row = await db('cv_data').where({ id: 1 }).first(); + if (!row) { + return res.status(404).json({ error: 'CV data not found' }); + } + res.json(JSON.parse(row.data)); + } catch (error) { + console.error('Error fetching CV:', error); + res.status(500).json({ error: 'Failed to fetch CV data' }); + } + }); + + router.put('/', authMiddleware, async (req, res) => { + try { + const data = req.body; + if (!data || typeof data !== 'object') { + return res.status(400).json({ error: 'Invalid CV data' }); + } + if (!data.personal?.name) { + return res.status(400).json({ error: 'personal.name is required' }); + } + await db('cv_data').where({ id: 1 }).update({ + data: JSON.stringify(data), + updated_at: db.fn.now() + }); + res.json({ success: true, message: 'CV data updated' }); + } catch (error) { + console.error('Error updating CV:', error); + res.status(500).json({ error: 'Failed to update CV data' }); + } + }); + + router.get('/export', async (req, res) => { + try { + const row = await db('cv_data').where({ id: 1 }).first(); + if (!row) { + return res.status(404).json({ error: 'CV data not found' }); + } + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Content-Disposition', 'attachment; filename="cv.json"'); + res.send(row.data); + } catch (error) { + console.error('Error exporting CV:', error); + res.status(500).json({ error: 'Failed to export CV data' }); + } + }); + + return router; +} + +beforeAll(async () => { + const testDbPath = join(__dirname, 'test.db'); + + db = knex({ + client: 'better-sqlite3', + connection: { filename: testDbPath }, + useNullAsDefault: true + }); + + await db.schema.dropTableIfExists('cv_data'); + await db.schema.createTable('cv_data', (table) => { + table.increments('id').primary(); + table.text('data').notNullable(); + table.datetime('updated_at').defaultTo(db.fn.now()); + }); + + const defaultData = { + personal: { name: 'Test User', title: 'Developer', email: 'test@test.com' }, + experience: [], + skills: {}, + education: [], + projects: [] + }; + + await db('cv_data').insert({ id: 1, data: JSON.stringify(defaultData) }); + + registerToken(TEST_TOKEN); + + app = express(); + app.use(cors()); + app.use(express.json()); + app.use('/api/cv', createCVRoutes(db)); +}); + +afterAll(async () => { + if (db) { + await db.destroy(); + } +}); + +describe('CV API', () => { + it('GET /api/cv returns CV data', async () => { + const response = await request(app).get('/api/cv'); + expect(response.status).toBe(200); + expect(response.body.personal.name).toBe('Test User'); + }); + + it('PUT /api/cv updates CV data', async () => { + const response = await request(app) + .put('/api/cv') + .set('Authorization', `Bearer ${TEST_TOKEN}`) + .send({ + personal: { name: 'Updated Name', title: 'Dev', email: 'test@test.com' }, + experience: [], + skills: {}, + education: [], + projects: [] + }); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + + const getResponse = await request(app).get('/api/cv'); + expect(getResponse.body.personal.name).toBe('Updated Name'); + }); + + it('PUT /api/cv validates required fields', async () => { + const response = await request(app) + .put('/api/cv') + .set('Authorization', `Bearer ${TEST_TOKEN}`) + .send({ personal: {} }); + expect(response.status).toBe(400); + expect(response.body.error).toContain('name is required'); + }); + + it('PUT /api/cv requires authentication', async () => { + const response = await request(app) + .put('/api/cv') + .send({ + personal: { name: 'Test', title: 'Dev', email: 'test@test.com' }, + experience: [], + skills: {}, + education: [], + projects: [] + }); + expect(response.status).toBe(401); + }); + + it('GET /api/cv/export returns 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'); + const data = JSON.parse(response.text); + expect(data.personal).toBeDefined(); + }); +});