62e0fe94ef
完成资源卡按类型和按依赖分类展现的功能 Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/129 Co-authored-by: menghao <mh18530625731@163.com> Co-committed-by: menghao <mh18530625731@163.com>
263 lines
7.7 KiB
TypeScript
263 lines
7.7 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('fails closed only for producer-derived data when the audit tail is truncated', () => {
|
|
const graph = normalizeProjectResourceGraph(
|
|
readModel({
|
|
resourceIds: [
|
|
'asset:spec',
|
|
'asset:ui',
|
|
'asset:derived',
|
|
'asset:negative',
|
|
'asset:fractional',
|
|
'asset:unsafe',
|
|
],
|
|
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,
|
|
},
|
|
{
|
|
resourceId: 'asset:negative',
|
|
dependencyDepth: -1,
|
|
},
|
|
{
|
|
resourceId: 'asset:fractional',
|
|
dependencyDepth: 1.5,
|
|
},
|
|
{
|
|
resourceId: 'asset:unsafe',
|
|
dependencyDepth: Number.MAX_SAFE_INTEGER + 1,
|
|
},
|
|
],
|
|
taskFlows: [
|
|
{
|
|
id: 'flow:spec-ui',
|
|
kind: 'task-flow',
|
|
sourceTaskId: 'art-director',
|
|
targetTaskId: 'design-foundation',
|
|
sourceResourceIds: ['asset:spec'],
|
|
targetResourceIds: ['asset:ui'],
|
|
cyclic: false,
|
|
},
|
|
],
|
|
referenceEdges: [
|
|
{
|
|
id: 'reference:spec-ui',
|
|
kind: 'asset-reference',
|
|
sourceResourceId: 'asset:spec',
|
|
targetResourceId: 'asset:ui',
|
|
cyclic: false,
|
|
},
|
|
],
|
|
connectionIndex: [
|
|
{
|
|
resourceId: 'asset:spec',
|
|
upstreamReferenceResourceIds: [],
|
|
downstreamReferenceResourceIds: ['asset:ui'],
|
|
referenceEdgeIds: ['reference:spec-ui'],
|
|
taskFlowIds: ['flow:spec-ui'],
|
|
},
|
|
{
|
|
resourceId: 'asset:ui',
|
|
upstreamReferenceResourceIds: ['asset:spec'],
|
|
downstreamReferenceResourceIds: [],
|
|
referenceEdgeIds: ['reference:spec-ui'],
|
|
taskFlowIds: ['flow:spec-ui'],
|
|
},
|
|
],
|
|
unresolvedReferenceResourceIds: ['external:missing'],
|
|
cyclicResourceIds: ['asset:ui'],
|
|
cyclicTaskIds: ['art-director'],
|
|
producerMappingTruncated: true,
|
|
}),
|
|
);
|
|
|
|
expect(graph.producerTaskIdByResourceId).toEqual(new Map());
|
|
expect(graph.dependencyDepthByResourceId).toEqual(
|
|
new Map([
|
|
['asset:spec', 0],
|
|
['asset:ui', 1],
|
|
['asset:derived', 2],
|
|
]),
|
|
);
|
|
expect(graph.taskFlows).toEqual([]);
|
|
expect(graph.cyclicTaskIds).toEqual(new Set());
|
|
expect(graph.referenceEdges.map((edge) => edge.id)).toEqual([
|
|
'reference:spec-ui',
|
|
]);
|
|
expect(graph.unresolvedReferenceResourceIds).toEqual([
|
|
'external:missing',
|
|
]);
|
|
expect(graph.cyclicResourceIds).toEqual(new Set(['asset:ui']));
|
|
expect(graph.producerMappingTruncated).toBe(true);
|
|
expect(projectResourceGraphNeighbors(graph, 'asset:ui')).toEqual({
|
|
upstreamResourceIds: new Set(['asset:spec']),
|
|
downstreamResourceIds: new Set(),
|
|
connectedEdgeIds: new Set(['reference:spec-ui']),
|
|
});
|
|
expect(projectResourceGraphNeighbors(graph, 'asset:deleted')).toEqual({
|
|
upstreamResourceIds: new Set(),
|
|
downstreamResourceIds: new Set(),
|
|
connectedEdgeIds: new Set(),
|
|
});
|
|
});
|
|
});
|