Assessor_2.0/tests/features/category-branch.test.ts

85 lines
4.3 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { FILTERED_GRAPH_SELECTION_ID, graphSelectionNodeIds, graphSelectionUnionNodeIds, relationBranchNodeIds } from '../../src/features/mitre-graph/category-branch';
import type { ObsidianGraphModel } from '../../src/features/mitre-graph/obsidian-graph';
const graph: ObsidianGraphModel = {
nodes: [
{ id: 'category:a', kind: 'category', label: 'A', x: 0, y: 0, radius: 8 },
{ id: 'category:b', kind: 'category', label: 'B', x: 0, y: 0, radius: 8 },
{ id: 'finding:a', kind: 'finding', label: 'A finding', x: 0, y: 0, radius: 8 },
{ id: 'finding:b', kind: 'finding', label: 'B finding', x: 0, y: 0, radius: 8 },
{ id: 'finding:c', kind: 'finding', label: 'Direct tactic finding', x: 0, y: 0, radius: 8 },
{ id: 'technique:shared', kind: 'technique', label: 'Shared', x: 0, y: 0, radius: 8 },
{ id: 'target:a', kind: 'target', label: 'Target A', x: 0, y: 0, radius: 8 },
{ id: 'tactic:execution', kind: 'tactic', label: 'Execution', x: 0, y: 0, radius: 8 }
],
edges: [
{ id: 'a-category', source: 'finding:a', target: 'category:a', kind: 'category' },
{ id: 'b-category', source: 'finding:b', target: 'category:b', kind: 'category' },
{ id: 'a-technique', source: 'finding:a', target: 'technique:shared', kind: 'technique' },
{ id: 'b-technique', source: 'finding:b', target: 'technique:shared', kind: 'technique' },
{ id: 'a-target', source: 'finding:a', target: 'target:a', kind: 'target' },
{ id: 'technique-tactic', source: 'technique:shared', target: 'tactic:execution', kind: 'technique' },
{ id: 'direct-tactic', source: 'finding:c', target: 'tactic:execution', kind: 'tactic' }
]
};
describe('category graph branch', () => {
it('includes the selected category branch without escaping through shared MITRE nodes', () => {
expect(relationBranchNodeIds(graph, 'category:a')).toEqual([
'category:a', 'finding:a', 'technique:shared', 'target:a', 'tactic:execution'
]);
expect(relationBranchNodeIds(graph, 'category:a')).not.toContain('finding:b');
expect(relationBranchNodeIds(graph, 'category:a')).not.toContain('category:b');
});
it('supports shared MITRE technique and tactic roots without escaping recursively', () => {
expect(relationBranchNodeIds(graph, 'technique:shared')).toEqual([
'category:a', 'category:b', 'finding:a', 'finding:b', 'technique:shared', 'target:a', 'tactic:execution'
]);
expect(relationBranchNodeIds(graph, 'tactic:execution')).toEqual([
'category:a', 'category:b', 'finding:a', 'finding:b', 'finding:c', 'technique:shared', 'target:a', 'tactic:execution'
]);
});
it('keeps every same-kind endpoint attached to a matched finding', () => {
const multiDefenseGraph: ObsidianGraphModel = {
nodes: [
{ id: 'finding:a', kind: 'finding', label: 'Finding', x: 0, y: 0, radius: 8 },
{ id: 'defense:a', kind: 'defense', label: 'Defense A', x: 0, y: 0, radius: 8 },
{ id: 'defense:b', kind: 'defense', label: 'Defense B', x: 0, y: 0, radius: 8 },
{ id: 'target:a', kind: 'target', label: 'Target', x: 0, y: 0, radius: 8 }
],
edges: [
{ id: 'defense-a', source: 'finding:a', target: 'defense:a', kind: 'defense' },
{ id: 'defense-b', source: 'finding:a', target: 'defense:b', kind: 'defense' },
{ id: 'target-a', source: 'finding:a', target: 'target:a', kind: 'target' }
]
};
expect(relationBranchNodeIds(multiDefenseGraph, 'defense:a')).toEqual([
'finding:a', 'defense:a', 'defense:b', 'target:a'
]);
});
it('ignores finding roots', () => {
expect(relationBranchNodeIds(graph, 'finding:a')).toEqual([]);
});
it('selects every node for filters without a dedicated relation node', () => {
expect(graphSelectionNodeIds(graph, FILTERED_GRAPH_SELECTION_ID)).toEqual(graph.nodes.map(node => node.id));
});
it('combines multiple selected relation branches for extraction', () => {
expect(graphSelectionUnionNodeIds(graph, ['category:a', 'category:b'])).toEqual([
'category:a', 'category:b', 'finding:a', 'finding:b', 'technique:shared', 'target:a', 'tactic:execution'
]);
});
it('keeps a selected finding and its direct relations extractable', () => {
expect(graphSelectionUnionNodeIds(graph, ['finding:a'])).toEqual([
'category:a', 'finding:a', 'technique:shared', 'target:a'
]);
});
});