Files

255 lines
9.1 KiB
TypeScript
Raw Permalink 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.
/**
* Blocks 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('Blocks API', () => {
let app: FastifyInstance;
let db: SqliteAdapter;
let drawingId: string;
let createdBlockId: 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 hierarchy
const projRes = await app.inject({
method: 'POST',
url: '/api/projects',
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Blocks 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: 'Blocks Test Drawing' },
});
drawingId = JSON.parse(drawRes.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}/blocks`,
});
expect(response.statusCode).toBe(401);
});
});
// ─── Create ─────────────────────────────────────────
describe('POST /api/drawings/:drawingId/blocks', () => {
it('should create a block with 201', async () => {
const response = await app.inject({
method: 'POST',
url: `/api/drawings/${drawingId}/blocks`,
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Standard Table', category: 'Mobiliar', description: 'A standard round table' },
});
expect(response.statusCode).toBe(201);
const body = JSON.parse(response.body);
expect(body.id).toBeDefined();
expect(body.name).toBe('Standard Table');
expect(body.category).toBe('Mobiliar');
expect(body.description).toBe('A standard round table');
expect(body.drawing_id).toBe(drawingId);
createdBlockId = body.id;
});
it('should create a block with default values', async () => {
const response = await app.inject({
method: 'POST',
url: `/api/drawings/${drawingId}/blocks`,
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Minimal Block' },
});
expect(response.statusCode).toBe(201);
const body = JSON.parse(response.body);
expect(body.name).toBe('Minimal Block');
expect(body.category).toBe('Allgemein');
expect(body.description).toBeNull();
expect(body.elements_json).toBe('[]');
});
it('should reject block without name with 400', async () => {
const response = await app.inject({
method: 'POST',
url: `/api/drawings/${drawingId}/blocks`,
headers: { authorization: `Bearer ${authToken}` },
payload: { category: 'Test' },
});
expect(response.statusCode).toBe(400);
const body = JSON.parse(response.body);
expect(body.error).toContain('Name is required');
});
it('should reject block with empty name with 400', async () => {
const response = await app.inject({
method: 'POST',
url: `/api/drawings/${drawingId}/blocks`,
headers: { authorization: `Bearer ${authToken}` },
payload: { name: '' },
});
expect(response.statusCode).toBe(400);
});
});
// ─── List ───────────────────────────────────────────
describe('GET /api/drawings/:drawingId/blocks', () => {
it('should list blocks for a drawing', async () => {
const response = await app.inject({
method: 'GET',
url: `/api/drawings/${drawingId}/blocks`,
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 blocks', async () => {
const projRes = await app.inject({
method: 'POST',
url: '/api/projects',
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Empty Blocks 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}/blocks`,
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/blocks/:id', () => {
it('should update block name', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/blocks/${createdBlockId}`,
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'Updated Block Name' },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.name).toBe('Updated Block Name');
});
it('should update block category and description', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/blocks/${createdBlockId}`,
headers: { authorization: `Bearer ${authToken}` },
payload: { category: 'Bühne', description: 'Stage equipment' },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.category).toBe('Bühne');
expect(body.description).toBe('Stage equipment');
});
it('should update block elements_json', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/blocks/${createdBlockId}`,
headers: { authorization: `Bearer ${authToken}` },
payload: { elements_json: '[{"type":"rect"}]' },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.elements_json).toBe('[{"type":"rect"}]');
});
it('should return 404 for non-existent block update', async () => {
const response = await app.inject({
method: 'PATCH',
url: '/api/blocks/non-existent-id',
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'New Name' },
});
expect(response.statusCode).toBe(404);
});
});
// ─── Delete ─────────────────────────────────────────
describe('DELETE /api/blocks/:id', () => {
it('should delete a block', async () => {
const createRes = await app.inject({
method: 'POST',
url: `/api/drawings/${drawingId}/blocks`,
headers: { authorization: `Bearer ${authToken}` },
payload: { name: 'To Be Deleted' },
});
const blockId = JSON.parse(createRes.body).id;
const response = await app.inject({
method: 'DELETE',
url: `/api/blocks/${blockId}`,
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}/blocks`,
headers: { authorization: `Bearer ${authToken}` },
});
const blocks = JSON.parse(listRes.body);
expect(blocks.find((b: any) => b.id === blockId)).toBeUndefined();
});
it('should return 404 for non-existent block delete', async () => {
const response = await app.inject({
method: 'DELETE',
url: '/api/blocks/non-existent-id',
headers: { authorization: `Bearer ${authToken}` },
});
expect(response.statusCode).toBe(404);
});
});
});