Files
web-cad/backend/tests/elements.test.ts
T

250 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Elements 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('Elements API', () => {
let app: FastifyInstance;
let db: SqliteAdapter;
let drawingId: string;
let layerId: string;
let createdElementId: string;
let authToken: string;
beforeAll(async () => {
db = new SqliteAdapter(':memory:');
await db.init();
app = await createServer({ db, port: 0 });
await app.ready();
const regRes = await app.inject({
method: 'POST',
url: '/api/auth/register',
payload: { email: 'admin-test@example.com', password: 'Password123!', name: 'Admin Test', role: 'admin' },
});
authToken = JSON.parse(regRes.body).session.token;
// Create project → drawing → layer hierarchy
const projRes = await app.inject({
method: 'POST',
url: '/api/projects',
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Elements Test Project' },
});
const projectId = JSON.parse(projRes.body).id;
const drawRes = await app.inject({
method: 'POST',
url: `/api/projects/${projectId}/drawings`,
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Elements Test Drawing' },
});
drawingId = JSON.parse(drawRes.body).id;
const layerRes = await app.inject({
method: 'POST',
url: `/api/drawings/${drawingId}/layers`,
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Elements Layer' },
});
layerId = JSON.parse(layerRes.body).id;
});
afterAll(async () => {
await app.close();
db.close();
});
// ─── Auth Check ──────────────────────────────────────
describe('Authentication', () => {
it('should return 401 without authorization header', async () => {
const response = await app.inject({
method: 'GET',
url: `/api/drawings/${drawingId}/elements`,
});
expect(response.statusCode).toBe(401);
});
});
// ─── Create ─────────────────────────────────────────
describe('POST /api/drawings/:drawingId/elements', () => {
it('should create an element with 201', async () => {
const response = 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 },
});
expect(response.statusCode).toBe(201);
const body = JSON.parse(response.body);
expect(body.id).toBeDefined();
expect(body.type).toBe('rect');
expect(body.x).toBe(10);
expect(body.y).toBe(20);
expect(body.width).toBe(100);
expect(body.height).toBe(50);
expect(body.drawing_id).toBe(drawingId);
expect(body.layer_id).toBe(layerId);
createdElementId = body.id;
});
it('should create an element with default values', async () => {
const response = await app.inject({
method: 'POST',
url: `/api/drawings/${drawingId}/elements`,
headers: { authorization: `Bearer ${authToken}` },
payload: { layer_id: layerId, type: 'circle' },
});
expect(response.statusCode).toBe(201);
const body = JSON.parse(response.body);
expect(body.type).toBe('circle');
expect(body.x).toBe(0);
expect(body.y).toBe(0);
expect(body.width).toBe(0);
expect(body.height).toBe(0);
expect(body.properties_json).toBe('{}');
});
});
// ─── List ───────────────────────────────────────────
describe('GET /api/drawings/:drawingId/elements', () => {
it('should list elements for a drawing', async () => {
const response = await app.inject({
method: 'GET',
url: `/api/drawings/${drawingId}/elements`,
headers: { authorization: `Bearer ${authToken}` },
});
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 elements', async () => {
// Create a new drawing with no elements
const projRes = await app.inject({
method: 'POST',
url: '/api/projects',
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Empty Elements Project' },
});
const projId = JSON.parse(projRes.body).id;
const drawRes = await app.inject({
method: 'POST',
url: `/api/projects/${projId}/drawings`,
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Empty Drawing' },
});
const emptyDrawId = JSON.parse(drawRes.body).id;
const response = await app.inject({
method: 'GET',
url: `/api/drawings/${emptyDrawId}/elements`,
headers: { authorization: `Bearer ${authToken}` },
});
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/elements/:id', () => {
it('should update element position', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/elements/${createdElementId}`,
headers: { authorization: `Bearer ${authToken}` },
payload: { x: 100, y: 200 },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.x).toBe(100);
expect(body.y).toBe(200);
});
it('should update element dimensions', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/elements/${createdElementId}`,
headers: { authorization: `Bearer ${authToken}` },
payload: { width: 300, height: 150 },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.width).toBe(300);
expect(body.height).toBe(150);
});
it('should update element properties_json', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/elements/${createdElementId}`,
headers: { authorization: `Bearer ${authToken}` },
payload: { properties_json: '{"color":"red"}' },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.properties_json).toBe('{"color":"red"}');
});
it('should return 404 for non-existent element update', async () => {
const response = await app.inject({
method: 'PATCH',
url: '/api/elements/non-existent-id',
headers: { authorization: `Bearer ${authToken}` },
payload: { x: 0 },
});
expect(response.statusCode).toBe(404);
});
});
// ─── Delete ─────────────────────────────────────────
describe('DELETE /api/elements/:id', () => {
it('should delete an element', async () => {
// Create an element to delete
const createRes = await app.inject({
method: 'POST',
url: `/api/drawings/${drawingId}/elements`,
headers: { authorization: `Bearer ${authToken}` },
payload: { layer_id: layerId, type: 'line' },
});
const elemId = JSON.parse(createRes.body).id;
const response = await app.inject({
method: 'DELETE',
url: `/api/elements/${elemId}`,
headers: { authorization: `Bearer ${authToken}` },
});
expect(response.statusCode).toBe(204);
// Verify it's gone via list
const listRes = await app.inject({
method: 'GET',
url: `/api/drawings/${drawingId}/elements`,
headers: { authorization: `Bearer ${authToken}` },
});
const elements = JSON.parse(listRes.body);
expect(elements.find((e: any) => e.id === elemId)).toBeUndefined();
});
it('should return 404 for non-existent element delete', async () => {
const response = await app.inject({
method: 'DELETE',
url: '/api/elements/non-existent-id',
headers: { authorization: `Bearer ${authToken}` },
});
expect(response.statusCode).toBe(404);
});
});
});