1bcfaffdd4
- Fix frontend Network error: API_BASE now defaults to empty string (same-origin) - Fix 3 files: api.ts, AuthContext.tsx, Dashboard.tsx - Add Canvas 2D context mock in tests/setup.ts for jsdom - Fix -0 vs +0 in ZoomPanController.getViewport() - Add IntegrationWorkflow.test.ts (35 tests, full CAD workflow)
384 lines
14 KiB
TypeScript
384 lines
14 KiB
TypeScript
/**
|
||
* SqliteAdapter Unit Tests – init, CRUD for all entities, close
|
||
*/
|
||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||
import { SqliteAdapter } from '../src/database/SqliteAdapter.js';
|
||
|
||
let idCounter = 0;
|
||
function uniqueId(prefix: string): string {
|
||
idCounter++;
|
||
return `${prefix}-${Date.now()}-${idCounter}`;
|
||
}
|
||
|
||
describe('SqliteAdapter', () => {
|
||
let db: SqliteAdapter;
|
||
|
||
beforeAll(async () => {
|
||
db = new SqliteAdapter(':memory:');
|
||
await db.init();
|
||
});
|
||
|
||
afterAll(() => {
|
||
db.close();
|
||
});
|
||
|
||
// ─── Init & Close ───────────────────────────────────
|
||
|
||
describe('init() and close()', () => {
|
||
it('should initialize the database without errors', async () => {
|
||
const testDb = new SqliteAdapter(':memory:');
|
||
await testDb.init();
|
||
const users = testDb.listUsers();
|
||
expect(Array.isArray(users)).toBe(true);
|
||
testDb.close();
|
||
});
|
||
|
||
it('should seed a default user on init', async () => {
|
||
const defaultUser = db.getUser('user-default');
|
||
expect(defaultUser).not.toBeNull();
|
||
expect(defaultUser!.email).toBe('default@webcad.local');
|
||
expect(defaultUser!.role).toBe('admin');
|
||
});
|
||
});
|
||
|
||
// ─── Users CRUD ─────────────────────────────────────
|
||
|
||
describe('Users CRUD', () => {
|
||
it('should create and get a user', () => {
|
||
const uid = uniqueId('user');
|
||
const user = db.createUser({
|
||
id: uid,
|
||
email: `${uid}@example.com`,
|
||
password_hash: 'hash123',
|
||
name: 'CRUD User',
|
||
role: 'planer',
|
||
});
|
||
expect(user.id).toBe(uid);
|
||
expect(user.email).toBe(`${uid}@example.com`);
|
||
|
||
const fetched = db.getUser(user.id);
|
||
expect(fetched).not.toBeNull();
|
||
expect(fetched!.email).toBe(`${uid}@example.com`);
|
||
});
|
||
|
||
it('should get user by email', () => {
|
||
const uid = uniqueId('user');
|
||
db.createUser({ id: uid, email: `${uid}@example.com`, password_hash: 'hash', name: 'Email User', role: 'planer' });
|
||
const user = db.getUserByEmail(`${uid}@example.com`);
|
||
expect(user).not.toBeNull();
|
||
expect(user!.name).toBe('Email User');
|
||
});
|
||
|
||
it('should return null for non-existent user', () => {
|
||
expect(db.getUser('non-existent-id')).toBeNull();
|
||
expect(db.getUserByEmail('nobody@nowhere.com')).toBeNull();
|
||
});
|
||
|
||
it('should update a user', () => {
|
||
const uid = uniqueId('user');
|
||
db.createUser({ id: uid, email: `${uid}@example.com`, password_hash: 'hash', name: 'Original Name', role: 'planer' });
|
||
const updated = db.updateUser(uid, { name: 'Updated Name', role: 'admin' });
|
||
expect(updated).not.toBeNull();
|
||
expect(updated!.name).toBe('Updated Name');
|
||
expect(updated!.role).toBe('admin');
|
||
});
|
||
|
||
it('should list users', () => {
|
||
const users = db.listUsers();
|
||
expect(users.length).toBeGreaterThanOrEqual(2);
|
||
});
|
||
|
||
it('should delete a user', () => {
|
||
const uid = uniqueId('user');
|
||
db.createUser({ id: uid, email: `${uid}@example.com`, password_hash: 'hash', name: 'Delete Me', role: 'planer' });
|
||
const ok = db.deleteUser(uid);
|
||
expect(ok).toBe(true);
|
||
expect(db.getUser(uid)).toBeNull();
|
||
});
|
||
|
||
it('should return false when deleting non-existent user', () => {
|
||
expect(db.deleteUser('non-existent-id')).toBe(false);
|
||
});
|
||
});
|
||
|
||
// ─── Projects CRUD ──────────────────────────────────
|
||
|
||
describe('Projects CRUD', () => {
|
||
it('should create and get a project', () => {
|
||
const pid = uniqueId('proj');
|
||
const project = db.createProject({ id: pid, name: 'Test Project', description: 'Test desc' });
|
||
expect(project.id).toBe(pid);
|
||
expect(project.name).toBe('Test Project');
|
||
|
||
const fetched = db.getProject(pid);
|
||
expect(fetched).not.toBeNull();
|
||
expect(fetched!.name).toBe('Test Project');
|
||
});
|
||
|
||
it('should return null for non-existent project', () => {
|
||
expect(db.getProject('non-existent-id')).toBeNull();
|
||
});
|
||
|
||
it('should update a project', () => {
|
||
const pid = uniqueId('proj');
|
||
db.createProject({ id: pid, name: 'Update Project' });
|
||
const updated = db.updateProject(pid, { name: 'Updated Project', description: 'New desc' });
|
||
expect(updated).not.toBeNull();
|
||
expect(updated!.name).toBe('Updated Project');
|
||
expect(updated!.description).toBe('New desc');
|
||
});
|
||
|
||
it('should list projects', () => {
|
||
const projects = db.listProjects();
|
||
expect(projects.length).toBeGreaterThanOrEqual(1);
|
||
});
|
||
|
||
it('should delete a project', () => {
|
||
const pid = uniqueId('proj');
|
||
db.createProject({ id: pid, name: 'Delete Project' });
|
||
const ok = db.deleteProject(pid);
|
||
expect(ok).toBe(true);
|
||
expect(db.getProject(pid)).toBeNull();
|
||
});
|
||
|
||
it('should cascade delete drawings when project is deleted', () => {
|
||
const pid = uniqueId('proj');
|
||
const did = uniqueId('draw');
|
||
db.createProject({ id: pid, name: 'Cascade Project' });
|
||
db.createDrawing({ id: did, project_id: pid, name: 'Cascade Drawing' });
|
||
db.deleteProject(pid);
|
||
expect(db.getDrawing(did)).toBeNull();
|
||
});
|
||
});
|
||
|
||
// ─── Drawings CRUD ──────────────────────────────────
|
||
|
||
describe('Drawings CRUD', () => {
|
||
let projectId: string;
|
||
|
||
it('should create and get a drawing', () => {
|
||
projectId = uniqueId('proj');
|
||
const did = uniqueId('draw');
|
||
db.createProject({ id: projectId, name: 'Drawing Project' });
|
||
const drawing = db.createDrawing({ id: did, project_id: projectId, name: 'Floor 1' });
|
||
expect(drawing.id).toBe(did);
|
||
expect(drawing.name).toBe('Floor 1');
|
||
expect(drawing.project_id).toBe(projectId);
|
||
|
||
const fetched = db.getDrawing(did);
|
||
expect(fetched).not.toBeNull();
|
||
expect(fetched!.name).toBe('Floor 1');
|
||
});
|
||
|
||
it('should list drawings by project', () => {
|
||
const did1 = uniqueId('draw');
|
||
const did2 = uniqueId('draw');
|
||
db.createDrawing({ id: did1, project_id: projectId, name: 'Drawing A' });
|
||
db.createDrawing({ id: did2, project_id: projectId, name: 'Drawing B' });
|
||
const drawings = db.listDrawings(projectId);
|
||
expect(drawings.length).toBeGreaterThanOrEqual(3);
|
||
});
|
||
|
||
it('should update a drawing', () => {
|
||
const did = uniqueId('draw');
|
||
db.createDrawing({ id: did, project_id: projectId, name: 'Update Me' });
|
||
const updated = db.updateDrawing(did, { name: 'Updated Drawing', data_json: '{"x":1}' });
|
||
expect(updated).not.toBeNull();
|
||
expect(updated!.name).toBe('Updated Drawing');
|
||
expect(updated!.data_json).toBe('{"x":1}');
|
||
});
|
||
|
||
it('should delete a drawing', () => {
|
||
const did = uniqueId('draw');
|
||
db.createDrawing({ id: did, project_id: projectId, name: 'Delete Me' });
|
||
const ok = db.deleteDrawing(did);
|
||
expect(ok).toBe(true);
|
||
expect(db.getDrawing(did)).toBeNull();
|
||
});
|
||
|
||
it('should cascade delete layers when drawing is deleted', () => {
|
||
const did = uniqueId('draw');
|
||
const lid = uniqueId('layer');
|
||
db.createDrawing({ id: did, project_id: projectId, name: 'Cascade Drawing' });
|
||
db.createLayer({ id: lid, drawing_id: did, name: 'Cascade Layer' });
|
||
db.deleteDrawing(did);
|
||
const layers = db.listLayers(did);
|
||
expect(layers.find((l) => l.id === lid)).toBeUndefined();
|
||
});
|
||
});
|
||
|
||
// ─── Layers CRUD ────────────────────────────────────
|
||
|
||
describe('Layers CRUD', () => {
|
||
let drawingId: string;
|
||
|
||
it('should create and get a layer', () => {
|
||
const pid = uniqueId('proj');
|
||
drawingId = uniqueId('draw');
|
||
const lid = uniqueId('layer');
|
||
db.createProject({ id: pid, name: 'Layer Project' });
|
||
db.createDrawing({ id: drawingId, project_id: pid, name: 'Layer Drawing' });
|
||
const layer = db.createLayer({ id: lid, drawing_id: drawingId, name: 'Layer 1', color: '#ff0000' });
|
||
expect(layer.id).toBe(lid);
|
||
expect(layer.name).toBe('Layer 1');
|
||
expect(layer.color).toBe('#ff0000');
|
||
expect(layer.visible).toBe(1);
|
||
expect(layer.locked).toBe(0);
|
||
});
|
||
|
||
it('should list layers by drawing', () => {
|
||
const lid2 = uniqueId('layer');
|
||
db.createLayer({ id: lid2, drawing_id: drawingId, name: 'Layer 2' });
|
||
const layers = db.listLayers(drawingId);
|
||
expect(layers.length).toBeGreaterThanOrEqual(2);
|
||
});
|
||
|
||
it('should update a layer', () => {
|
||
const lid = uniqueId('layer');
|
||
db.createLayer({ id: lid, drawing_id: drawingId, name: 'Update Layer' });
|
||
const updated = db.updateLayer(lid, { name: 'Updated Layer', visible: 0, locked: 1 });
|
||
expect(updated).not.toBeNull();
|
||
expect(updated!.name).toBe('Updated Layer');
|
||
expect(updated!.visible).toBe(0);
|
||
expect(updated!.locked).toBe(1);
|
||
});
|
||
|
||
it('should delete a layer', () => {
|
||
const lid = uniqueId('layer');
|
||
db.createLayer({ id: lid, drawing_id: drawingId, name: 'Delete Layer' });
|
||
const ok = db.deleteLayer(lid);
|
||
expect(ok).toBe(true);
|
||
const layers = db.listLayers(drawingId);
|
||
expect(layers.find((l) => l.id === lid)).toBeUndefined();
|
||
});
|
||
});
|
||
|
||
// ─── Elements CRUD ──────────────────────────────────
|
||
|
||
describe('Elements CRUD', () => {
|
||
let drawingId: string;
|
||
let layerId: string;
|
||
|
||
it('should create and get an element', () => {
|
||
const pid = uniqueId('proj');
|
||
drawingId = uniqueId('draw');
|
||
layerId = uniqueId('layer');
|
||
const eid = uniqueId('elem');
|
||
db.createProject({ id: pid, name: 'Element Project' });
|
||
db.createDrawing({ id: drawingId, project_id: pid, name: 'Element Drawing' });
|
||
db.createLayer({ id: layerId, drawing_id: drawingId, name: 'Element Layer' });
|
||
|
||
const element = db.createElement({
|
||
id: eid,
|
||
drawing_id: drawingId,
|
||
layer_id: layerId,
|
||
type: 'rect',
|
||
x: 10,
|
||
y: 20,
|
||
width: 100,
|
||
height: 50,
|
||
});
|
||
expect(element.id).toBe(eid);
|
||
expect(element.type).toBe('rect');
|
||
expect(element.x).toBe(10);
|
||
expect(element.y).toBe(20);
|
||
});
|
||
|
||
it('should list elements by drawing', () => {
|
||
const eid2 = uniqueId('elem');
|
||
db.createElement({ id: eid2, drawing_id: drawingId, layer_id: layerId, type: 'circle' });
|
||
const elements = db.listElements(drawingId);
|
||
expect(elements.length).toBeGreaterThanOrEqual(2);
|
||
});
|
||
|
||
it('should update an element', () => {
|
||
const eid = uniqueId('elem');
|
||
db.createElement({ id: eid, drawing_id: drawingId, layer_id: layerId, type: 'line' });
|
||
const updated = db.updateElement(eid, { x: 500, y: 600, width: 200 });
|
||
expect(updated).not.toBeNull();
|
||
expect(updated!.x).toBe(500);
|
||
expect(updated!.y).toBe(600);
|
||
expect(updated!.width).toBe(200);
|
||
});
|
||
|
||
it('should delete an element', () => {
|
||
const eid = uniqueId('elem');
|
||
db.createElement({ id: eid, drawing_id: drawingId, layer_id: layerId, type: 'text' });
|
||
const ok = db.deleteElement(eid);
|
||
expect(ok).toBe(true);
|
||
const elements = db.listElements(drawingId);
|
||
expect(elements.find((e) => e.id === eid)).toBeUndefined();
|
||
});
|
||
});
|
||
|
||
// ─── Blocks CRUD ────────────────────────────────────
|
||
|
||
describe('Blocks CRUD', () => {
|
||
let drawingId: string;
|
||
|
||
it('should create and get a block', () => {
|
||
const pid = uniqueId('proj');
|
||
drawingId = uniqueId('draw');
|
||
const bid = uniqueId('block');
|
||
db.createProject({ id: pid, name: 'Block Project' });
|
||
db.createDrawing({ id: drawingId, project_id: pid, name: 'Block Drawing' });
|
||
|
||
const block = db.createBlock({ id: bid, drawing_id: drawingId, name: 'Table Block', category: 'Mobiliar' });
|
||
expect(block.id).toBe(bid);
|
||
expect(block.name).toBe('Table Block');
|
||
expect(block.category).toBe('Mobiliar');
|
||
expect(block.elements_json).toBe('[]');
|
||
});
|
||
|
||
it('should list blocks by drawing', () => {
|
||
const bid2 = uniqueId('block');
|
||
db.createBlock({ id: bid2, drawing_id: drawingId, name: 'Chair Block' });
|
||
const blocks = db.listBlocks(drawingId);
|
||
expect(blocks.length).toBeGreaterThanOrEqual(2);
|
||
});
|
||
|
||
it('should update a block', () => {
|
||
const bid = uniqueId('block');
|
||
db.createBlock({ id: bid, drawing_id: drawingId, name: 'Update Block' });
|
||
const updated = db.updateBlock(bid, { name: 'Updated Block', category: 'Bühne' });
|
||
expect(updated).not.toBeNull();
|
||
expect(updated!.name).toBe('Updated Block');
|
||
expect(updated!.category).toBe('Bühne');
|
||
});
|
||
|
||
it('should delete a block', () => {
|
||
const bid = uniqueId('block');
|
||
db.createBlock({ id: bid, drawing_id: drawingId, name: 'Delete Block' });
|
||
const ok = db.deleteBlock(bid);
|
||
expect(ok).toBe(true);
|
||
const blocks = db.listBlocks(drawingId);
|
||
expect(blocks.find((b) => b.id === bid)).toBeUndefined();
|
||
});
|
||
});
|
||
|
||
// ─── Settings CRUD ──────────────────────────────────
|
||
|
||
describe('Settings CRUD', () => {
|
||
it('should set and get a setting', () => {
|
||
db.setSetting('test-key', 'test-value');
|
||
const setting = db.getSetting('test-key');
|
||
expect(setting).not.toBeNull();
|
||
expect(setting!.key).toBe('test-key');
|
||
expect(setting!.value).toBe('test-value');
|
||
expect(setting!.updated_at).toBeDefined();
|
||
});
|
||
|
||
it('should return null for non-existent setting', () => {
|
||
expect(db.getSetting('non-existent-key')).toBeNull();
|
||
});
|
||
|
||
it('should upsert (update existing setting)', () => {
|
||
db.setSetting('upsert-key', 'initial');
|
||
db.setSetting('upsert-key', 'updated');
|
||
const setting = db.getSetting('upsert-key');
|
||
expect(setting).not.toBeNull();
|
||
expect(setting!.value).toBe('updated');
|
||
});
|
||
});
|
||
});
|