49 lines
4.4 KiB
JavaScript
49 lines
4.4 KiB
JavaScript
/**
|
||
* 画布入口的 canonical-first 选择测试。已迁移项目直接进入 headless,只有明确迁移需求或未部署接口可走旧快照。
|
||
* 权限失败不能降级成旧数据读取;入口只保留已验证迁移材料,不自动发迁移请求。
|
||
*/
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { loadCanvasEntry } from '../../src/lib/canvas-entry.ts';
|
||
import { createRegistry, builtinComponents } from '../../src/lib/canvas-core/index.ts';
|
||
// UUID 工厂区分 board、workspace、profile,供入口解析和 service 身份验证。
|
||
const id = n => `10000000-0000-4000-8000-${String(n).padStart(12, '0')}`;
|
||
// 模拟已解析 board,workspaceRef/boardRef 以明确 ID 输入,避免把 slug 解析与模式选择混成同一个断言。
|
||
const board = { id: id(1), workspace_id: id(2), kind: 'board', title: 'Board', version: 1 };
|
||
// 当前账户与内置 registry,transport 工厂必须收到该 profile 身份。
|
||
const profile = { id: id(3) }, registry = createRegistry(builtinComponents());
|
||
// request 提供 profile/board/旧 canvas,transport 返回注入 canonical 响应;所有路径记录到 calls 检查不应发生的 fallback。
|
||
function fixture(response) {
|
||
const calls = [], request = async path => { calls.push(path); if (path === 'profile') return profile; if (path.endsWith('/canvas')) return { version: 1, document: { schema_version: 2, nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } } }; return board; };
|
||
return { calls, options: { request, workspaceRef: board.workspace_id, boardRef: board.id, registry, transport: accountId => { assert.equal(accountId, profile.id); return async input => { calls.push(input.path); return response; }; } } };
|
||
}
|
||
|
||
// canonical 完整模型已可用时只进入 headless,不能再读取旧 canvas 快照制造第二份文档来源。
|
||
test('canonical-first entry never reads a legacy snapshot for an adopted project', async () => {
|
||
const f = fixture({ status: 200, body: { model: { documentFormatVersion: 1, projectId: board.id, complete: true, canvases: [{ id: board.id, parent: null }], elements: [], tombstones: {}, versions: { [board.id]: 1 }, removedCanvasIds: [] } } });
|
||
const result = await loadCanvasEntry(f.options); assert.equal(result.mode, 'headless'); assert.equal(result.model.document.projectId, board.id);
|
||
assert.equal(f.calls.some(path => path.endsWith('/canvas')), false);
|
||
});
|
||
|
||
// 404 未部署或合法 migration_required 允许读取旧快照;403 权限失败直接报错,绝不退回 legacy。
|
||
test('explicit migration requirement or missing deployment keeps legacy reads; permission failure does not fall back', async () => {
|
||
for (const response of [{ status: 404, body: { error: 'not_found' } }, { status: 409, body: { error: 'canonical_migration_required', root_id: board.id, document_format_version: 1, source_versions: { [board.id]: 1 } } }]) {
|
||
const f = fixture(response); assert.equal((await loadCanvasEntry(f.options)).mode, 'legacy'); assert.equal(f.calls.filter(path => path.endsWith('/canvas')).length, 1);
|
||
}
|
||
const f = fixture({ status: 403, body: { error: 'forbidden' } });
|
||
await assert.rejects(() => loadCanvasEntry(f.options), /forbidden/); assert.equal(f.calls.some(path => path.endsWith('/canvas')), false);
|
||
});
|
||
|
||
// legacy 入口保留验证过的 root/source 版本向量及 profile/snapshot,不发 migrate;404 未部署则不提供虚构迁移材料。
|
||
// 固定响应只检验入口如何选择模式、保留材料和安排请求,不执行模型迁移或旧画布写入。
|
||
test('legacy entry retains verified migration root and the full source vector without offering migration for an unavailable API',async()=>{
|
||
const child=id(4),f=fixture({status:409,body:{error:'canonical_migration_required',root_id:board.id,document_format_version:1,source_versions:{[board.id]:3,[child]:7}}});
|
||
const result=await loadCanvasEntry(f.options);
|
||
assert.equal(result.mode,'legacy');assert.equal(result.migration.rootId,board.id);
|
||
assert.deepEqual([...result.migration.sourceVersions],[[board.id,3],[child,7]]);assert.equal(result.migration.sourceVersions.set,undefined);
|
||
assert.equal(result.profile.id,profile.id);assert.ok(result.snapshot);
|
||
assert.equal(f.calls.some(path=>path.endsWith('/migrate')),false);
|
||
const unavailable=await loadCanvasEntry(fixture({status:404,body:{error:'not_found'}}).options);
|
||
assert.equal(unavailable.mode,'legacy');assert.equal(unavailable.migration,undefined);
|
||
});
|