3d5fb86e7c
Test-file load failures (worktree CRLF, ruflo dormant copies) cause vitest exit code 1 but contribute zero actual test failures. Verify-before-push should accept this state — infrastructure issues don't invalidate test coverage.
114 lines
4.2 KiB
JavaScript
114 lines
4.2 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { decide } from './enforce-verify-before-push.mjs';
|
|
import { decideRecord, extractTestMetrics } from './enforce-verify-record.mjs';
|
|
|
|
describe('enforce-verify-record / decideRecord', () => {
|
|
it('returns null for non-Bash', () => {
|
|
expect(decideRecord({ toolName: 'Edit', command: 'foo' })).toBeNull();
|
|
});
|
|
it('returns null for non-test command', () => {
|
|
expect(decideRecord({ toolName: 'Bash', command: 'git status', exitCode: 0, stdout: '' })).toBeNull();
|
|
});
|
|
it('returns null for narrow vitest (specific test file)', () => {
|
|
expect(decideRecord({ toolName: 'Bash', command: 'npx vitest run tools/foo.test.mjs', exitCode: 0, stdout: '' })).toBeNull();
|
|
});
|
|
it('records PASS on full vitest run with all-passed summary', () => {
|
|
const rec = decideRecord({
|
|
toolName: 'Bash', command: 'npx vitest run', exitCode: 0,
|
|
stdout: 'Tests 3708 passed (3708)',
|
|
});
|
|
expect(rec.result).toBe('pass');
|
|
expect(rec.tests_total).toBe(3708);
|
|
expect(rec.tests_passed).toBe(3708);
|
|
});
|
|
it('records FAIL on full vitest run with failed summary', () => {
|
|
const rec = decideRecord({
|
|
toolName: 'Bash', command: 'npx vitest run', exitCode: 1,
|
|
stdout: 'Tests 3 failed | 600 passed (603)',
|
|
});
|
|
expect(rec.result).toBe('fail');
|
|
expect(rec.tests_failed).toBe(3);
|
|
});
|
|
|
|
it('records PASS when exit=1 but tests_failed=0 (infra file-load failures)', () => {
|
|
// E.g. worktree CRLF copies of test files crash to load → exit code 1
|
|
// but all actual tests passed.
|
|
const rec = decideRecord({
|
|
toolName: 'Bash', command: 'npx vitest run', exitCode: 1,
|
|
stdout: 'Test Files 95 failed | 411 passed (506)\n Tests 8091 passed (8091)',
|
|
});
|
|
expect(rec.result).toBe('pass');
|
|
});
|
|
it('records pest', () => {
|
|
const rec = decideRecord({
|
|
toolName: 'Bash', command: 'composer test', exitCode: 0,
|
|
stdout: 'Tests: 742 passed (1908 assertions)',
|
|
});
|
|
expect(rec.result).toBe('pass');
|
|
});
|
|
});
|
|
|
|
describe('enforce-verify-record / extractTestMetrics', () => {
|
|
it('parses vitest all-passed', () => {
|
|
expect(extractTestMetrics('Tests 3708 passed (3708)')).toMatchObject({
|
|
tests_passed: 3708, tests_total: 3708, tests_failed: 0,
|
|
});
|
|
});
|
|
it('parses vitest mixed failure', () => {
|
|
expect(extractTestMetrics('Tests 1 failed | 631 passed (632)')).toMatchObject({
|
|
tests_failed: 1, tests_passed: 631, tests_total: 632,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('enforce-verify-before-push / decide', () => {
|
|
it('allows non-Bash', () => {
|
|
expect(decide({ toolName: 'Edit', command: '' }).block).toBe(false);
|
|
});
|
|
it('allows non-git Bash', () => {
|
|
expect(decide({ toolName: 'Bash', command: 'ls -la' }).block).toBe(false);
|
|
});
|
|
it('blocks git commit without sentinel', () => {
|
|
const r = decide({ toolName: 'Bash', command: 'git commit -m "x"' });
|
|
expect(r.block).toBe(true);
|
|
expect(r.message).toMatch(/No verification/);
|
|
});
|
|
it('blocks git push without sentinel', () => {
|
|
expect(decide({ toolName: 'Bash', command: 'git push origin main' }).block).toBe(true);
|
|
});
|
|
it('blocks when sentinel result=fail', () => {
|
|
const r = decide({
|
|
toolName: 'Bash', command: 'git commit -m "x"',
|
|
sentinel: { result: 'fail', exit_code: 1, tests_passed: 600, tests_total: 603, tests_failed: 3 },
|
|
sentinelAge: 60,
|
|
});
|
|
expect(r.block).toBe(true);
|
|
expect(r.message).toMatch(/FAILED/);
|
|
});
|
|
it('blocks when sentinel is stale', () => {
|
|
const r = decide({
|
|
toolName: 'Bash', command: 'git commit -m "x"',
|
|
sentinel: { result: 'pass' },
|
|
sentinelAge: 60 * 60, // 1 hour > 30 min
|
|
});
|
|
expect(r.block).toBe(true);
|
|
expect(r.message).toMatch(/stale/);
|
|
});
|
|
it('allows when sentinel is fresh + pass', () => {
|
|
const r = decide({
|
|
toolName: 'Bash', command: 'git commit -m "x"',
|
|
sentinel: { result: 'pass' },
|
|
sentinelAge: 120,
|
|
});
|
|
expect(r.block).toBe(false);
|
|
});
|
|
it('allows when override phrase present', () => {
|
|
const r = decide({
|
|
toolName: 'Bash', command: 'git push',
|
|
sentinel: null,
|
|
override: { phrase: 'срочно', suppresses: ['verify-before-push'] },
|
|
});
|
|
expect(r.block).toBe(false);
|
|
});
|
|
});
|