FABRIC/src/beadWorkspaceScanner.test.ts
jedarden 654377df50
Some checks are pending
CI / test (18.x) (push) Waiting to run
CI / test (20.x) (push) Waiting to run
CI / test (22.x) (push) Waiting to run
fix(tests): fix failing unit tests
- Fix beadWorkspaceScanner.test.ts: make beforeEach async to use top-level await
- Fix parser.real-logs.integration.test.ts: change sequence test to non-decreasing (real logs have duplicate sequences)
- Fix FleetSummaryBar.test.tsx: correct expected separator count from 5 to 4

All 2484 tests now pass (was 89 failed due to wrong Node.js version + 3 test bugs).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:55:35 -04:00

230 lines
6.8 KiB
TypeScript

/**
* Tests for Bead Workspace Scanner
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import { scanBeadWorkspaces, getProjectStats, getClosedBeadsForWorkspace, ProjectStats } from './beadWorkspaceScanner.js';
import { WorkspaceConfig } from './config.js';
// Mock fs module
vi.mock('fs');
const mockFs = vi.mocked(fs);
// Mock config module
vi.mock('./config.js', () => ({
loadWorkspaces: vi.fn(),
}));
describe('Bead Workspace Scanner', () => {
const mockWorkspaces: WorkspaceConfig[] = [
{
path: '/home/coding/FABRIC',
name: 'FABRIC',
prefix: 'bf',
},
{
path: '/home/coding/spaxel',
name: 'spaxel',
prefix: 'bf',
},
];
const mockBeadsData = {
'/home/coding/FABRIC/.beads/issues.jsonl': [
JSON.stringify({
id: 'bf-1abc',
title: 'Test bead 1',
status: 'closed',
closed_at: '2026-05-20T10:00:00Z',
assignee: 'claude-code-glm-4.7-alpha',
source_repo: '.',
}),
JSON.stringify({
id: 'bf-2def',
title: 'Test bead 2',
status: 'open',
source_repo: '.',
}),
JSON.stringify({
id: 'bf-3ghi',
title: 'Test bead 3',
status: 'closed',
closed_at: '2026-05-21T14:30:00Z',
assignee: 'claude-code-glm-4.7-beta',
source_repo: '.',
}),
].join('\n'),
'/home/coding/spaxel/.beads/issues.jsonl': [
JSON.stringify({
id: 'bf-4jkl',
title: 'Spaxel bead 1',
status: 'closed',
closed_at: '2026-05-19T08:00:00Z',
assignee: 'claude-code-glm-4.7-gamma',
source_repo: '.',
}),
JSON.stringify({
id: 'bf-5mno',
title: 'Spaxel bead 2',
status: 'closed',
closed_at: '2026-05-20T12:00:00Z',
assignee: 'claude-code-glm-4.7-alpha',
source_repo: '.',
}),
].join('\n'),
};
beforeEach(async () => {
vi.clearAllMocks();
// Mock loadWorkspaces to return our test workspaces
const { loadWorkspaces } = await import('./config.js');
vi.mocked(loadWorkspaces).mockReturnValue(mockWorkspaces);
// Mock fs.existsSync to return true for our test files
mockFs.existsSync.mockImplementation((filePath) => {
const pathStr = String(filePath);
return Object.keys(mockBeadsData).includes(pathStr);
});
// Mock fs.readFileSync to return our test data
mockFs.readFileSync.mockImplementation((filePath) => {
const pathStr = String(filePath);
if (mockBeadsData[pathStr]) {
return mockBeadsData[pathStr];
}
throw new Error(`File not found: ${pathStr}`);
});
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('scanBeadWorkspaces', () => {
it('should scan all configured workspaces', () => {
const result = scanBeadWorkspaces();
expect(result.workspacesScanned).toBe(2);
expect(result.totalBeads).toBe(5);
expect(result.totalClosed).toBe(4);
});
it('should return project stats sorted by closed count', () => {
const result = scanBeadWorkspaces();
expect(result.byProject).toHaveLength(2);
expect(result.byProject[0].name).toBe('FABRIC');
expect(result.byProject[0].closedCount).toBe(2);
expect(result.byProject[1].name).toBe('spaxel');
expect(result.byProject[1].closedCount).toBe(2);
});
it('should track assignees per project', () => {
const result = scanBeadWorkspaces();
const fabric = result.byProject.find(p => p.name === 'FABRIC');
expect(fabric?.byAssignee['claude-code-glm-4.7-alpha']).toBe(1);
expect(fabric?.byAssignee['claude-code-glm-4.7-beta']).toBe(1);
const spaxel = result.byProject.find(p => p.name === 'spaxel');
expect(spaxel?.byAssignee['claude-code-glm-4.7-gamma']).toBe(1);
expect(spaxel?.byAssignee['claude-code-glm-4.7-alpha']).toBe(1);
});
it('should track most recent closure timestamp', () => {
const result = scanBeadWorkspaces();
const fabric = result.byProject.find(p => p.name === 'FABRIC');
expect(fabric?.lastClosedAt).toBe('2026-05-21T14:30:00Z');
const spaxel = result.byProject.find(p => p.name === 'spaxel');
expect(spaxel?.lastClosedAt).toBe('2026-05-20T12:00:00Z');
});
it('should handle missing workspace directories gracefully', () => {
mockFs.existsSync.mockImplementation((filePath) => {
const pathStr = String(filePath);
return pathStr.includes('spaxel'); // Only spaxel exists
});
const result = scanBeadWorkspaces();
expect(result.workspacesScanned).toBe(2);
expect(result.byProject).toHaveLength(1);
expect(result.byProject[0].name).toBe('spaxel');
});
it('should handle invalid JSON lines gracefully', () => {
mockFs.readFileSync.mockImplementation((filePath) => {
const pathStr = String(filePath);
if (pathStr.includes('FABRIC')) {
return [
JSON.stringify({
id: 'bf-1abc',
title: 'Valid bead',
status: 'closed',
closed_at: '2026-05-20T10:00:00Z',
source_repo: '.',
}),
'invalid json line',
JSON.stringify({
id: 'bf-2def',
title: 'Another valid bead',
status: 'closed',
closed_at: '2026-05-21T10:00:00Z',
source_repo: '.',
}),
].join('\n');
}
return '';
});
const result = scanBeadWorkspaces();
expect(result.totalClosed).toBe(2);
});
});
describe('getProjectStats', () => {
it('should return stats for a specific project', () => {
const stats = getProjectStats('FABRIC');
expect(stats).toBeDefined();
expect(stats?.name).toBe('FABRIC');
expect(stats?.closedCount).toBe(2);
expect(stats?.prefix).toBe('bf');
});
it('should return undefined for non-existent project', () => {
const stats = getProjectStats('nonexistent');
expect(stats).toBeUndefined();
});
});
describe('getClosedBeadsForWorkspace', () => {
it('should return closed beads for a workspace', () => {
const beads = getClosedBeadsForWorkspace('/home/coding/FABRIC');
expect(beads).toHaveLength(2);
expect(beads[0].status).toBe('closed');
expect(beads[1].status).toBe('closed');
});
it('should respect limit parameter', () => {
const beads = getClosedBeadsForWorkspace('/home/coding/FABRIC', 1);
expect(beads).toHaveLength(1);
});
it('should return empty array for non-existent workspace', () => {
mockFs.existsSync.mockReturnValue(false);
const beads = getClosedBeadsForWorkspace('/nonexistent/path');
expect(beads).toEqual([]);
});
});
});