alnilam-cli
Version:
Git-native AI career coach that converts multi-year ambitions into weekly execution
326 lines (325 loc) • 13.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// Mock the API module before importing
jest.mock('../api.js', () => ({
addEvidence: jest.fn(),
}));
const api_js_1 = require("../api.js");
describe('Evidence Management', () => {
beforeEach(() => {
jest.clearAllMocks();
// Reset console methods
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.spyOn(console, 'error').mockImplementation(() => { });
jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Evidence Type Validation', () => {
it('should validate evidence types', () => {
const validTypes = ['commit', 'pr', 'time', 'note'];
validTypes.forEach(type => {
expect(validTypes.includes(type)).toBe(true);
});
const invalidTypes = ['task', 'meeting', 'invalid'];
invalidTypes.forEach(type => {
expect(validTypes.includes(type)).toBe(false);
});
});
});
describe('Evidence Creation', () => {
it('should create evidence with required fields only', async () => {
const mockEvidence = {
id: 'evidence-1',
type: 'note',
message: 'Test note',
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'note',
message: 'Test note',
goal_id: undefined,
source: undefined,
metadata: undefined,
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(api_js_1.addEvidence).toHaveBeenCalledWith(evidenceData);
expect(result).toEqual(mockEvidence);
});
it('should create evidence with all optional fields', async () => {
const mockEvidence = {
id: 'evidence-2',
type: 'commit',
message: 'Add new feature',
goal_id: 'goal-123',
source: 'feature/new-feature',
metadata: { lines_added: 100, lines_deleted: 20 },
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'commit',
message: 'Add new feature',
goal_id: 'goal-123',
source: 'feature/new-feature',
metadata: { lines_added: 100, lines_deleted: 20 },
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(api_js_1.addEvidence).toHaveBeenCalledWith(evidenceData);
expect(result).toEqual(mockEvidence);
});
it('should handle addEvidence API errors', async () => {
const errorMessage = 'Failed to add evidence';
api_js_1.addEvidence.mockRejectedValue(new Error(errorMessage));
const evidenceData = {
type: 'note',
message: 'Test note',
goal_id: undefined,
source: undefined,
metadata: undefined,
};
await expect((0, api_js_1.addEvidence)(evidenceData)).rejects.toThrow(errorMessage);
});
});
describe('Time Evidence Validation', () => {
it('should accept valid time entries', async () => {
const mockEvidence = {
id: 'time-evidence',
type: 'time',
message: 'Development work',
metadata: { minutes: 120 },
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'time',
message: 'Development work',
goal_id: undefined,
source: undefined,
metadata: { minutes: 120 },
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(result.metadata?.minutes).toBe(120);
expect(result.type).toBe('time');
});
it('should validate positive minutes for time entries', () => {
const validMinutes = [1, 30, 60, 120, 480];
validMinutes.forEach(minutes => {
expect(minutes).toBeGreaterThan(0);
});
const invalidMinutes = [0, -1, -30];
invalidMinutes.forEach(minutes => {
expect(minutes).toBeLessThanOrEqual(0);
});
});
it('should handle time entries with goal linking', async () => {
const mockEvidence = {
id: 'time-evidence-with-goal',
type: 'time',
message: 'Feature development',
goal_id: 'goal-456',
metadata: { minutes: 180 },
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'time',
message: 'Feature development',
goal_id: 'goal-456',
source: undefined,
metadata: { minutes: 180 },
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(result.goal_id).toBe('goal-456');
expect(result.metadata?.minutes).toBe(180);
});
});
describe('Commit Evidence', () => {
it('should handle commit evidence with code changes', async () => {
const mockEvidence = {
id: 'commit-evidence',
type: 'commit',
message: 'Fix authentication bug',
source: 'fix/auth-issue',
metadata: {
lines_added: 15,
lines_deleted: 8,
files_changed: 3,
commit_hash: 'abc123'
},
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'commit',
message: 'Fix authentication bug',
goal_id: undefined,
source: 'fix/auth-issue',
metadata: {
lines_added: 15,
lines_deleted: 8,
files_changed: 3,
commit_hash: 'abc123'
},
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(result.type).toBe('commit');
expect(result.source).toBe('fix/auth-issue');
expect(result.metadata?.lines_added).toBe(15);
expect(result.metadata?.lines_deleted).toBe(8);
});
});
describe('PR Evidence', () => {
it('should handle PR evidence with review metadata', async () => {
const mockEvidence = {
id: 'pr-evidence',
type: 'pr',
message: 'Add comprehensive testing strategy',
source: 'https://github.com/user/repo/pull/15',
metadata: {
pr_number: 15,
status: 'merged',
reviews_received: 2,
files_changed: 8
},
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'pr',
message: 'Add comprehensive testing strategy',
goal_id: undefined,
source: 'https://github.com/user/repo/pull/15',
metadata: {
pr_number: 15,
status: 'merged',
reviews_received: 2,
files_changed: 8
},
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(result.type).toBe('pr');
expect(result.source).toBe('https://github.com/user/repo/pull/15');
expect(result.metadata?.pr_number).toBe(15);
});
});
describe('Note Evidence', () => {
it('should handle note evidence with custom metadata', async () => {
const mockEvidence = {
id: 'note-evidence',
type: 'note',
message: 'Planning session for Q4 goals',
metadata: {
location: 'team meeting',
attendees: ['alice', 'bob'],
duration_minutes: 45
},
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'note',
message: 'Planning session for Q4 goals',
goal_id: undefined,
source: undefined,
metadata: {
location: 'team meeting',
attendees: ['alice', 'bob'],
duration_minutes: 45
},
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(result.type).toBe('note');
expect(result.metadata?.location).toBe('team meeting');
expect(result.metadata?.attendees).toEqual(['alice', 'bob']);
});
});
describe('Metadata Handling', () => {
it('should handle empty metadata gracefully', async () => {
const mockEvidence = {
id: 'minimal-evidence',
type: 'note',
message: 'Simple note',
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'note',
message: 'Simple note',
goal_id: undefined,
source: undefined,
metadata: undefined,
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(result.metadata).toBeUndefined();
});
it('should merge minutes with additional metadata', () => {
const baseMetadata = { minutes: 60 };
const additionalMetadata = { category: 'development', priority: 'high' };
const mergedMetadata = { ...baseMetadata, ...additionalMetadata };
expect(mergedMetadata).toEqual({
minutes: 60,
category: 'development',
priority: 'high'
});
});
it('should handle JSON metadata parsing errors', () => {
const invalidJson = '{ invalid json }';
expect(() => {
JSON.parse(invalidJson);
}).toThrow();
});
it('should handle valid JSON metadata', () => {
const validJson = '{"key": "value", "number": 42, "array": [1, 2, 3]}';
const parsed = JSON.parse(validJson);
expect(parsed).toEqual({
key: 'value',
number: 42,
array: [1, 2, 3]
});
});
});
describe('Goal Linking', () => {
it('should handle evidence linked to goals', async () => {
const mockEvidence = {
id: 'linked-evidence',
type: 'commit',
message: 'Work towards weekly goal',
goal_id: 'weekly-goal-123',
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'commit',
message: 'Work towards weekly goal',
goal_id: 'weekly-goal-123',
source: undefined,
metadata: undefined,
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(result.goal_id).toBe('weekly-goal-123');
});
it('should handle evidence without goal linking', async () => {
const mockEvidence = {
id: 'unlinked-evidence',
type: 'note',
message: 'General research notes',
created_at: '2025-07-02T10:00:00Z'
};
api_js_1.addEvidence.mockResolvedValue(mockEvidence);
const evidenceData = {
type: 'note',
message: 'General research notes',
goal_id: undefined,
source: undefined,
metadata: undefined,
};
const result = await (0, api_js_1.addEvidence)(evidenceData);
expect(result.goal_id).toBeUndefined();
});
});
});