fix: replace hardcoded localhost:3001 with relative API URL + add canvas mock and integration test

- 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)
This commit is contained in:
2026-06-26 17:48:24 +02:00
parent 89b91a1050
commit 1bcfaffdd4
22 changed files with 4867 additions and 7 deletions
+129
View File
@@ -0,0 +1,129 @@
/**
* Settings API Tests Key/Value settings (Get / Put / Upsert)
*/
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('Settings API', () => {
let app: FastifyInstance;
let db: SqliteAdapter;
beforeAll(async () => {
db = new SqliteAdapter(':memory:');
await db.init();
app = await createServer({ db, port: 0 });
await app.ready();
});
afterAll(async () => {
await app.close();
db.close();
});
// ─── Get (missing key → 404) ─────────────────────────
describe('GET /api/settings/:key', () => {
it('should return 404 for non-existent setting', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/settings/non-existent-key',
});
expect(response.statusCode).toBe(404);
const body = JSON.parse(response.body);
expect(body.error).toContain('Setting not found');
});
it('should return a setting that was previously set', async () => {
// First set a value
await app.inject({
method: 'PUT',
url: '/api/settings/test-key',
payload: { value: 'test-value' },
});
const response = await app.inject({
method: 'GET',
url: '/api/settings/test-key',
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.key).toBe('test-key');
expect(body.value).toBe('test-value');
expect(body.updated_at).toBeDefined();
});
});
// ─── Put (upsert) ───────────────────────────────────
describe('PUT /api/settings/:key', () => {
it('should create a new setting', async () => {
const response = await app.inject({
method: 'PUT',
url: '/api/settings/new-setting',
payload: { value: 'new-value' },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.key).toBe('new-setting');
expect(body.value).toBe('new-value');
expect(body.updated_at).toBeDefined();
});
it('should update an existing setting (upsert)', async () => {
// Create initial
await app.inject({
method: 'PUT',
url: '/api/settings/upsert-key',
payload: { value: 'initial' },
});
// Update it
const response = await app.inject({
method: 'PUT',
url: '/api/settings/upsert-key',
payload: { value: 'updated' },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.key).toBe('upsert-key');
expect(body.value).toBe('updated');
// Verify via GET
const getRes = await app.inject({
method: 'GET',
url: '/api/settings/upsert-key',
});
const getBody = JSON.parse(getRes.body);
expect(getBody.value).toBe('updated');
});
it('should reject setting without value', async () => {
const response = await app.inject({
method: 'PUT',
url: '/api/settings/no-value-key',
payload: {},
});
const body = JSON.parse(response.body);
expect(body.error).toContain('Value is required');
});
it('should handle complex JSON values', async () => {
const complexValue = JSON.stringify({ nested: { object: true }, array: [1, 2, 3] });
const response = await app.inject({
method: 'PUT',
url: '/api/settings/complex-config',
payload: { value: complexValue },
});
expect(response.statusCode).toBe(200);
const getRes = await app.inject({
method: 'GET',
url: '/api/settings/complex-config',
});
const body = JSON.parse(getRes.body);
expect(body.value).toBe(complexValue);
});
});
});