mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-23 00:41:36 +00:00
* feat: configure prettier with pre-commit hook integration Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo root with husky pre-commit hook integration. Moves husky from gitnexus/ to root package.json for reliable hook installation. - Root package.json with prepare/format/format:check scripts - .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4 - .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md - .gitattributes enforcing LF line endings for Windows consistency - Pre-commit hook uses direct node_modules/.bin/ paths (no npx) * style: apply prettier formatting to entire codebase One-time bulk format. No logic changes. Use .git-blame-ignore-revs to skip this commit in git blame. * chore: add .git-blame-ignore-revs for prettier format commit * perf: pre-commit hook runs only tests related to staged files Use vitest --related to scope test execution to tests that import the changed files, instead of running the full suite on every commit. * perf: remove vitest from pre-commit hook, keep in CI only Pre-commit now runs lint-staged + tsc only. Tests run in CI (ci-tests.yml) where they belong — keeps commits fast. * ci: add prettier format check to quality workflow PRs will now fail if code isn't formatted with prettier.
131 lines
4.6 KiB
TypeScript
131 lines
4.6 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { JobManager } from '../../src/server/analyze-job.js';
|
|
|
|
describe('JobManager', () => {
|
|
let manager: JobManager;
|
|
|
|
beforeEach(() => {
|
|
manager = new JobManager();
|
|
});
|
|
|
|
afterEach(() => {
|
|
manager.dispose();
|
|
});
|
|
|
|
it('creates a job with queued status', () => {
|
|
const job = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
expect(job.id).toBeTruthy();
|
|
expect(job.status).toBe('queued');
|
|
expect(job.repoUrl).toBe('https://github.com/user/repo');
|
|
});
|
|
|
|
it('retrieves a job by id', () => {
|
|
const created = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
const retrieved = manager.getJob(created.id);
|
|
expect(retrieved).toEqual(created);
|
|
});
|
|
|
|
it('returns undefined for unknown job id', () => {
|
|
expect(manager.getJob('nonexistent')).toBeUndefined();
|
|
});
|
|
|
|
it('enforces single-slot concurrency', () => {
|
|
const job1 = manager.createJob({ repoUrl: 'https://github.com/user/repo1' });
|
|
manager.updateJob(job1.id, { status: 'analyzing' });
|
|
expect(() => manager.createJob({ repoUrl: 'https://github.com/user/repo2' })).toThrow(
|
|
/already in progress/,
|
|
);
|
|
});
|
|
|
|
it('allows new job after previous completes', () => {
|
|
const job1 = manager.createJob({ repoUrl: 'https://github.com/user/repo1' });
|
|
manager.updateJob(job1.id, { status: 'analyzing' });
|
|
manager.updateJob(job1.id, { status: 'complete' });
|
|
const job2 = manager.createJob({ repoUrl: 'https://github.com/user/repo2' });
|
|
expect(job2.status).toBe('queued');
|
|
});
|
|
|
|
it('returns existing job for same repoUrl when active', () => {
|
|
const job1 = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
manager.updateJob(job1.id, { status: 'analyzing' });
|
|
const job2 = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
expect(job2.id).toBe(job1.id);
|
|
});
|
|
|
|
it('updates job progress', () => {
|
|
const job = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
manager.updateJob(job.id, {
|
|
status: 'analyzing',
|
|
progress: { phase: 'parsing', percent: 30, message: 'Parsing code' },
|
|
});
|
|
const updated = manager.getJob(job.id)!;
|
|
expect(updated.status).toBe('analyzing');
|
|
expect(updated.progress.percent).toBe(30);
|
|
});
|
|
|
|
it('emits progress events', () => {
|
|
const job = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
const events: any[] = [];
|
|
manager.onProgress(job.id, (data) => events.push(data));
|
|
|
|
manager.updateJob(job.id, {
|
|
progress: { phase: 'parsing', percent: 50, message: 'Parsing code' },
|
|
});
|
|
|
|
expect(events).toHaveLength(1);
|
|
expect(events[0].percent).toBe(50);
|
|
});
|
|
|
|
it('emits terminal event on complete', () => {
|
|
const job = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
const events: any[] = [];
|
|
manager.onProgress(job.id, (data) => events.push(data));
|
|
|
|
manager.updateJob(job.id, { status: 'complete', repoName: 'repo' });
|
|
|
|
expect(events).toHaveLength(1);
|
|
expect(events[0].phase).toBe('complete');
|
|
expect(events[0].percent).toBe(100);
|
|
});
|
|
|
|
it('sets completedAt on terminal status', () => {
|
|
const job = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
manager.updateJob(job.id, { status: 'complete' });
|
|
expect(manager.getJob(job.id)!.completedAt).toBeDefined();
|
|
});
|
|
|
|
it('unsubscribe stops events', () => {
|
|
const job = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
const events: any[] = [];
|
|
const unsub = manager.onProgress(job.id, (data) => events.push(data));
|
|
|
|
manager.updateJob(job.id, {
|
|
progress: { phase: 'p1', percent: 10, message: 'm1' },
|
|
});
|
|
unsub();
|
|
manager.updateJob(job.id, {
|
|
progress: { phase: 'p2', percent: 20, message: 'm2' },
|
|
});
|
|
|
|
expect(events).toHaveLength(1);
|
|
});
|
|
|
|
it('cancelJob sets status to failed with reason', () => {
|
|
const job = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
manager.updateJob(job.id, { status: 'analyzing' });
|
|
const cancelled = manager.cancelJob(job.id, 'Cancelled by user');
|
|
expect(cancelled).toBe(true);
|
|
expect(manager.getJob(job.id)!.status).toBe('failed');
|
|
expect(manager.getJob(job.id)!.error).toBe('Cancelled by user');
|
|
});
|
|
|
|
it('cancelJob returns false for terminal jobs', () => {
|
|
const job = manager.createJob({ repoUrl: 'https://github.com/user/repo' });
|
|
manager.updateJob(job.id, { status: 'complete' });
|
|
expect(manager.cancelJob(job.id)).toBe(false);
|
|
});
|
|
|
|
it('cancelJob returns false for unknown job', () => {
|
|
expect(manager.cancelJob('nonexistent')).toBe(false);
|
|
});
|
|
});
|