@elench/testkit 0.1.44 → 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -161,12 +161,29 @@ If `reporting.knownFailuresFile` is configured, `testkit` enriches
|
|
|
161
161
|
- per-file `triage` metadata (issue, classification, description)
|
|
162
162
|
- top-level `triageSummary` counts for known vs untriaged failures
|
|
163
163
|
|
|
164
|
+
Known-failure entry authoring uses this contract:
|
|
165
|
+
|
|
166
|
+
- `title`
|
|
167
|
+
- exact issue-tracker title for the linked issue
|
|
168
|
+
- shared issues may reuse the same title across multiple local entries
|
|
169
|
+
- `description`
|
|
170
|
+
- product-local bug slice or route-family summary
|
|
171
|
+
- this is where file-specific nuance belongs
|
|
172
|
+
- `whyFailing`
|
|
173
|
+
- underlying technical cause of the failure
|
|
174
|
+
|
|
164
175
|
If `reporting.issueValidation` is also configured, `testkit` validates known-failure
|
|
165
176
|
issue references against GitHub and adds top-level `knownFailuresIssueValidation`
|
|
166
177
|
data to the run/status artifacts. The most important stale-triage signal is:
|
|
167
178
|
|
|
168
179
|
- a known-failure test still fails, but the linked GitHub issue is closed
|
|
169
180
|
|
|
181
|
+
In `mode: "error"`, exact GitHub metadata drift is also treated as a validation
|
|
182
|
+
failure:
|
|
183
|
+
|
|
184
|
+
- title does not match the linked issue title
|
|
185
|
+
- local open/closed state does not match the linked issue state
|
|
186
|
+
|
|
170
187
|
## Authoring
|
|
171
188
|
|
|
172
189
|
HTTP suites:
|
|
@@ -436,7 +436,7 @@ function buildIssueValidationEntry({ entry, observed, issueData }) {
|
|
|
436
436
|
if (issueData?.exists && issueData.title && issueData.title !== entry.title) {
|
|
437
437
|
findings.push({
|
|
438
438
|
code: "title_mismatch",
|
|
439
|
-
severity: "
|
|
439
|
+
severity: "error",
|
|
440
440
|
message: `Known failure ${entry.id} title does not match issue #${entry.issue.number}`,
|
|
441
441
|
});
|
|
442
442
|
}
|
|
@@ -444,7 +444,7 @@ function buildIssueValidationEntry({ entry, observed, issueData }) {
|
|
|
444
444
|
if (issueData?.exists && normalizedIssueState && normalizedIssueState !== entry.state) {
|
|
445
445
|
findings.push({
|
|
446
446
|
code: "state_mismatch",
|
|
447
|
-
severity: "
|
|
447
|
+
severity: "error",
|
|
448
448
|
message: `Known failure ${entry.id} state ${entry.state} does not match issue #${entry.issue.number} state ${normalizedIssueState}`,
|
|
449
449
|
});
|
|
450
450
|
}
|
|
@@ -25,7 +25,7 @@ describe("known failures GitHub validation", () => {
|
|
|
25
25
|
expect(parseGitHubRepoSlug("https://gitlab.com/acme/repo.git")).toBe(null);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
it("
|
|
28
|
+
it("fails validation for title/state drift and closed issues that still reproduce", async () => {
|
|
29
29
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-issues-"));
|
|
30
30
|
tempDirs.push(tempDir);
|
|
31
31
|
const document = normalizeKnownFailuresDocument({
|
|
@@ -98,11 +98,114 @@ describe("known failures GitHub validation", () => {
|
|
|
98
98
|
|
|
99
99
|
expect(result.summary.byCode.closed_but_failing).toBe(1);
|
|
100
100
|
expect(result.summary.byCode.title_mismatch).toBe(1);
|
|
101
|
+
expect(result.summary.byCode.state_mismatch).toBe(1);
|
|
101
102
|
expect(result.summary.errors).toBeGreaterThan(0);
|
|
102
103
|
expect(result.entries[0].status).toBe("closed_but_failing");
|
|
103
104
|
expect(shouldFailKnownFailureIssueValidation(result)).toBe(true);
|
|
104
105
|
});
|
|
105
106
|
|
|
107
|
+
it("allows multiple local entries to share one issue title while keeping distinct descriptions", async () => {
|
|
108
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-shared-title-"));
|
|
109
|
+
tempDirs.push(tempDir);
|
|
110
|
+
const document = normalizeKnownFailuresDocument({
|
|
111
|
+
schemaVersion: 1,
|
|
112
|
+
issueRepo: "acme/repo",
|
|
113
|
+
entries: [
|
|
114
|
+
{
|
|
115
|
+
id: "route-a-invalid-uuid",
|
|
116
|
+
title: "UUID path params reach Postgres and return 500 instead of 400",
|
|
117
|
+
classification: "product_bug",
|
|
118
|
+
state: "open",
|
|
119
|
+
issue: {
|
|
120
|
+
repo: "acme/repo",
|
|
121
|
+
number: 77,
|
|
122
|
+
url: "https://github.com/acme/repo/issues/77",
|
|
123
|
+
},
|
|
124
|
+
description: "Route A leaks Postgres UUID parse errors on malformed ids.",
|
|
125
|
+
whyFailing: "Route A is missing UUID param validation.",
|
|
126
|
+
lastReviewedAt: "2026-04-27",
|
|
127
|
+
matches: [
|
|
128
|
+
{
|
|
129
|
+
service: "api",
|
|
130
|
+
type: "int",
|
|
131
|
+
path: "src/api/routes/__testkit__/route-a.int.testkit.ts",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "route-b-invalid-uuid",
|
|
137
|
+
title: "UUID path params reach Postgres and return 500 instead of 400",
|
|
138
|
+
classification: "product_bug",
|
|
139
|
+
state: "open",
|
|
140
|
+
issue: {
|
|
141
|
+
repo: "acme/repo",
|
|
142
|
+
number: 77,
|
|
143
|
+
url: "https://github.com/acme/repo/issues/77",
|
|
144
|
+
},
|
|
145
|
+
description: "Route B leaks Postgres UUID parse errors on malformed ids.",
|
|
146
|
+
whyFailing: "Route B is missing UUID param validation.",
|
|
147
|
+
lastReviewedAt: "2026-04-27",
|
|
148
|
+
matches: [
|
|
149
|
+
{
|
|
150
|
+
service: "api",
|
|
151
|
+
type: "int",
|
|
152
|
+
path: "src/api/routes/__testkit__/route-b.int.testkit.ts",
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const result = await validateKnownFailureIssues({
|
|
160
|
+
productDir: tempDir,
|
|
161
|
+
document,
|
|
162
|
+
statusArtifact: {
|
|
163
|
+
tests: [
|
|
164
|
+
{
|
|
165
|
+
service: "api",
|
|
166
|
+
type: "int",
|
|
167
|
+
path: "src/api/routes/__testkit__/route-a.int.testkit.ts",
|
|
168
|
+
status: "failed",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
service: "api",
|
|
172
|
+
type: "int",
|
|
173
|
+
path: "src/api/routes/__testkit__/route-b.int.testkit.ts",
|
|
174
|
+
status: "failed",
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
config: {
|
|
179
|
+
provider: "github",
|
|
180
|
+
mode: "error",
|
|
181
|
+
},
|
|
182
|
+
transport: {
|
|
183
|
+
async fetchRepoIssues(repo, numbers) {
|
|
184
|
+
const map = new Map();
|
|
185
|
+
map.set(numbers[0], {
|
|
186
|
+
repo,
|
|
187
|
+
number: numbers[0],
|
|
188
|
+
exists: true,
|
|
189
|
+
title: "UUID path params reach Postgres and return 500 instead of 400",
|
|
190
|
+
state: "OPEN",
|
|
191
|
+
url: `https://github.com/${repo}/issues/${numbers[0]}`,
|
|
192
|
+
checkedAt: "2026-04-27T00:00:00.000Z",
|
|
193
|
+
source: "github",
|
|
194
|
+
});
|
|
195
|
+
return map;
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(result.summary.byCode.title_mismatch).toBeUndefined();
|
|
201
|
+
expect(result.summary.errors).toBe(0);
|
|
202
|
+
expect(result.entries).toHaveLength(2);
|
|
203
|
+
expect(result.entries[0].findings).toEqual([]);
|
|
204
|
+
expect(result.entries[1].findings).toEqual([]);
|
|
205
|
+
expect(result.entries[0].status).toBe("open_and_failing");
|
|
206
|
+
expect(result.entries[1].status).toBe("open_and_failing");
|
|
207
|
+
});
|
|
208
|
+
|
|
106
209
|
it("warns when an open issue is not reproduced", async () => {
|
|
107
210
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-open-"));
|
|
108
211
|
tempDirs.push(tempDir);
|
|
@@ -23,11 +23,14 @@ export interface KnownFailureMatch {
|
|
|
23
23
|
|
|
24
24
|
export interface KnownFailureEntry {
|
|
25
25
|
id: string;
|
|
26
|
+
/** Exact issue-tracker title for the linked issue. */
|
|
26
27
|
title: string;
|
|
27
28
|
classification: KnownFailureClassification;
|
|
28
29
|
state: KnownFailureState;
|
|
29
30
|
issue: KnownFailureIssueRef;
|
|
31
|
+
/** Product-local bug slice or route-family summary. */
|
|
30
32
|
description: string;
|
|
33
|
+
/** Underlying technical cause of the failure. */
|
|
31
34
|
whyFailing: string;
|
|
32
35
|
lastReviewedAt: string;
|
|
33
36
|
matches: KnownFailureMatch[];
|