triager-tools.test.js
4.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* triager-tools 单测
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'devloop-tr-'));
process.env.HOME = TEST_HOME;
const pathResolver = require('../../../services/devloop/path-resolver');
const docStore = require('../../../services/devloop/doc-store');
const tools = require('../../../src/tools/devloop/triager-tools');
function setupProject() {
pathResolver._resetForTesting();
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tr-proj-'));
const pid = 'p-triagertest';
const reg = {
[pid]: { root_path: root, mode: 'linked-new', company_id: 'c-test' },
};
fs.mkdirSync(path.join(TEST_HOME, '.devloop'), { recursive: true });
fs.writeFileSync(
path.join(TEST_HOME, '.devloop', 'linked-projects.json'),
JSON.stringify(reg)
);
pathResolver._resetForTesting();
return { pid, root };
}
describe('triager-tools', () => {
let env, ctx;
beforeEach(() => {
env = setupProject();
ctx = { pid: env.pid, agent: 'triager' };
});
afterEach(() => {
fs.rmSync(env.root, { recursive: true, force: true });
});
describe('extract_evidence', () => {
it('verifies a quote present in a file', async () => {
const target = path.join(env.root, 'unit-x.js');
fs.writeFileSync(target, 'const VAL = 42;\nfunction foo() { return VAL; }\n');
const r = await tools.extract_evidence.execute({
evidence: ['unit-x.js: function foo() { return VAL'],
}, ctx);
expect(r.ok).toBe(true);
expect(r.results[0].ok).toBe(true);
});
it('rejects malformed evidence', async () => {
const r = await tools.extract_evidence.execute({
evidence: ['no-colon-no-quote'],
}, ctx);
expect(r.ok).toBe(false);
expect(r.failed[0].reason).toMatch(/malformed/);
});
it('reports quote not found', async () => {
const target = path.join(env.root, 'file.js');
fs.writeFileSync(target, 'function bar() {}');
const r = await tools.extract_evidence.execute({
evidence: ['file.js: never-going-to-find-this'],
}, ctx);
expect(r.ok).toBe(false);
expect(r.failed[0].reason).toMatch(/not found/);
});
});
describe('category_classifier', () => {
it('flags infra_flake on EADDRINUSE in process log', async () => {
const r = await tools.category_classifier.execute({
result_json: { failures: [] },
process_log: 'Error: listen EADDRINUSE 0.0.0.0:3000',
}, ctx);
expect(r.category).toBe('infra_flake');
});
it('flags impl_bug on 500 vs 4xx', async () => {
const r = await tools.category_classifier.execute({
result_json: { failures: [{ got: { status: 500 }, expected: { status: 401 } }] },
process_log: '',
}, ctx);
expect(r.category).toBe('impl_bug');
});
it('falls back to contract_mismatch with low confidence', async () => {
const r = await tools.category_classifier.execute({
result_json: { failures: [] },
process_log: '',
sdd_text: 'this sdd describes everything',
}, ctx);
expect(r.category).toBe('contract_mismatch');
expect(r.confidence).toBeLessThanOrEqual(0.5);
});
});
describe('diff_contract_vs_impl', () => {
it('reports divergence when endpoint not in code', async () => {
const unitId = 'u-x';
const unitDir = path.join(env.root, 'units', unitId);
fs.mkdirSync(path.join(unitDir, 'code'), { recursive: true });
fs.writeFileSync(
path.join(unitDir, 'contract.json'),
JSON.stringify({
exports: { http: [{ method: 'POST', path: '/api/login' }] },
})
);
fs.writeFileSync(
path.join(unitDir, 'code', 'server.js'),
'function nothing(){}'
);
const r = await tools.diff_contract_vs_impl.execute({ unit_id: unitId }, ctx);
expect(r.matches).toBe(false);
expect(r.divergences.some(d => d.includes('/api/login'))).toBe(true);
});
it('matches when endpoint appears in code', async () => {
const unitId = 'u-y';
const unitDir = path.join(env.root, 'units', unitId);
fs.mkdirSync(path.join(unitDir, 'code'), { recursive: true });
fs.writeFileSync(
path.join(unitDir, 'contract.json'),
JSON.stringify({
exports: { http: [{ method: 'GET', path: '/api/ping' }] },
})
);
fs.writeFileSync(
path.join(unitDir, 'code', 'server.js'),
`app.get('/api/ping', (q,r) => r.send('pong'))`
);
const r = await tools.diff_contract_vs_impl.execute({ unit_id: unitId }, ctx);
expect(r.matches).toBe(true);
});
});
describe('find_similar_bug', () => {
it('returns empty when no bugs.ndjson yet', async () => {
const r = await tools.find_similar_bug.execute({
signature: { unit_id: 'u-none', test_name: 't' },
}, ctx);
expect(r.ok).toBe(true);
expect(r.total).toBe(0);
});
});
});