Files
excalidraw-mcp-sentinel/tests/backend/project-switch-e2e.test.ts
T
Maxime Roy (new.blacc)andGitHub 1166ea5b3f chore: release v1.0.5 (#10)
feat(projects): project management UI, sync countdown, fix project switching
2026-04-06 13:18:03 +02:00

183 lines
7.5 KiB
TypeScript

/**
* End-to-end tests for project switching.
*
* Exercises the full HTTP stack: create projects → add elements → switch →
* verify elements are isolated per project and survive round-trips.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import request from 'supertest';
import { initDb, closeDb, setActiveTenant } from '../../src/db.js';
import path from 'path';
import os from 'os';
import fs from 'fs';
let dbPath: string;
let app: any;
beforeEach(async () => {
dbPath = path.join(os.tmpdir(), `excalidraw-e2e-project-switch-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
initDb(dbPath);
setActiveTenant('default');
const mod = await import('../../src/server.js');
app = mod.default;
});
afterEach(() => {
closeDb();
for (const suffix of ['', '-wal', '-shm']) {
try { fs.unlinkSync(dbPath + suffix); } catch {}
}
});
function rect(id: string, x = 0, y = 0) {
return { id, type: 'rectangle', x, y, width: 100, height: 60, version: 1 };
}
// ─── E2E: draw in project, switch away, switch back ─────────
describe('E2E: project switch round-trip', () => {
it('draw 2 elements in "dude", switch to default, switch back — elements preserved', async () => {
// 1. Create project "dude"
const createRes = await request(app).post('/api/projects').send({ name: 'dude' });
expect(createRes.status).toBe(201);
const dudeId = createRes.body.project.id;
// Remember default project id
const listBefore = await request(app).get('/api/projects');
const defaultProject = listBefore.body.projects.find((p: any) => p.name === 'Default');
expect(defaultProject).toBeDefined();
const defaultId = defaultProject.id;
// 2. Switch to "dude"
const switchRes = await request(app).put('/api/project/active').send({ projectId: dudeId });
expect(switchRes.status).toBe(200);
// 3. Draw 2 rectangles in "dude"
const r1 = await request(app).post('/api/elements').send(rect('dude-box-1', 10, 10));
const r2 = await request(app).post('/api/elements').send(rect('dude-box-2', 200, 200));
expect(r1.status).toBe(200);
expect(r2.status).toBe(200);
// Verify 2 elements present
const dudeCheck1 = await request(app).get('/api/elements');
expect(dudeCheck1.body.elements.length).toBe(2);
// 4. Switch to "default"
await request(app).put('/api/project/active').send({ projectId: defaultId });
// Default should be empty
const defaultCheck = await request(app).get('/api/elements');
expect(defaultCheck.body.elements.length).toBe(0);
// 5. Switch back to "dude"
await request(app).put('/api/project/active').send({ projectId: dudeId });
// 6. Verify both elements are still there
const dudeCheck2 = await request(app).get('/api/elements');
expect(dudeCheck2.body.elements.length).toBe(2);
const ids = dudeCheck2.body.elements.map((e: any) => e.id);
expect(ids).toContain('dude-box-1');
expect(ids).toContain('dude-box-2');
});
it('multiple switches do not leak elements between projects', async () => {
// Create 3 projects
const pA = await request(app).post('/api/projects').send({ name: 'Alpha' });
const pB = await request(app).post('/api/projects').send({ name: 'Bravo' });
const pC = await request(app).post('/api/projects').send({ name: 'Charlie' });
const aId = pA.body.project.id;
const bId = pB.body.project.id;
const cId = pC.body.project.id;
// Add 1 element to each
await request(app).put('/api/project/active').send({ projectId: aId });
await request(app).post('/api/elements').send(rect('alpha-el'));
await request(app).put('/api/project/active').send({ projectId: bId });
await request(app).post('/api/elements').send(rect('bravo-el'));
await request(app).put('/api/project/active').send({ projectId: cId });
await request(app).post('/api/elements').send(rect('charlie-el'));
// Rapid switching: C → A → B → A → C
await request(app).put('/api/project/active').send({ projectId: aId });
await request(app).put('/api/project/active').send({ projectId: bId });
await request(app).put('/api/project/active').send({ projectId: aId });
await request(app).put('/api/project/active').send({ projectId: cId });
// Verify each project has exactly its own element
await request(app).put('/api/project/active').send({ projectId: aId });
const aElems = await request(app).get('/api/elements');
expect(aElems.body.elements.length).toBe(1);
expect(aElems.body.elements[0].id).toBe('alpha-el');
await request(app).put('/api/project/active').send({ projectId: bId });
const bElems = await request(app).get('/api/elements');
expect(bElems.body.elements.length).toBe(1);
expect(bElems.body.elements[0].id).toBe('bravo-el');
await request(app).put('/api/project/active').send({ projectId: cId });
const cElems = await request(app).get('/api/elements');
expect(cElems.body.elements.length).toBe(1);
expect(cElems.body.elements[0].id).toBe('charlie-el');
});
it('updating an element in one project does not affect another', async () => {
const pX = await request(app).post('/api/projects').send({ name: 'ProjX' });
const xId = pX.body.project.id;
const listRes = await request(app).get('/api/projects');
const defaultId = listRes.body.projects.find((p: any) => p.name === 'Default').id;
// Add element to default
await request(app).put('/api/project/active').send({ projectId: defaultId });
await request(app).post('/api/elements').send(rect('def-rect', 0, 0));
// Add element to ProjX
await request(app).put('/api/project/active').send({ projectId: xId });
await request(app).post('/api/elements').send(rect('x-rect', 0, 0));
// Update element in ProjX
const updateRes = await request(app).put('/api/elements/x-rect').send({ x: 999, y: 999 });
expect(updateRes.status).toBe(200);
expect(updateRes.body.success).toBe(true);
// Verify ProjX has updated coords
const xElems = await request(app).get('/api/elements');
expect(xElems.body.elements).toHaveLength(1);
expect(xElems.body.elements[0].x).toBe(999);
// Verify Default still has original coords
await request(app).put('/api/project/active').send({ projectId: defaultId });
const defElems = await request(app).get('/api/elements');
expect(defElems.body.elements[0].x).toBe(0);
});
it('deleting an element in one project does not affect another', async () => {
const pY = await request(app).post('/api/projects').send({ name: 'ProjY' });
const yId = pY.body.project.id;
const listRes = await request(app).get('/api/projects');
const defaultId = listRes.body.projects.find((p: any) => p.name === 'Default').id;
// Add element to default
await request(app).put('/api/project/active').send({ projectId: defaultId });
await request(app).post('/api/elements').send(rect('def-del', 50, 50));
// Add element to ProjY
await request(app).put('/api/project/active').send({ projectId: yId });
await request(app).post('/api/elements').send(rect('y-del', 50, 50));
// Delete from ProjY
await request(app).delete('/api/elements/y-del');
// ProjY: 0 elements
const yElems = await request(app).get('/api/elements');
expect(yElems.body.elements.length).toBe(0);
// Default: still has its element
await request(app).put('/api/project/active').send({ projectId: defaultId });
const defElems = await request(app).get('/api/elements');
expect(defElems.body.elements.length).toBe(1);
expect(defElems.body.elements[0].id).toBe('def-del');
});
});