bug-tracker.test.js 3.99 KB
/**
 * bugTracker 单测
 */
const fs = require('fs');

const pathResolver = require('../../services/devloop/path-resolver');
const bugTracker = require('../../services/devloop/bug-tracker');

describe('bugTracker', () => {
  let pid, root;

  beforeEach(() => {
    pathResolver._resetForTesting();
    const r = pathResolver.allocateProjectRoot({ company_id: 'c-test', mode: 'sandbox' });
    pid = r.pid;
    root = r.root_path;
  });

  afterEach(() => {
    fs.rmSync(root, { recursive: true, force: true });
  });

  describe('open + dedup', () => {
    it('open new bug returns id', () => {
      const b = bugTracker.open(pid, {
        source: 'unit_test', unit_id: 'auth-be',
        test_name: 'login.invalid-pwd', severity: 'high',
        title: 'expected 401 got 500',
      });
      expect(b.id).toMatch(/^bug-/);
      expect(b.status).toBe('open');
    });

    it('same unit + test → dedup returns existing id', () => {
      const b1 = bugTracker.open(pid, {
        unit_id: 'auth-be', test_name: 'login.invalid-pwd', title: 'T1',
      });
      const b2 = bugTracker.open(pid, {
        unit_id: 'auth-be', test_name: 'login.invalid-pwd', title: 'T2',
      });
      expect(b2.id).toBe(b1.id);
    });
  });

  describe('transition', () => {
    it('open → fixing → fixed → verified', () => {
      const b = bugTracker.open(pid, {
        unit_id: 'auth-be', test_name: 'x', title: 'T',
      });
      bugTracker.transition(pid, b.id, { to: 'fixing', by: 'coder' });
      expect(bugTracker.get(pid, b.id).status).toBe('fixing');
      bugTracker.transition(pid, b.id, { to: 'fixed', by: 'tester' });
      expect(bugTracker.get(pid, b.id).status).toBe('fixed');
      bugTracker.transition(pid, b.id, { to: 'verified', by: 'integrator' });
      expect(bugTracker.get(pid, b.id).status).toBe('verified');
    });

    it('open → verified (skip) throws', () => {
      const b = bugTracker.open(pid, { unit_id: 'x-be', test_name: 'y', title: 'T' });
      expect(() => bugTracker.transition(pid, b.id, { to: 'verified', by: 'system' }))
        .toThrow(bugTracker.InvalidBugTransitionError);
    });

    it('verified → open (reopen) ok', () => {
      const b = bugTracker.open(pid, { unit_id: 'x-be', test_name: 'y', title: 'T' });
      bugTracker.transition(pid, b.id, { to: 'fixing', by: 'coder' });
      bugTracker.transition(pid, b.id, { to: 'fixed', by: 'tester' });
      bugTracker.transition(pid, b.id, { to: 'verified', by: 'integrator' });
      bugTracker.transition(pid, b.id, { to: 'open', by: 'system' });
      expect(bugTracker.get(pid, b.id).status).toBe('open');
    });
  });

  describe('list + stats', () => {
    it('filter by status', () => {
      const b1 = bugTracker.open(pid, { unit_id: 'a-be', test_name: 't1', title: 'T1' });
      const b2 = bugTracker.open(pid, { unit_id: 'b-be', test_name: 't2', title: 'T2' });
      bugTracker.transition(pid, b2.id, { to: 'fixing', by: 'coder' });
      const open = bugTracker.list(pid, { status: ['open'] });
      const fixing = bugTracker.list(pid, { status: ['fixing'] });
      expect(open.map(b => b.id)).toContain(b1.id);
      expect(fixing.map(b => b.id)).toContain(b2.id);
    });

    it('stats counts by status', () => {
      bugTracker.open(pid, { unit_id: 'a-be', test_name: 't1', title: 'T', severity: 'high' });
      bugTracker.open(pid, { unit_id: 'b-be', test_name: 't2', title: 'T', severity: 'low' });
      const s = bugTracker.stats(pid);
      expect(s.open).toBe(2);
      expect(s.by_severity.high).toBe(1);
      expect(s.by_severity.low).toBe(1);
    });
  });

  describe('history', () => {
    it('tracks all transitions', () => {
      const b = bugTracker.open(pid, { unit_id: 'a-be', test_name: 't', title: 'T' });
      bugTracker.transition(pid, b.id, { to: 'fixing', by: 'coder' });
      bugTracker.transition(pid, b.id, { to: 'fixed', by: 'tester' });
      const h = bugTracker.history(pid, b.id);
      // 含 open 时的 history (1) + 2 次 transition = 3 条
      expect(h.length).toBeGreaterThanOrEqual(3);
    });
  });
});