test: add phase security/smoke/e2e coverage and tighten MCP contract handling

This commit is contained in:
newblacc
2026-03-30 15:52:24 +02:00
parent a605a15282
commit f1db126566
22 changed files with 1986 additions and 136 deletions
+17 -1
View File
@@ -33,7 +33,8 @@ describe('cleanElementForExcalidraw', () => {
expect(cleaned).not.toHaveProperty('createdAt');
expect(cleaned).not.toHaveProperty('updatedAt');
expect(cleaned).not.toHaveProperty('version');
// version is kept — it is the Excalidraw element version, not a DB field
expect(cleaned).toHaveProperty('version', 3);
expect(cleaned).not.toHaveProperty('syncedAt');
expect(cleaned).not.toHaveProperty('source');
expect(cleaned).not.toHaveProperty('syncTimestamp');
@@ -219,6 +220,21 @@ describe('computeElementHash', () => {
const hash = computeElementHash([{ id: 'x', version: 1 }]);
expect(hash.startsWith('1')).toBe(true);
});
it('is order-stable for same id/version set', () => {
const a = [
{ id: 'a', version: 1 },
{ id: 'b', version: 3 },
{ id: 'c', version: 2 },
];
const b = [
{ id: 'c', version: 2 },
{ id: 'a', version: 1 },
{ id: 'b', version: 3 },
];
expect(computeElementHash(a)).toBe(computeElementHash(b));
});
});
// ─── isImageElement ─────────────────────────────────────────
+90
View File
@@ -0,0 +1,90 @@
import { describe, it, expect, vi } from 'vitest';
import {
expandLabelsToNative,
prepareElementsForScene,
} from '../../frontend/src/utils/scenePreparation.js';
import type { ServerElement } from '../../frontend/src/utils/elementHelpers.js';
describe('expandLabelsToNative', () => {
it('creates a native bound text element at container center', () => {
const input = [{
id: 'box-1',
type: 'rectangle',
x: 100,
y: 200,
width: 300,
height: 120,
label: { text: 'Title' },
boundElements: [{ id: 'arrow-1', type: 'arrow' }],
}];
const out = expandLabelsToNative(input as any[]);
expect(out).toHaveLength(2);
const container = out.find((el) => el.id === 'box-1') as any;
const text = out.find((el) => el.id === 'box-1_label') as any;
expect(container.boundElements).toEqual([
{ id: 'arrow-1', type: 'arrow' },
{ id: 'box-1_label', type: 'text' },
]);
expect(text.containerId).toBe('box-1');
expect(text.text).toBe('Title');
expect(text.x).toBe(230);
expect(text.y).toBe(250);
});
it('passes through elements with no label.text unchanged', () => {
const a = { id: 'a', type: 'rectangle', x: 0, y: 0, width: 100, height: 40 };
const b = { id: 'b', type: 'text', x: 10, y: 10, text: 'Hello' };
const out = expandLabelsToNative([a, b] as any[]);
expect(out).toEqual([a, b]);
});
});
describe('prepareElementsForScene', () => {
it('routes native browser-synced elements without conversion', () => {
const native = {
id: 'native-1',
type: 'rectangle',
x: 0,
y: 0,
width: 100,
height: 50,
seed: 123,
versionNonce: 456,
version: 1,
} as any as ServerElement;
const converter = vi.fn((elements: readonly any[]) =>
elements.map((el) => ({ ...el, converted: true }))
);
const out = prepareElementsForScene([native], converter as any);
expect(converter).not.toHaveBeenCalled();
expect(out).toHaveLength(1);
expect((out[0] as any).id).toBe('native-1');
expect((out[0] as any).converted).toBeUndefined();
});
it('routes MCP stubs through converter', () => {
const stub = {
id: 'stub-1',
type: 'rectangle',
x: 10,
y: 20,
width: 80,
height: 40,
label: { text: 'Stub' },
version: 1,
} as ServerElement;
const converter = vi.fn((elements: readonly any[]) =>
elements.map((el) => ({ ...el, converted: true }))
);
const out = prepareElementsForScene([stub], converter as any);
expect(converter).toHaveBeenCalledTimes(1);
expect(out.some((el) => (el as any).id === 'stub-1' && (el as any).converted)).toBe(true);
});
});
+3 -2
View File
@@ -12,7 +12,7 @@ import {
// ─── cleanElementForExcalidraw comprehensive ────────────────
describe('cleanElementForExcalidraw - comprehensive', () => {
it('strips all server-only metadata fields', () => {
it('strips server-only metadata fields but preserves Excalidraw version', () => {
const serverEl = {
id: 'el-1',
type: 'rectangle',
@@ -31,7 +31,8 @@ describe('cleanElementForExcalidraw - comprehensive', () => {
const cleaned = cleanElementForExcalidraw(serverEl);
expect(cleaned).not.toHaveProperty('createdAt');
expect(cleaned).not.toHaveProperty('updatedAt');
expect(cleaned).not.toHaveProperty('version');
// version is kept — it is the Excalidraw element version, not a DB field
expect(cleaned).toHaveProperty('version', 1);
expect(cleaned).not.toHaveProperty('syncedAt');
expect(cleaned).not.toHaveProperty('source');
expect(cleaned).not.toHaveProperty('syncTimestamp');