/** * Layers API Tests – CRUD (List / Create / Update / Delete) */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import type { FastifyInstance } from 'fastify'; import { SqliteAdapter } from '../src/database/SqliteAdapter.js'; import { createServer } from '../src/server.js'; describe('Layers API', () => { let app: FastifyInstance; let db: SqliteAdapter; let drawingId: string; let createdLayerId: string; beforeAll(async () => { db = new SqliteAdapter(':memory:'); await db.init(); app = await createServer({ db, port: 0 }); await app.ready(); // Create project → drawing hierarchy const projRes = await app.inject({ method: 'POST', url: '/api/projects', payload: { name: 'Layers Test Project' }, }); const projectId = JSON.parse(projRes.body).id; const drawRes = await app.inject({ method: 'POST', url: `/api/projects/${projectId}/drawings`, payload: { name: 'Layers Test Drawing' }, }); drawingId = JSON.parse(drawRes.body).id; }); afterAll(async () => { await app.close(); db.close(); }); // ─── Create ───────────────────────────────────────── describe('POST /api/drawings/:drawingId/layers', () => { it('should create a layer with 201', async () => { const response = await app.inject({ method: 'POST', url: `/api/drawings/${drawingId}/layers`, payload: { name: 'Background', color: '#ff0000' }, }); expect(response.statusCode).toBe(201); const body = JSON.parse(response.body); expect(body.id).toBeDefined(); expect(body.name).toBe('Background'); expect(body.color).toBe('#ff0000'); expect(body.drawing_id).toBe(drawingId); createdLayerId = body.id; }); it('should create a layer with default values', async () => { const response = await app.inject({ method: 'POST', url: `/api/drawings/${drawingId}/layers`, payload: {}, }); expect(response.statusCode).toBe(201); const body = JSON.parse(response.body); expect(body.name).toBe('Layer'); expect(body.visible).toBe(1); expect(body.locked).toBe(0); expect(body.color).toBe('#ffffff'); expect(body.line_type).toBe('solid'); }); }); // ─── List ─────────────────────────────────────────── describe('GET /api/drawings/:drawingId/layers', () => { it('should list layers for a drawing', async () => { const response = await app.inject({ method: 'GET', url: `/api/drawings/${drawingId}/layers`, }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(Array.isArray(body)).toBe(true); expect(body.length).toBeGreaterThanOrEqual(2); }); it('should return empty array for drawing with no layers', async () => { // Create a new drawing with no layers const projRes = await app.inject({ method: 'POST', url: '/api/projects', payload: { name: 'Empty Layers Project' }, }); const projId = JSON.parse(projRes.body).id; const drawRes = await app.inject({ method: 'POST', url: `/api/projects/${projId}/drawings`, payload: { name: 'Empty Drawing' }, }); const emptyDrawId = JSON.parse(drawRes.body).id; const response = await app.inject({ method: 'GET', url: `/api/drawings/${emptyDrawId}/layers`, }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(Array.isArray(body)).toBe(true); expect(body.length).toBe(0); }); }); // ─── Update ───────────────────────────────────────── describe('PATCH /api/layers/:id', () => { it('should update layer name', async () => { const response = await app.inject({ method: 'PATCH', url: `/api/layers/${createdLayerId}`, payload: { name: 'Updated Layer Name' }, }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(body.name).toBe('Updated Layer Name'); }); it('should update layer visibility', async () => { const response = await app.inject({ method: 'PATCH', url: `/api/layers/${createdLayerId}`, payload: { visible: 0, locked: 1 }, }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(body.visible).toBe(0); expect(body.locked).toBe(1); }); it('should update layer color and line_type', async () => { const response = await app.inject({ method: 'PATCH', url: `/api/layers/${createdLayerId}`, payload: { color: '#00ff00', line_type: 'dashed' }, }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(body.color).toBe('#00ff00'); expect(body.line_type).toBe('dashed'); }); it('should return 404 for non-existent layer update', async () => { const response = await app.inject({ method: 'PATCH', url: '/api/layers/non-existent-id', payload: { name: 'New Name' }, }); expect(response.statusCode).toBe(404); }); }); // ─── Delete ───────────────────────────────────────── describe('DELETE /api/layers/:id', () => { it('should delete a layer', async () => { // Create a layer to delete const createRes = await app.inject({ method: 'POST', url: `/api/drawings/${drawingId}/layers`, payload: { name: 'To Be Deleted' }, }); const layerId = JSON.parse(createRes.body).id; const response = await app.inject({ method: 'DELETE', url: `/api/layers/${layerId}`, }); expect(response.statusCode).toBe(204); // Verify it's gone via list const listRes = await app.inject({ method: 'GET', url: `/api/drawings/${drawingId}/layers`, }); const layers = JSON.parse(listRes.body); expect(layers.find((l: any) => l.id === layerId)).toBeUndefined(); }); it('should return 404 for non-existent layer delete', async () => { const response = await app.inject({ method: 'DELETE', url: '/api/layers/non-existent-id', }); expect(response.statusCode).toBe(404); }); }); });