forked from EvoMap/evolver
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathissueReporter.test.js
More file actions
147 lines (132 loc) · 6.61 KB
/
Copy pathissueReporter.test.js
File metadata and controls
147 lines (132 loc) · 6.61 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
const assert = require('assert');
const MODULE_PATH = require.resolve('../src/gep/issueReporter');
function withFetchMock(mock, fn) {
const original = global.fetch;
global.fetch = mock;
return Promise.resolve(fn()).finally(function () { global.fetch = original; });
}
function jsonResponse(body, status) {
return {
ok: status == null || (status >= 200 && status < 300),
status: status || 200,
json: async function () { return body; },
text: async function () { return JSON.stringify(body); },
};
}
(async function run() {
delete require.cache[MODULE_PATH];
const { findExistingIssue } = require('../src/gep/issueReporter');
// Case 1: search returns matching open issue -> returns object
await withFetchMock(async function (url, opts) {
assert.ok(String(url).includes('/search/issues'), 'should hit search API');
assert.ok(String(url).includes('is%3Aopen'), 'should request open issues');
return jsonResponse({
items: [
{ number: 397, html_url: 'https://github.com/x/y/issues/397', title: '[Auto] Recurring failure: Repeated failures with gene: gene_gep_repair_from_errors', state: 'open' }
],
});
}, async function () {
const result = await findExistingIssue('x/y', '[Auto] Recurring failure: Repeated failures with gene: gene_gep_repair_from_errors', 'faketoken');
assert.ok(result && result.number === 397, 'should find existing open issue');
});
// Case 2: search returns no items -> returns null
await withFetchMock(async function () {
return jsonResponse({ items: [] });
}, async function () {
const result = await findExistingIssue('x/y', '[Auto] Recurring failure: Something new', 'faketoken');
assert.strictEqual(result, null, 'should return null when no matches');
});
// Case 3: search returns only closed issues (API scoped to open via query, but guard client-side)
await withFetchMock(async function () {
return jsonResponse({
items: [
{ number: 100, html_url: 'https://github.com/x/y/issues/100', title: '[Auto] Recurring failure: foo', state: 'closed' }
],
});
}, async function () {
const result = await findExistingIssue('x/y', '[Auto] Recurring failure: foo', 'faketoken');
assert.strictEqual(result, null, 'closed issues should not count');
});
// Case 4: network failure -> returns null (non-fatal)
await withFetchMock(async function () { throw new Error('network down'); }, async function () {
const result = await findExistingIssue('x/y', '[Auto] Recurring failure: foo', 'faketoken');
assert.strictEqual(result, null, 'network error should return null');
});
// Case 5: non-OK response -> returns null
await withFetchMock(async function () {
return { ok: false, status: 403, text: async function () { return 'rate limited'; }, json: async function () { return {}; } };
}, async function () {
const result = await findExistingIssue('x/y', '[Auto] Recurring failure: foo', 'faketoken');
assert.strictEqual(result, null, 'non-OK should return null');
});
// Case 6: empty title -> returns null without hitting fetch
let called = false;
await withFetchMock(async function () { called = true; return jsonResponse({ items: [] }); }, async function () {
const result = await findExistingIssue('x/y', '', 'faketoken');
assert.strictEqual(result, null, 'empty title should short-circuit');
assert.strictEqual(called, false, 'fetch should not be called for empty title');
});
// Case 7: default repo matches config.SELF_PR_REPO (no legacy repo drift since v1.69.7)
const savedIssueRepo = process.env.EVOLVER_ISSUE_REPO;
delete process.env.EVOLVER_ISSUE_REPO;
try {
delete require.cache[MODULE_PATH];
delete require.cache[require.resolve('../src/config')];
const { getConfig } = require('../src/gep/issueReporter');
const { SELF_PR_REPO } = require('../src/config');
const cfg = getConfig();
assert.ok(cfg, 'getConfig should return an object when auto-issue is enabled by default');
assert.strictEqual(cfg.repo, SELF_PR_REPO, 'issueReporter default repo must equal config.SELF_PR_REPO');
assert.strictEqual(SELF_PR_REPO, 'EvoMap/evolver', 'SELF_PR_REPO default should be EvoMap/evolver');
} finally {
if (savedIssueRepo === undefined) {
delete process.env.EVOLVER_ISSUE_REPO;
} else {
process.env.EVOLVER_ISSUE_REPO = savedIssueRepo;
}
}
// Case A: computeErrorKey is stable across (Nx) occurrence counts.
// Regression: the (Nx) prefix had been hashed verbatim, mutating the key
// every cycle and defeating recentIssueKeys dedup.
delete require.cache[MODULE_PATH];
const { computeErrorKey } = require('../src/gep/issueReporter');
const k3 = computeErrorKey(['recurring_errsig(3x):timeout on API', 'failure_loop_detected']);
const k4 = computeErrorKey(['recurring_errsig(4x):timeout on API', 'failure_loop_detected']);
const k5 = computeErrorKey(['recurring_errsig(5x):timeout on API', 'failure_loop_detected']);
assert.strictEqual(k3, k4, 'errorKey should be stable across (Nx) counts');
assert.strictEqual(k4, k5, 'errorKey should be stable across (Nx) counts');
// Case B: shouldReport enforces minStreak even when streak signal is absent.
// Regression: prior guard "streakCount > 0 && < minStreak" was a no-op when
// the consecutive_failure_streak_N signal was missing.
const { shouldReport } = require('../src/gep/issueReporter');
const gatedCfg = { repo: 'x/y', cooldownMs: 86400000, minStreak: 5 };
assert.strictEqual(
shouldReport(['failure_loop_detected', 'recurring_errsig(2x):foo'], gatedCfg),
false,
'minStreak gate must apply when streak signal is absent'
);
assert.strictEqual(
shouldReport(['failure_loop_detected', 'recurring_errsig(2x):foo', 'consecutive_failure_streak_10'], gatedCfg),
true,
'should pass when streak meets minStreak'
);
// Case C: findExistingIssue no longer matches unrelated titles via loose
// substring fallback. Prior `it.title.indexOf(titleSig) !== -1` matched
// issues that merely contained the search signature as a substring.
await withFetchMock(async function () {
return jsonResponse({
items: [
{ number: 12345, state: 'open', html_url: 'https://x/y/12345',
title: 'META discussion about [Auto] Recurring failure: boom with unrelated context' }
],
});
}, async function () {
const target = '[Auto] Recurring failure: boom';
const result = await findExistingIssue('x/y', target, 'faketoken');
assert.strictEqual(result, null, 'substring fallback must not match unrelated titles');
});
console.log('issueReporter.test.js: OK');
})().catch(function (err) {
console.error(err);
process.exit(1);
});