/** * E2E Workflow Test: Login → Create Project → Create Drawing → Create Layer → Create Element → 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('E2E: Full CAD Workflow', () => { let app: FastifyInstance; let db: SqliteAdapter; let authToken: string; let projectId: string; let drawingId: string; let layerId: string; let elementId: string; beforeAll(async () => { db = new SqliteAdapter(':memory:'); await db.init(); app = await createServer({ db, port: 0 }); await app.ready(); }); afterAll(async () => { await app.close(); db.close(); }); it('Step 1: Register a new user', async () => { const res = await app.inject({ method: 'POST', url: '/api/auth/register', payload: { email: 'e2e@example.com', password: 'Password123!', name: 'E2E Tester' }, }); expect(res.statusCode).toBe(201); const body = JSON.parse(res.body); expect(body.session.token).toBeDefined(); authToken = body.session.token; }); it('Step 2: Get user profile', async () => { const res = await app.inject({ method: 'GET', url: '/api/auth/me', headers: { authorization: `Bearer ${authToken}` }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body); expect(body.email).toBe('e2e@example.com'); }); it('Step 3: Create a project', async () => { const res = await app.inject({ method: 'POST', url: '/api/projects', headers: { authorization: `Bearer ${authToken}` }, payload: { name: 'E2E Test Project', description: 'Full workflow test' }, }); expect(res.statusCode).toBe(201); const body = JSON.parse(res.body); expect(body.name).toBe('E2E Test Project'); projectId = body.id; }); it('Step 4: Create a drawing in the project', async () => { const res = await app.inject({ method: 'POST', url: `/api/projects/${projectId}/drawings`, headers: { authorization: `Bearer ${authToken}` }, payload: { name: 'Test Drawing' }, }); expect(res.statusCode).toBe(201); const body = JSON.parse(res.body); expect(body.name).toBe('Test Drawing'); drawingId = body.id; }); it('Step 5: Create a layer in the drawing', async () => { const res = await app.inject({ method: 'POST', url: `/api/drawings/${drawingId}/layers`, headers: { authorization: `Bearer ${authToken}` }, payload: { name: 'Test Layer', color: '#ff0000' }, }); expect(res.statusCode).toBe(201); const body = JSON.parse(res.body); expect(body.name).toBe('Test Layer'); layerId = body.id; }); it('Step 6: Create an element on the layer', async () => { const res = await app.inject({ method: 'POST', url: `/api/drawings/${drawingId}/elements`, headers: { authorization: `Bearer ${authToken}` }, payload: { layer_id: layerId, type: 'rect', x: 10, y: 20, width: 100, height: 50, properties_json: JSON.stringify({ stroke: '#0000ff', fill: '#cccccc' }), }, }); expect(res.statusCode).toBe(201); const body = JSON.parse(res.body); expect(body.type).toBe('rect'); elementId = body.id; }); it('Step 7: List elements in the drawing', async () => { const res = await app.inject({ method: 'GET', url: `/api/drawings/${drawingId}/elements`, headers: { authorization: `Bearer ${authToken}` }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body); expect(body.length).toBe(1); expect(body[0].id).toBe(elementId); }); it('Step 8: Update the element', async () => { const res = await app.inject({ method: 'PATCH', url: `/api/elements/${elementId}`, headers: { authorization: `Bearer ${authToken}` }, payload: { x: 50, y: 60, width: 200, height: 100 }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body); expect(body.x).toBe(50); expect(body.width).toBe(200); }); it('Step 9: Update the layer visibility', async () => { const res = await app.inject({ method: 'PATCH', url: `/api/layers/${layerId}`, headers: { authorization: `Bearer ${authToken}` }, payload: { visible: 0 }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body); expect(body.visible).toBe(0); }); it('Step 10: Delete the element', async () => { const res = await app.inject({ method: 'DELETE', url: `/api/elements/${elementId}`, headers: { authorization: `Bearer ${authToken}` }, }); expect(res.statusCode).toBe(204); }); it('Step 11: Verify element is deleted', async () => { const res = await app.inject({ method: 'GET', url: `/api/drawings/${drawingId}/elements`, headers: { authorization: `Bearer ${authToken}` }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body); expect(body.length).toBe(0); }); it('Step 12: Delete the drawing', async () => { const res = await app.inject({ method: 'DELETE', url: `/api/drawings/${drawingId}`, headers: { authorization: `Bearer ${authToken}` }, }); expect(res.statusCode).toBe(204); }); it('Step 13: Delete the project', async () => { const res = await app.inject({ method: 'DELETE', url: `/api/projects/${projectId}`, headers: { authorization: `Bearer ${authToken}` }, }); expect(res.statusCode).toBe(204); }); it('Step 14: Logout', async () => { const res = await app.inject({ method: 'POST', url: '/api/auth/logout', headers: { authorization: `Bearer ${authToken}` }, }); expect(res.statusCode).toBe(204); }); it('Step 15: Verify token is invalid after logout', async () => { const res = await app.inject({ method: 'GET', url: '/api/auth/me', headers: { authorization: `Bearer ${authToken}` }, }); expect(res.statusCode).toBe(401); }); });