@elench/testkit 0.1.80 → 0.1.81
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 +50 -35
- package/lib/cli/args.mjs +2 -14
- package/lib/cli/args.test.mjs +1 -17
- package/lib/cli/command-helpers.mjs +1 -20
- package/lib/cli/entrypoint.mjs +0 -4
- package/lib/cli/presentation/colors.mjs +1 -1
- package/lib/cli/presentation/failure-presentation.mjs +4 -4
- package/lib/cli/presentation/run-reporter.mjs +23 -9
- package/lib/cli/presentation/run-reporter.test.mjs +12 -6
- package/lib/cli/presentation/summary-box.test.mjs +4 -4
- package/lib/cli/viewer.mjs +18 -19
- package/lib/config/index.mjs +6 -6
- package/lib/config/runtime.mjs +8 -8
- package/lib/config-api/index.d.ts +4 -4
- package/lib/package.test.mjs +4 -4
- package/lib/{known-failures → regressions}/github.mjs +36 -78
- package/lib/regressions/github.test.mjs +324 -0
- package/lib/regressions/index.d.ts +189 -0
- package/lib/{known-failures → regressions}/index.mjs +90 -93
- package/lib/{known-failures → regressions}/index.test.mjs +37 -48
- package/lib/runner/formatting.mjs +49 -34
- package/lib/runner/formatting.test.mjs +16 -15
- package/lib/runner/metadata.mjs +1 -1
- package/lib/runner/orchestrator.mjs +7 -9
- package/lib/runner/regressions.mjs +304 -0
- package/lib/runner/{triage.test.mjs → regressions.test.mjs} +50 -36
- package/lib/runner/reporting.mjs +2 -2
- package/lib/runner/reporting.test.mjs +2 -2
- package/lib/runner/run-finalization.mjs +18 -30
- package/lib/runner/template-steps.mjs +2 -2
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +8 -8
- package/lib/cli/commands/known-failures/render.mjs +0 -19
- package/lib/cli/commands/known-failures/validate.mjs +0 -20
- package/lib/cli/known-failures.mjs +0 -164
- package/lib/known-failures/github.test.mjs +0 -512
- package/lib/known-failures/index.d.ts +0 -192
- package/lib/runner/triage.mjs +0 -221
- /package/lib/{known-failures → regressions}/github-cache.mjs +0 -0
- /package/lib/{known-failures → regressions}/github-transport.mjs +0 -0
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Command } from "@oclif/core";
|
|
2
|
-
import { makeKnownFailuresFlags } from "../../command-helpers.mjs";
|
|
3
|
-
import { runKnownFailuresValidateCommand } from "../../known-failures.mjs";
|
|
4
|
-
|
|
5
|
-
export default class KnownFailuresValidateCommand extends Command {
|
|
6
|
-
static summary = "Validate known failures against the latest status artifact";
|
|
7
|
-
|
|
8
|
-
static enableJsonFlag = true;
|
|
9
|
-
|
|
10
|
-
static flags = makeKnownFailuresFlags();
|
|
11
|
-
|
|
12
|
-
async run() {
|
|
13
|
-
const { flags } = await this.parse(KnownFailuresValidateCommand);
|
|
14
|
-
await runKnownFailuresValidateCommand({
|
|
15
|
-
...flags,
|
|
16
|
-
issueMode: flags["issue-mode"] || null,
|
|
17
|
-
});
|
|
18
|
-
return { ok: true };
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { resolveProductDir } from "../config/index.mjs";
|
|
4
|
-
import { loadTestkitConfig } from "../config/config-loader.mjs";
|
|
5
|
-
import {
|
|
6
|
-
buildKnownFailureIssueValidationSummaryLines,
|
|
7
|
-
normalizeKnownFailureIssueValidationConfig,
|
|
8
|
-
shouldFailKnownFailureIssueValidation,
|
|
9
|
-
validateKnownFailureIssues,
|
|
10
|
-
} from "../known-failures/github.mjs";
|
|
11
|
-
import {
|
|
12
|
-
loadKnownFailuresDocument,
|
|
13
|
-
renderKnownFailuresMarkdown,
|
|
14
|
-
validateKnownFailuresDocument,
|
|
15
|
-
} from "../known-failures/index.mjs";
|
|
16
|
-
import { collectGitMetadata } from "../runner/metadata.mjs";
|
|
17
|
-
|
|
18
|
-
const DEFAULT_ISSUE_VALIDATION = {
|
|
19
|
-
provider: "github",
|
|
20
|
-
mode: "error",
|
|
21
|
-
cacheTtlSeconds: 900,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export async function runKnownFailuresRenderCommand(options = {}) {
|
|
25
|
-
const context = await resolveKnownFailuresContext(options);
|
|
26
|
-
const document = loadKnownFailuresDocument(context.knownFailuresPath, context.knownFailuresRelativePath);
|
|
27
|
-
const markdown = renderKnownFailuresMarkdown(document);
|
|
28
|
-
|
|
29
|
-
if (options.output) {
|
|
30
|
-
const outputPath = path.resolve(context.productDir, options.output);
|
|
31
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
32
|
-
fs.writeFileSync(outputPath, markdown);
|
|
33
|
-
console.log(`Wrote ${path.relative(context.productDir, outputPath)}`);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
process.stdout.write(markdown);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function runKnownFailuresValidateCommand(options = {}) {
|
|
41
|
-
const context = await resolveKnownFailuresContext(options);
|
|
42
|
-
const document = loadKnownFailuresDocument(context.knownFailuresPath, context.knownFailuresRelativePath);
|
|
43
|
-
const statusArtifact = context.statusArtifactPath
|
|
44
|
-
? readJsonIfExists(context.statusArtifactPath)
|
|
45
|
-
: undefined;
|
|
46
|
-
|
|
47
|
-
const documentValidation = validateKnownFailuresDocument(document, {
|
|
48
|
-
productDir: context.productDir,
|
|
49
|
-
statusArtifact,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const issueValidation = await validateKnownFailureIssues({
|
|
53
|
-
productDir: context.productDir,
|
|
54
|
-
document,
|
|
55
|
-
statusArtifact,
|
|
56
|
-
config: context.issueValidationConfig,
|
|
57
|
-
gitMetadata: collectGitMetadata(context.productDir),
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const issueErrors = collectIssueMessages(issueValidation, "error");
|
|
61
|
-
const issueWarnings = collectIssueMessages(issueValidation, "warning");
|
|
62
|
-
const hasErrors =
|
|
63
|
-
documentValidation.errors.length > 0 ||
|
|
64
|
-
shouldFailKnownFailureIssueValidation(issueValidation) ||
|
|
65
|
-
issueErrors.length > 0;
|
|
66
|
-
|
|
67
|
-
if (hasErrors) {
|
|
68
|
-
console.error("Known failures validation failed:");
|
|
69
|
-
for (const error of documentValidation.errors) {
|
|
70
|
-
console.error(`- ${error}`);
|
|
71
|
-
}
|
|
72
|
-
for (const error of issueErrors) {
|
|
73
|
-
console.error(`- ${error}`);
|
|
74
|
-
}
|
|
75
|
-
if (documentValidation.warnings.length > 0 || issueWarnings.length > 0) {
|
|
76
|
-
console.error("");
|
|
77
|
-
for (const warning of documentValidation.warnings) {
|
|
78
|
-
console.error(`! ${warning}`);
|
|
79
|
-
}
|
|
80
|
-
for (const warning of issueWarnings) {
|
|
81
|
-
console.error(`! ${warning}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
for (const line of buildKnownFailureIssueValidationSummaryLines(issueValidation)) {
|
|
85
|
-
console.error(line);
|
|
86
|
-
}
|
|
87
|
-
process.exitCode = 1;
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
console.log(
|
|
92
|
-
`Known failures valid: ${documentValidation.stats.entries} entries, ${documentValidation.stats.matches} matches`
|
|
93
|
-
);
|
|
94
|
-
if (documentValidation.stats.failedTests > 0) {
|
|
95
|
-
console.log(
|
|
96
|
-
`Status coverage: ${documentValidation.stats.triagedFailedTests}/${documentValidation.stats.failedTests} failed tests triaged`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
for (const line of buildKnownFailureIssueValidationSummaryLines(issueValidation)) {
|
|
100
|
-
console.log(line);
|
|
101
|
-
}
|
|
102
|
-
for (const warning of [...documentValidation.warnings, ...issueWarnings]) {
|
|
103
|
-
console.warn(`! ${warning}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function resolveKnownFailuresContext(options) {
|
|
108
|
-
const productDir = resolveProductDir(process.cwd(), options.dir);
|
|
109
|
-
const { config } = await loadTestkitConfig(productDir);
|
|
110
|
-
const reporting = config?.reporting || {};
|
|
111
|
-
|
|
112
|
-
const knownFailuresRelativePath = normalizeOptionalString(options.input)
|
|
113
|
-
|| normalizeOptionalString(reporting.knownFailuresFile);
|
|
114
|
-
if (!knownFailuresRelativePath) {
|
|
115
|
-
throw new Error(
|
|
116
|
-
"Known failures file not configured. Set reporting.knownFailuresFile in testkit.config.ts or pass --input."
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const statusRelativePath = normalizeOptionalString(options.status) || "testkit.status.json";
|
|
121
|
-
const configuredIssueValidation = normalizeKnownFailureIssueValidationConfig(reporting.issueValidation)
|
|
122
|
-
|| DEFAULT_ISSUE_VALIDATION;
|
|
123
|
-
const issueMode = normalizeOptionalString(options.issueMode) || "error";
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
productDir,
|
|
127
|
-
knownFailuresRelativePath,
|
|
128
|
-
knownFailuresPath: path.resolve(productDir, knownFailuresRelativePath),
|
|
129
|
-
statusArtifactPath: statusRelativePath ? path.resolve(productDir, statusRelativePath) : null,
|
|
130
|
-
issueValidationConfig: {
|
|
131
|
-
...configuredIssueValidation,
|
|
132
|
-
mode: issueMode,
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function collectIssueMessages(validation, severity) {
|
|
138
|
-
if (!validation) return [];
|
|
139
|
-
|
|
140
|
-
const messages = [
|
|
141
|
-
...validation.findings
|
|
142
|
-
.filter((finding) => finding.severity === severity)
|
|
143
|
-
.map((finding) => finding.message),
|
|
144
|
-
];
|
|
145
|
-
for (const entry of validation.entries) {
|
|
146
|
-
for (const finding of entry.findings) {
|
|
147
|
-
if (finding.severity === severity) {
|
|
148
|
-
messages.push(finding.message);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return [...new Set(messages)];
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function normalizeOptionalString(value) {
|
|
156
|
-
if (typeof value !== "string") return null;
|
|
157
|
-
const normalized = value.trim();
|
|
158
|
-
return normalized.length > 0 ? normalized : null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function readJsonIfExists(filePath) {
|
|
162
|
-
if (!fs.existsSync(filePath)) return undefined;
|
|
163
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
164
|
-
}
|
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { normalizeKnownFailuresDocument } from "./index.mjs";
|
|
6
|
-
import {
|
|
7
|
-
parseGitHubRepoSlug,
|
|
8
|
-
shouldFailKnownFailureIssueValidation,
|
|
9
|
-
validateKnownFailureIssues,
|
|
10
|
-
} from "./github.mjs";
|
|
11
|
-
|
|
12
|
-
const tempDirs = [];
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
for (const tempDir of tempDirs.splice(0)) {
|
|
16
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe("known failures GitHub validation", () => {
|
|
21
|
-
it("parses GitHub repo slugs from common origin URLs", () => {
|
|
22
|
-
expect(parseGitHubRepoSlug("https://github.com/acme/repo.git")).toBe("acme/repo");
|
|
23
|
-
expect(parseGitHubRepoSlug("git@github.com:acme/repo.git")).toBe("acme/repo");
|
|
24
|
-
expect(parseGitHubRepoSlug("ssh://git@github.com/acme/repo.git")).toBe("acme/repo");
|
|
25
|
-
expect(parseGitHubRepoSlug("https://gitlab.com/acme/repo.git")).toBe(null);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("fails validation for title/state drift and closed issues that still reproduce", async () => {
|
|
29
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-issues-"));
|
|
30
|
-
tempDirs.push(tempDir);
|
|
31
|
-
const document = normalizeKnownFailuresDocument({
|
|
32
|
-
schemaVersion: 1,
|
|
33
|
-
issueRepo: "acme/repo",
|
|
34
|
-
entries: [
|
|
35
|
-
{
|
|
36
|
-
id: "bad-message",
|
|
37
|
-
title: "Bad message bug",
|
|
38
|
-
classification: "product_bug",
|
|
39
|
-
state: "open",
|
|
40
|
-
issue: {
|
|
41
|
-
repo: "acme/repo",
|
|
42
|
-
number: 12,
|
|
43
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
44
|
-
},
|
|
45
|
-
description: "Wrong message",
|
|
46
|
-
whyFailing: "Payload is wrong",
|
|
47
|
-
lastReviewedAt: "2026-04-27",
|
|
48
|
-
matches: [
|
|
49
|
-
{
|
|
50
|
-
service: "api",
|
|
51
|
-
type: "int",
|
|
52
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const result = await validateKnownFailureIssues({
|
|
60
|
-
productDir: tempDir,
|
|
61
|
-
document,
|
|
62
|
-
statusArtifact: {
|
|
63
|
-
tests: [
|
|
64
|
-
{
|
|
65
|
-
service: "api",
|
|
66
|
-
type: "int",
|
|
67
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
68
|
-
status: "failed",
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
config: {
|
|
73
|
-
provider: "github",
|
|
74
|
-
mode: "error",
|
|
75
|
-
cacheTtlSeconds: 60,
|
|
76
|
-
},
|
|
77
|
-
gitMetadata: {
|
|
78
|
-
repoSlug: "acme/repo",
|
|
79
|
-
remoteUrl: "https://github.com/acme/repo.git",
|
|
80
|
-
},
|
|
81
|
-
transport: {
|
|
82
|
-
async fetchRepoIssues(repo, numbers) {
|
|
83
|
-
const map = new Map();
|
|
84
|
-
map.set(numbers[0], {
|
|
85
|
-
repo,
|
|
86
|
-
number: numbers[0],
|
|
87
|
-
exists: true,
|
|
88
|
-
title: "Different title",
|
|
89
|
-
state: "CLOSED",
|
|
90
|
-
url: `https://github.com/${repo}/issues/${numbers[0]}`,
|
|
91
|
-
checkedAt: "2026-04-27T00:00:00.000Z",
|
|
92
|
-
source: "github",
|
|
93
|
-
});
|
|
94
|
-
return map;
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
expect(result.summary.byCode.closed_but_failing).toBe(1);
|
|
100
|
-
expect(result.summary.byCode.title_mismatch).toBe(1);
|
|
101
|
-
expect(result.summary.byCode.state_mismatch).toBe(1);
|
|
102
|
-
expect(result.summary.errors).toBeGreaterThan(0);
|
|
103
|
-
expect(result.entries[0].status).toBe("closed_but_failing");
|
|
104
|
-
expect(shouldFailKnownFailureIssueValidation(result)).toBe(true);
|
|
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
|
-
|
|
209
|
-
it("warns when an open issue is not reproduced", async () => {
|
|
210
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-open-"));
|
|
211
|
-
tempDirs.push(tempDir);
|
|
212
|
-
const document = normalizeKnownFailuresDocument({
|
|
213
|
-
schemaVersion: 1,
|
|
214
|
-
issueRepo: "acme/repo",
|
|
215
|
-
entries: [
|
|
216
|
-
{
|
|
217
|
-
id: "bad-message",
|
|
218
|
-
title: "Bad message bug",
|
|
219
|
-
classification: "product_bug",
|
|
220
|
-
state: "open",
|
|
221
|
-
issue: {
|
|
222
|
-
repo: "acme/repo",
|
|
223
|
-
number: 12,
|
|
224
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
225
|
-
},
|
|
226
|
-
description: "Wrong message",
|
|
227
|
-
whyFailing: "Payload is wrong",
|
|
228
|
-
lastReviewedAt: "2026-04-27",
|
|
229
|
-
matches: [
|
|
230
|
-
{
|
|
231
|
-
service: "api",
|
|
232
|
-
type: "int",
|
|
233
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
const result = await validateKnownFailureIssues({
|
|
241
|
-
productDir: tempDir,
|
|
242
|
-
document,
|
|
243
|
-
statusArtifact: {
|
|
244
|
-
tests: [
|
|
245
|
-
{
|
|
246
|
-
service: "api",
|
|
247
|
-
type: "int",
|
|
248
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
249
|
-
status: "passed",
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
},
|
|
253
|
-
config: {
|
|
254
|
-
provider: "github",
|
|
255
|
-
mode: "warn",
|
|
256
|
-
},
|
|
257
|
-
gitMetadata: {
|
|
258
|
-
repoSlug: "acme/repo",
|
|
259
|
-
},
|
|
260
|
-
transport: {
|
|
261
|
-
async fetchRepoIssues(repo, numbers) {
|
|
262
|
-
const map = new Map();
|
|
263
|
-
map.set(numbers[0], {
|
|
264
|
-
repo,
|
|
265
|
-
number: numbers[0],
|
|
266
|
-
exists: true,
|
|
267
|
-
title: "Bad message bug",
|
|
268
|
-
state: "OPEN",
|
|
269
|
-
url: `https://github.com/${repo}/issues/${numbers[0]}`,
|
|
270
|
-
checkedAt: "2026-04-27T00:00:00.000Z",
|
|
271
|
-
source: "github",
|
|
272
|
-
});
|
|
273
|
-
return map;
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
expect(result.summary.byCode.open_not_reproduced).toBe(1);
|
|
279
|
-
expect(result.summary.errors).toBe(0);
|
|
280
|
-
expect(result.summary.warnings).toBeGreaterThan(0);
|
|
281
|
-
expect(shouldFailKnownFailureIssueValidation(result)).toBe(false);
|
|
282
|
-
expect(result.entries[0].observed).toMatchObject({
|
|
283
|
-
matchedTests: 1,
|
|
284
|
-
executedTests: 1,
|
|
285
|
-
passedTests: 1,
|
|
286
|
-
failedTests: 0,
|
|
287
|
-
skippedTests: 0,
|
|
288
|
-
notRunTests: 0,
|
|
289
|
-
reproduced: false,
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it("does not treat skipped or not_run files as not reproduced", async () => {
|
|
294
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-not-executed-"));
|
|
295
|
-
tempDirs.push(tempDir);
|
|
296
|
-
const document = normalizeKnownFailuresDocument({
|
|
297
|
-
schemaVersion: 1,
|
|
298
|
-
issueRepo: "acme/repo",
|
|
299
|
-
entries: [
|
|
300
|
-
{
|
|
301
|
-
id: "bad-message",
|
|
302
|
-
title: "Bad message bug",
|
|
303
|
-
classification: "product_bug",
|
|
304
|
-
state: "open",
|
|
305
|
-
issue: {
|
|
306
|
-
repo: "acme/repo",
|
|
307
|
-
number: 12,
|
|
308
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
309
|
-
},
|
|
310
|
-
description: "Wrong message",
|
|
311
|
-
whyFailing: "Payload is wrong",
|
|
312
|
-
lastReviewedAt: "2026-04-27",
|
|
313
|
-
matches: [
|
|
314
|
-
{
|
|
315
|
-
service: "api",
|
|
316
|
-
type: "int",
|
|
317
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
service: "api",
|
|
321
|
-
type: "int",
|
|
322
|
-
path: "src/api/routes/__testkit__/blocked.int.testkit.ts",
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
},
|
|
326
|
-
],
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
const result = await validateKnownFailureIssues({
|
|
330
|
-
productDir: tempDir,
|
|
331
|
-
document,
|
|
332
|
-
statusArtifact: {
|
|
333
|
-
tests: [
|
|
334
|
-
{
|
|
335
|
-
service: "api",
|
|
336
|
-
type: "int",
|
|
337
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
338
|
-
status: "skipped",
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
service: "api",
|
|
342
|
-
type: "int",
|
|
343
|
-
path: "src/api/routes/__testkit__/blocked.int.testkit.ts",
|
|
344
|
-
status: "not_run",
|
|
345
|
-
},
|
|
346
|
-
],
|
|
347
|
-
},
|
|
348
|
-
config: {
|
|
349
|
-
provider: "github",
|
|
350
|
-
mode: "warn",
|
|
351
|
-
},
|
|
352
|
-
transport: {
|
|
353
|
-
async fetchRepoIssues(repo, numbers) {
|
|
354
|
-
const map = new Map();
|
|
355
|
-
map.set(numbers[0], {
|
|
356
|
-
repo,
|
|
357
|
-
number: numbers[0],
|
|
358
|
-
exists: true,
|
|
359
|
-
title: "Bad message bug",
|
|
360
|
-
state: "OPEN",
|
|
361
|
-
url: `https://github.com/${repo}/issues/${numbers[0]}`,
|
|
362
|
-
checkedAt: "2026-04-27T00:00:00.000Z",
|
|
363
|
-
source: "github",
|
|
364
|
-
});
|
|
365
|
-
return map;
|
|
366
|
-
},
|
|
367
|
-
},
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
expect(result.summary.byCode.open_not_reproduced).toBeUndefined();
|
|
371
|
-
expect(result.entries[0].findings).toEqual([]);
|
|
372
|
-
expect(result.entries[0].status).toBe("not_executed");
|
|
373
|
-
expect(result.entries[0].observed).toMatchObject({
|
|
374
|
-
matchedTests: 2,
|
|
375
|
-
executedTests: 0,
|
|
376
|
-
passedTests: 0,
|
|
377
|
-
failedTests: 0,
|
|
378
|
-
skippedTests: 1,
|
|
379
|
-
notRunTests: 1,
|
|
380
|
-
reproduced: false,
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it("still treats mixed passed and skipped execution as not reproduced", async () => {
|
|
385
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-mixed-execution-"));
|
|
386
|
-
tempDirs.push(tempDir);
|
|
387
|
-
const document = normalizeKnownFailuresDocument({
|
|
388
|
-
schemaVersion: 1,
|
|
389
|
-
issueRepo: "acme/repo",
|
|
390
|
-
entries: [
|
|
391
|
-
{
|
|
392
|
-
id: "bad-message",
|
|
393
|
-
title: "Bad message bug",
|
|
394
|
-
classification: "product_bug",
|
|
395
|
-
state: "open",
|
|
396
|
-
issue: {
|
|
397
|
-
repo: "acme/repo",
|
|
398
|
-
number: 12,
|
|
399
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
400
|
-
},
|
|
401
|
-
description: "Wrong message",
|
|
402
|
-
whyFailing: "Payload is wrong",
|
|
403
|
-
lastReviewedAt: "2026-04-27",
|
|
404
|
-
matches: [
|
|
405
|
-
{
|
|
406
|
-
service: "api",
|
|
407
|
-
type: "int",
|
|
408
|
-
path: "src/api/routes/__testkit__/passed.int.testkit.ts",
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
service: "api",
|
|
412
|
-
type: "int",
|
|
413
|
-
path: "src/api/routes/__testkit__/skipped.int.testkit.ts",
|
|
414
|
-
},
|
|
415
|
-
],
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
const result = await validateKnownFailureIssues({
|
|
421
|
-
productDir: tempDir,
|
|
422
|
-
document,
|
|
423
|
-
statusArtifact: {
|
|
424
|
-
tests: [
|
|
425
|
-
{
|
|
426
|
-
service: "api",
|
|
427
|
-
type: "int",
|
|
428
|
-
path: "src/api/routes/__testkit__/passed.int.testkit.ts",
|
|
429
|
-
status: "passed",
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
service: "api",
|
|
433
|
-
type: "int",
|
|
434
|
-
path: "src/api/routes/__testkit__/skipped.int.testkit.ts",
|
|
435
|
-
status: "skipped",
|
|
436
|
-
},
|
|
437
|
-
],
|
|
438
|
-
},
|
|
439
|
-
config: {
|
|
440
|
-
provider: "github",
|
|
441
|
-
mode: "warn",
|
|
442
|
-
},
|
|
443
|
-
transport: {
|
|
444
|
-
async fetchRepoIssues(repo, numbers) {
|
|
445
|
-
const map = new Map();
|
|
446
|
-
map.set(numbers[0], {
|
|
447
|
-
repo,
|
|
448
|
-
number: numbers[0],
|
|
449
|
-
exists: true,
|
|
450
|
-
title: "Bad message bug",
|
|
451
|
-
state: "OPEN",
|
|
452
|
-
url: `https://github.com/${repo}/issues/${numbers[0]}`,
|
|
453
|
-
checkedAt: "2026-04-27T00:00:00.000Z",
|
|
454
|
-
source: "github",
|
|
455
|
-
});
|
|
456
|
-
return map;
|
|
457
|
-
},
|
|
458
|
-
},
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
expect(result.summary.byCode.open_not_reproduced).toBe(1);
|
|
462
|
-
expect(result.entries[0].status).toBe("open_not_reproduced");
|
|
463
|
-
expect(result.entries[0].observed).toMatchObject({
|
|
464
|
-
matchedTests: 2,
|
|
465
|
-
executedTests: 1,
|
|
466
|
-
passedTests: 1,
|
|
467
|
-
failedTests: 0,
|
|
468
|
-
skippedTests: 1,
|
|
469
|
-
notRunTests: 0,
|
|
470
|
-
reproduced: false,
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it("falls back to warning when validation is unavailable", async () => {
|
|
475
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-unavailable-"));
|
|
476
|
-
tempDirs.push(tempDir);
|
|
477
|
-
const document = normalizeKnownFailuresDocument({
|
|
478
|
-
schemaVersion: 1,
|
|
479
|
-
entries: [
|
|
480
|
-
{
|
|
481
|
-
id: "bad-message",
|
|
482
|
-
title: "Bad message bug",
|
|
483
|
-
classification: "product_bug",
|
|
484
|
-
state: "open",
|
|
485
|
-
issue: {
|
|
486
|
-
repo: "acme/repo",
|
|
487
|
-
number: 12,
|
|
488
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
489
|
-
},
|
|
490
|
-
description: "Wrong message",
|
|
491
|
-
whyFailing: "Payload is wrong",
|
|
492
|
-
lastReviewedAt: "2026-04-27",
|
|
493
|
-
matches: [{ path: "src/api/routes/__testkit__/failing.int.testkit.ts" }],
|
|
494
|
-
},
|
|
495
|
-
],
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
const result = await validateKnownFailureIssues({
|
|
499
|
-
productDir: tempDir,
|
|
500
|
-
document,
|
|
501
|
-
config: {
|
|
502
|
-
provider: "github",
|
|
503
|
-
mode: "warn",
|
|
504
|
-
},
|
|
505
|
-
transport: null,
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
expect(result.summary.byCode.validation_unavailable).toBe(1);
|
|
509
|
-
expect(result.summary.errors).toBe(0);
|
|
510
|
-
expect(result.summary.warnings).toBeGreaterThan(0);
|
|
511
|
-
});
|
|
512
|
-
});
|