mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
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>
352 lines
12 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|