Files
Genarrative/apps/ai-game-creator-shell/tests/resourceDependencyGraphModel.test.ts
T
menghao ba996aad80
Project CI / Frontend tests (pull_request) Failing after 21s
Project CI / Repository checks (pull_request) Successful in 1m1s
Project CI / Backend tests (pull_request) Successful in 3m54s
Project CI / Native shell tests (pull_request) Successful in 11m49s
完成资源依赖排列阶段五
计算资源引用 SCC 与任务深度下限
拆分 producer assignment 和布局深度合同
补齐依赖布局回归测试
同步工作台 PRD、技术方案与项目记忆
2026-08-03 17:51:59 +08:00

196 lines
5.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
normalizeProjectResourceGraph,
projectResourceGraphNeighbors,
type ProjectResourceGraphReadModel,
} from '../src/view/project-development/resourceDependencyGraphModel';
function readModel(
overrides: Partial<ProjectResourceGraphReadModel> = {},
): ProjectResourceGraphReadModel {
return {
resourceIds: [],
referenceEdges: [],
taskFlows: [],
connectionIndex: [],
producerAssignments: [],
dependencyDepths: [],
unresolvedReferenceResourceIds: [],
cyclicResourceIds: [],
cyclicTaskIds: [],
producerMappingTruncated: false,
...overrides,
};
}
describe('resource dependency graph model', () => {
it('normalizes the Rust read model and filters stale resource endpoints', () => {
const graph = normalizeProjectResourceGraph(
readModel({
resourceIds: ['asset:source', 'asset:target'],
referenceEdges: [
{
id: 'valid-reference',
kind: 'asset-reference',
sourceResourceId: 'asset:source',
targetResourceId: 'asset:target',
cyclic: false,
},
{
id: 'ghost-reference',
kind: 'asset-reference',
sourceResourceId: 'asset:deleted',
targetResourceId: 'asset:target',
cyclic: false,
},
],
taskFlows: [
{
id: 'valid-flow',
kind: 'task-flow',
sourceTaskId: 'source-task',
targetTaskId: 'target-task',
sourceResourceIds: ['asset:source', 'asset:deleted'],
targetResourceIds: ['asset:target'],
cyclic: false,
},
{
id: 'ghost-flow',
kind: 'task-flow',
sourceTaskId: 'deleted-task',
targetTaskId: 'target-task',
sourceResourceIds: ['asset:deleted'],
targetResourceIds: ['asset:target'],
cyclic: false,
},
],
cyclicResourceIds: ['asset:source', 'asset:deleted'],
}),
);
expect(graph.referenceEdges.map((edge) => edge.id)).toEqual([
'valid-reference',
]);
expect(graph.taskFlows).toEqual([
expect.objectContaining({
id: 'valid-flow',
sourceResourceIds: ['asset:source'],
targetResourceIds: ['asset:target'],
}),
]);
expect(graph.cyclicResourceIds).toEqual(new Set(['asset:source']));
});
it('queries direct reference and aggregated task-flow neighbors from the bounded index', () => {
const graph = normalizeProjectResourceGraph(
readModel({
resourceIds: ['source:a', 'source:b', 'target:a', 'target:b'],
referenceEdges: [
{
id: 'reference:a',
kind: 'asset-reference',
sourceResourceId: 'source:a',
targetResourceId: 'target:a',
cyclic: false,
},
],
taskFlows: [
{
id: 'flow:a-b',
kind: 'task-flow',
sourceTaskId: 'task:a',
targetTaskId: 'task:b',
sourceResourceIds: ['source:a', 'source:b'],
targetResourceIds: ['target:a', 'target:b'],
cyclic: false,
},
],
connectionIndex: [
{
resourceId: 'target:a',
upstreamReferenceResourceIds: ['source:a'],
downstreamReferenceResourceIds: [],
referenceEdgeIds: ['reference:a'],
taskFlowIds: ['flow:a-b'],
},
],
}),
);
const neighbors = projectResourceGraphNeighbors(graph, 'target:a');
expect(neighbors.upstreamResourceIds).toEqual(
new Set(['source:a', 'source:b']),
);
expect(neighbors.downstreamResourceIds).toEqual(new Set());
expect(neighbors.connectedEdgeIds).toEqual(
new Set(['reference:a', 'flow:a-b']),
);
});
it('keeps real producer assignments and audit truncation metadata', () => {
const graph = normalizeProjectResourceGraph(
readModel({
resourceIds: ['asset:spec', 'asset:ui', 'asset:derived'],
producerAssignments: [
{
resourceId: 'asset:spec',
taskId: 'art-director',
},
{
resourceId: 'asset:ui',
taskId: 'design-foundation',
},
{
resourceId: 'asset:deleted',
taskId: 'task-1',
},
],
dependencyDepths: [
{
resourceId: 'asset:spec',
dependencyDepth: 0,
},
{
resourceId: 'asset:ui',
dependencyDepth: 1,
},
{
resourceId: 'asset:deleted',
dependencyDepth: 99,
},
{
resourceId: 'asset:ui',
dependencyDepth: 0,
},
{
resourceId: 'asset:derived',
dependencyDepth: 2,
},
],
producerMappingTruncated: true,
}),
);
expect(graph.producerTaskIdByResourceId).toEqual(
new Map([
['asset:spec', 'art-director'],
['asset:ui', 'design-foundation'],
]),
);
expect(graph.dependencyDepthByResourceId).toEqual(
new Map([
['asset:spec', 0],
['asset:ui', 1],
['asset:derived', 2],
]),
);
expect(graph.producerMappingTruncated).toBe(true);
expect(projectResourceGraphNeighbors(graph, 'asset:deleted')).toEqual({
upstreamResourceIds: new Set(),
downstreamResourceIds: new Set(),
connectedEdgeIds: new Set(),
});
});
});