200 lines
6.6 KiB
TypeScript
200 lines
6.6 KiB
TypeScript
|
|
/**
|
|||
|
|
* 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);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|