Files
automaker/libs/dependency-resolver/tests/resolver.test.ts
SuperComboGamer 584f5a3426 Merge main into massive-terminal-upgrade
Resolves merge conflicts:
- apps/server/src/routes/terminal/common.ts: Keep randomBytes import, use @automaker/utils for createLogger
- apps/ui/eslint.config.mjs: Use main's explicit globals list with XMLHttpRequest and MediaQueryListEvent additions
- apps/ui/src/components/views/terminal-view.tsx: Keep our terminal improvements (killAllSessions, beforeunload, better error handling)
- apps/ui/src/config/terminal-themes.ts: Keep our search highlight colors for all themes
- apps/ui/src/store/app-store.ts: Keep our terminal settings persistence improvements (merge function)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 20:27:44 -05:00

352 lines
12 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
resolveDependencies,
areDependenciesSatisfied,
getBlockingDependencies,
} from '../src/resolver';
import type { Feature } from '@automaker/types';
// Helper to create test features
function createFeature(
id: string,
options: {
dependencies?: string[];
status?: string;
priority?: number;
} = {}
): Feature {
return {
id,
category: 'test',
description: `Feature ${id}`,
dependencies: options.dependencies,
status: options.status || 'pending',
priority: options.priority,
};
}
describe('resolver.ts', () => {
describe('resolveDependencies', () => {
it('should handle features with no dependencies', () => {
const features = [createFeature('A'), createFeature('B'), createFeature('C')];
const result = resolveDependencies(features);
expect(result.orderedFeatures).toHaveLength(3);
expect(result.circularDependencies).toEqual([]);
expect(result.missingDependencies.size).toBe(0);
expect(result.blockedFeatures.size).toBe(0);
});
it('should order features with linear dependencies', () => {
const features = [
createFeature('C', { dependencies: ['B'] }),
createFeature('A'),
createFeature('B', { dependencies: ['A'] }),
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map((f) => f.id);
expect(ids.indexOf('A')).toBeLessThan(ids.indexOf('B'));
expect(ids.indexOf('B')).toBeLessThan(ids.indexOf('C'));
expect(result.circularDependencies).toEqual([]);
});
it('should respect priority within same dependency level', () => {
const features = [
createFeature('Low', { priority: 3 }),
createFeature('High', { priority: 1 }),
createFeature('Medium', { priority: 2 }),
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map((f) => f.id);
expect(ids).toEqual(['High', 'Medium', 'Low']);
});
it('should use default priority 2 when not specified', () => {
const features = [
createFeature('NoPriority'),
createFeature('HighPriority', { priority: 1 }),
createFeature('LowPriority', { priority: 3 }),
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map((f) => f.id);
expect(ids.indexOf('HighPriority')).toBeLessThan(ids.indexOf('NoPriority'));
expect(ids.indexOf('NoPriority')).toBeLessThan(ids.indexOf('LowPriority'));
});
it('should respect dependencies over priority', () => {
const features = [
createFeature('B', { dependencies: ['A'], priority: 1 }), // High priority but depends on A
createFeature('A', { priority: 3 }), // Low priority but no dependencies
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map((f) => f.id);
expect(ids.indexOf('A')).toBeLessThan(ids.indexOf('B'));
});
it('should detect circular dependencies (simple cycle)', () => {
const features = [
createFeature('A', { dependencies: ['B'] }),
createFeature('B', { dependencies: ['A'] }),
];
const result = resolveDependencies(features);
expect(result.circularDependencies).toHaveLength(1);
expect(result.circularDependencies[0]).toContain('A');
expect(result.circularDependencies[0]).toContain('B');
expect(result.orderedFeatures).toHaveLength(2); // All features still included
});
it('should detect circular dependencies (3-way cycle)', () => {
const features = [
createFeature('A', { dependencies: ['C'] }),
createFeature('B', { dependencies: ['A'] }),
createFeature('C', { dependencies: ['B'] }),
];
const result = resolveDependencies(features);
expect(result.circularDependencies.length).toBeGreaterThan(0);
const allCycleIds = result.circularDependencies.flat();
expect(allCycleIds).toContain('A');
expect(allCycleIds).toContain('B');
expect(allCycleIds).toContain('C');
});
it('should detect missing dependencies', () => {
const features = [createFeature('A', { dependencies: ['NonExistent'] }), createFeature('B')];
const result = resolveDependencies(features);
expect(result.missingDependencies.has('A')).toBe(true);
expect(result.missingDependencies.get('A')).toContain('NonExistent');
});
it('should detect blocked features (incomplete dependencies)', () => {
const features = [
createFeature('A', { status: 'pending' }),
createFeature('B', { dependencies: ['A'], status: 'pending' }),
];
const result = resolveDependencies(features);
expect(result.blockedFeatures.has('B')).toBe(true);
expect(result.blockedFeatures.get('B')).toContain('A');
});
it('should not mark features as blocked if dependencies are completed', () => {
const features = [
createFeature('A', { status: 'completed' }),
createFeature('B', { dependencies: ['A'], status: 'pending' }),
];
const result = resolveDependencies(features);
expect(result.blockedFeatures.has('B')).toBe(false);
});
it('should not mark features as blocked if dependencies are verified', () => {
const features = [
createFeature('A', { status: 'verified' }),
createFeature('B', { dependencies: ['A'], status: 'pending' }),
];
const result = resolveDependencies(features);
expect(result.blockedFeatures.has('B')).toBe(false);
});
it('should handle complex dependency graph', () => {
const features = [
createFeature('E', { dependencies: ['C', 'D'] }),
createFeature('D', { dependencies: ['B'] }),
createFeature('C', { dependencies: ['A', 'B'] }),
createFeature('B'),
createFeature('A'),
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map((f) => f.id);
// A and B have no dependencies - can be first or second
expect(ids.indexOf('A')).toBeLessThan(ids.indexOf('C'));
expect(ids.indexOf('B')).toBeLessThan(ids.indexOf('C'));
expect(ids.indexOf('B')).toBeLessThan(ids.indexOf('D'));
// C depends on A and B
expect(ids.indexOf('C')).toBeLessThan(ids.indexOf('E'));
// D depends on B
expect(ids.indexOf('D')).toBeLessThan(ids.indexOf('E'));
expect(result.circularDependencies).toEqual([]);
});
it('should handle multiple missing dependencies', () => {
const features = [createFeature('A', { dependencies: ['X', 'Y', 'Z'] })];
const result = resolveDependencies(features);
expect(result.missingDependencies.get('A')).toEqual(['X', 'Y', 'Z']);
});
it('should handle empty feature list', () => {
const result = resolveDependencies([]);
expect(result.orderedFeatures).toEqual([]);
expect(result.circularDependencies).toEqual([]);
expect(result.missingDependencies.size).toBe(0);
expect(result.blockedFeatures.size).toBe(0);
});
it('should handle features with both missing and existing dependencies', () => {
const features = [
createFeature('A'),
createFeature('B', { dependencies: ['A', 'NonExistent'] }),
];
const result = resolveDependencies(features);
expect(result.missingDependencies.get('B')).toContain('NonExistent');
const ids = result.orderedFeatures.map((f) => f.id);
expect(ids.indexOf('A')).toBeLessThan(ids.indexOf('B'));
});
});
describe('areDependenciesSatisfied', () => {
it('should return true for feature with no dependencies', () => {
const feature = createFeature('A');
const allFeatures = [feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(true);
});
it('should return true for feature with empty dependencies array', () => {
const feature = createFeature('A', { dependencies: [] });
const allFeatures = [feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(true);
});
it('should return true when all dependencies are completed', () => {
const dep = createFeature('Dep', { status: 'completed' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(true);
});
it('should return true when all dependencies are verified', () => {
const dep = createFeature('Dep', { status: 'verified' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(true);
});
it('should return false when any dependency is pending', () => {
const dep = createFeature('Dep', { status: 'pending' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(false);
});
it('should return false when any dependency is running', () => {
const dep = createFeature('Dep', { status: 'running' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(false);
});
it('should return false when dependency is missing', () => {
const feature = createFeature('A', { dependencies: ['NonExistent'] });
const allFeatures = [feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(false);
});
it('should check all dependencies', () => {
const dep1 = createFeature('Dep1', { status: 'completed' });
const dep2 = createFeature('Dep2', { status: 'pending' });
const feature = createFeature('A', { dependencies: ['Dep1', 'Dep2'] });
const allFeatures = [dep1, dep2, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(false);
});
});
describe('getBlockingDependencies', () => {
it('should return empty array for feature with no dependencies', () => {
const feature = createFeature('A');
const allFeatures = [feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual([]);
});
it('should return empty array when all dependencies are completed', () => {
const dep = createFeature('Dep', { status: 'completed' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual([]);
});
it('should return empty array when all dependencies are verified', () => {
const dep = createFeature('Dep', { status: 'verified' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual([]);
});
it('should return pending dependencies', () => {
const dep = createFeature('Dep', { status: 'pending' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual(['Dep']);
});
it('should return running dependencies', () => {
const dep = createFeature('Dep', { status: 'running' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual(['Dep']);
});
it('should return failed dependencies', () => {
const dep = createFeature('Dep', { status: 'failed' });
const feature = createFeature('A', { dependencies: ['Dep'] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual(['Dep']);
});
it('should return all incomplete dependencies', () => {
const dep1 = createFeature('Dep1', { status: 'pending' });
const dep2 = createFeature('Dep2', { status: 'completed' });
const dep3 = createFeature('Dep3', { status: 'running' });
const feature = createFeature('A', { dependencies: ['Dep1', 'Dep2', 'Dep3'] });
const allFeatures = [dep1, dep2, dep3, feature];
const blocking = getBlockingDependencies(feature, allFeatures);
expect(blocking).toContain('Dep1');
expect(blocking).toContain('Dep3');
expect(blocking).not.toContain('Dep2');
});
});
});