@elench/testkit 0.1.32 → 0.1.33

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.
@@ -0,0 +1,180 @@
1
+ import path from "path";
2
+
3
+ export function buildStatusArtifact({
4
+ productDir,
5
+ results,
6
+ suiteType,
7
+ suiteNames,
8
+ fileNames,
9
+ framework,
10
+ shard,
11
+ serviceFilter,
12
+ metadata,
13
+ }) {
14
+ const executedResults = results.filter((result) => !result.skipped);
15
+ const tests = [];
16
+
17
+ for (const result of executedResults) {
18
+ for (const suite of result.suites) {
19
+ for (const file of suite.files) {
20
+ tests.push({
21
+ service: result.name,
22
+ type: suite.type,
23
+ path: file.path,
24
+ status: file.status,
25
+ });
26
+ }
27
+ }
28
+ }
29
+
30
+ tests.sort(
31
+ (left, right) =>
32
+ left.service.localeCompare(right.service) ||
33
+ left.type.localeCompare(right.type) ||
34
+ left.path.localeCompare(right.path)
35
+ );
36
+
37
+ const summary = {
38
+ services: {
39
+ total: executedResults.length,
40
+ passed: executedResults.filter((result) => !result.failed).length,
41
+ failed: executedResults.filter((result) => result.failed).length,
42
+ },
43
+ tests: {
44
+ total: tests.length,
45
+ passed: tests.filter((test) => test.status === "passed").length,
46
+ failed: tests.filter((test) => test.status === "failed").length,
47
+ notRun: tests.filter((test) => test.status === "not_run").length,
48
+ },
49
+ };
50
+
51
+ const scope = {
52
+ suiteType,
53
+ suiteNames: [...(suiteNames || [])].sort(),
54
+ fileNames: [...(fileNames || [])].sort(),
55
+ framework: formatFrameworkForArtifact(framework || "all"),
56
+ shard: shard || null,
57
+ serviceFilter: serviceFilter || null,
58
+ };
59
+ scope.isFullRun =
60
+ scope.suiteNames.length === 0 &&
61
+ scope.fileNames.length === 0 &&
62
+ scope.framework === "all" &&
63
+ scope.shard === null &&
64
+ scope.serviceFilter === null;
65
+
66
+ return {
67
+ schemaVersion: 1,
68
+ source: "testkit",
69
+ notice: "Generated file. Do not edit manually.",
70
+ product: {
71
+ name: path.basename(productDir),
72
+ },
73
+ git: {
74
+ branch: metadata.git?.branch || null,
75
+ commitSha: metadata.git?.commitSha || null,
76
+ },
77
+ testkitVersion: metadata.testkitVersion,
78
+ scope,
79
+ summary,
80
+ tests,
81
+ };
82
+ }
83
+
84
+ export function buildRunArtifact({
85
+ productDir,
86
+ results,
87
+ startedAt,
88
+ finishedAt,
89
+ requestedJobs,
90
+ workerCount,
91
+ suiteType,
92
+ suiteNames,
93
+ fileNames,
94
+ framework,
95
+ shard,
96
+ serviceFilter,
97
+ metadata,
98
+ summarizeDbBackend,
99
+ }) {
100
+ const executed = results.filter((result) => !result.skipped);
101
+ const failedServices = executed.filter((result) => result.failed);
102
+ const totalSuites = executed.reduce((sum, result) => sum + result.suiteCount, 0);
103
+ const completedSuites = executed.reduce((sum, result) => sum + result.completedSuiteCount, 0);
104
+ const failedSuites = executed.reduce((sum, result) => sum + result.failedSuiteCount, 0);
105
+ const totalFiles = executed.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
106
+ const passedFiles = executed.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
107
+ const failedFiles = executed.reduce((sum, result) => sum + (result.failedFileCount || 0), 0);
108
+ const notRunFiles = executed.reduce((sum, result) => sum + (result.notRunFileCount || 0), 0);
109
+ const dbBackend = summarizeDbBackend(results);
110
+
111
+ return {
112
+ schemaVersion: 1,
113
+ source: "testkit",
114
+ generatedAt: new Date(finishedAt).toISOString(),
115
+ product: {
116
+ name: path.basename(productDir),
117
+ directory: productDir,
118
+ },
119
+ git: metadata.git,
120
+ host: metadata.host,
121
+ run: {
122
+ status: failedServices.length > 0 ? "failed" : "passed",
123
+ startedAt: new Date(startedAt).toISOString(),
124
+ finishedAt: new Date(finishedAt).toISOString(),
125
+ durationMs: finishedAt - startedAt,
126
+ requestedJobs,
127
+ workerCount,
128
+ dbBackend,
129
+ suiteType,
130
+ suiteNames,
131
+ fileNames,
132
+ framework: formatFrameworkForArtifact(framework),
133
+ shard,
134
+ serviceFilter,
135
+ testkitVersion: metadata.testkitVersion,
136
+ },
137
+ summary: {
138
+ services: {
139
+ total: executed.length,
140
+ passed: executed.length - failedServices.length,
141
+ failed: failedServices.length,
142
+ },
143
+ suites: {
144
+ total: totalSuites,
145
+ completed: completedSuites,
146
+ passed: completedSuites - failedSuites,
147
+ failed: failedSuites,
148
+ },
149
+ files: {
150
+ total: totalFiles,
151
+ passed: passedFiles,
152
+ failed: failedFiles,
153
+ notRun: notRunFiles,
154
+ },
155
+ },
156
+ services: results.map((result) => ({
157
+ name: result.name,
158
+ failed: result.failed,
159
+ skipped: result.skipped,
160
+ suiteCount: result.suiteCount,
161
+ completedSuiteCount: result.completedSuiteCount,
162
+ failedSuiteCount: result.failedSuiteCount,
163
+ totalFileCount: result.totalFileCount,
164
+ completedFileCount: result.completedFileCount,
165
+ passedFileCount: result.passedFileCount,
166
+ failedFileCount: result.failedFileCount,
167
+ notRunFileCount: result.notRunFileCount,
168
+ durationMs: result.durationMs,
169
+ totalTaskDurationMs: result.totalTaskDurationMs,
170
+ dbBackend: result.dbBackend,
171
+ suites: result.suites,
172
+ errors: result.errors,
173
+ })),
174
+ };
175
+ }
176
+
177
+ function formatFrameworkForArtifact(framework) {
178
+ if (framework === "k6") return "default";
179
+ return framework;
180
+ }
@@ -0,0 +1,193 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildRunArtifact, buildStatusArtifact } from "./reporting.mjs";
3
+
4
+ describe("runner reporting", () => {
5
+ it("builds run artifacts", () => {
6
+ const results = [
7
+ {
8
+ name: "api",
9
+ failed: false,
10
+ skipped: false,
11
+ suiteCount: 1,
12
+ completedSuiteCount: 1,
13
+ failedSuiteCount: 0,
14
+ totalFileCount: 3,
15
+ completedFileCount: 3,
16
+ passedFileCount: 3,
17
+ failedFileCount: 0,
18
+ notRunFileCount: 0,
19
+ durationMs: 1200,
20
+ totalTaskDurationMs: 2400,
21
+ dbBackend: "local",
22
+ suites: [],
23
+ errors: [],
24
+ },
25
+ {
26
+ name: "frontend",
27
+ failed: false,
28
+ skipped: true,
29
+ suiteCount: 0,
30
+ completedSuiteCount: 0,
31
+ failedSuiteCount: 0,
32
+ totalFileCount: 0,
33
+ completedFileCount: 0,
34
+ passedFileCount: 0,
35
+ failedFileCount: 0,
36
+ notRunFileCount: 0,
37
+ durationMs: 0,
38
+ totalTaskDurationMs: 0,
39
+ dbBackend: null,
40
+ suites: [],
41
+ errors: [],
42
+ },
43
+ ];
44
+
45
+ const artifact = buildRunArtifact({
46
+ productDir: "/tmp/my-product",
47
+ results,
48
+ startedAt: 1000,
49
+ finishedAt: 4000,
50
+ requestedJobs: 2,
51
+ workerCount: 1,
52
+ suiteType: "all",
53
+ suiteNames: [],
54
+ fileNames: [],
55
+ framework: "all",
56
+ shard: null,
57
+ serviceFilter: null,
58
+ metadata: {
59
+ git: {
60
+ branch: "main",
61
+ commitSha: "abc",
62
+ repoRoot: "/tmp",
63
+ },
64
+ host: {
65
+ hostname: "local",
66
+ username: "dev",
67
+ },
68
+ testkitVersion: "0.1.17",
69
+ },
70
+ summarizeDbBackend: () => "local",
71
+ });
72
+
73
+ expect(artifact.product.name).toBe("my-product");
74
+ expect(artifact.summary.services.total).toBe(1);
75
+ expect(artifact.summary.files).toEqual({
76
+ total: 3,
77
+ passed: 3,
78
+ failed: 0,
79
+ notRun: 0,
80
+ });
81
+ expect(artifact.services[0].durationMs).toBe(1200);
82
+ expect(artifact.services[0].totalTaskDurationMs).toBe(2400);
83
+ });
84
+
85
+ it("builds deterministic status artifacts", () => {
86
+ const status = buildStatusArtifact({
87
+ productDir: "/tmp/my-product",
88
+ results: [
89
+ {
90
+ name: "api",
91
+ failed: true,
92
+ skipped: false,
93
+ suites: [
94
+ {
95
+ name: "health",
96
+ type: "integration",
97
+ framework: "k6",
98
+ files: [
99
+ { path: "tests/api/integration/a.int.testkit.ts", status: "passed" },
100
+ { path: "tests/api/integration/b.int.testkit.ts", status: "failed" },
101
+ ],
102
+ },
103
+ ],
104
+ },
105
+ ],
106
+ suiteType: "int",
107
+ suiteNames: ["health"],
108
+ fileNames: ["tests/api/integration/b.int.testkit.ts"],
109
+ framework: "default",
110
+ shard: null,
111
+ serviceFilter: "api",
112
+ metadata: {
113
+ git: {
114
+ branch: "main",
115
+ commitSha: "abc123",
116
+ },
117
+ testkitVersion: "0.1.20",
118
+ },
119
+ });
120
+
121
+ expect(status).toEqual({
122
+ schemaVersion: 1,
123
+ source: "testkit",
124
+ notice: "Generated file. Do not edit manually.",
125
+ product: {
126
+ name: "my-product",
127
+ },
128
+ git: {
129
+ branch: "main",
130
+ commitSha: "abc123",
131
+ },
132
+ testkitVersion: "0.1.20",
133
+ scope: {
134
+ suiteType: "int",
135
+ suiteNames: ["health"],
136
+ fileNames: ["tests/api/integration/b.int.testkit.ts"],
137
+ framework: "default",
138
+ shard: null,
139
+ serviceFilter: "api",
140
+ isFullRun: false,
141
+ },
142
+ summary: {
143
+ services: {
144
+ total: 1,
145
+ passed: 0,
146
+ failed: 1,
147
+ },
148
+ tests: {
149
+ total: 2,
150
+ passed: 1,
151
+ failed: 1,
152
+ notRun: 0,
153
+ },
154
+ },
155
+ tests: [
156
+ {
157
+ service: "api",
158
+ type: "integration",
159
+ path: "tests/api/integration/a.int.testkit.ts",
160
+ status: "passed",
161
+ },
162
+ {
163
+ service: "api",
164
+ type: "integration",
165
+ path: "tests/api/integration/b.int.testkit.ts",
166
+ status: "failed",
167
+ },
168
+ ],
169
+ });
170
+ });
171
+
172
+ it("marks unfiltered status artifacts as full runs", () => {
173
+ const status = buildStatusArtifact({
174
+ productDir: "/tmp/my-product",
175
+ results: [],
176
+ suiteType: "all",
177
+ suiteNames: [],
178
+ fileNames: [],
179
+ framework: "all",
180
+ shard: null,
181
+ serviceFilter: null,
182
+ metadata: {
183
+ git: {
184
+ branch: "main",
185
+ commitSha: "abc123",
186
+ },
187
+ testkitVersion: "0.1.20",
188
+ },
189
+ });
190
+
191
+ expect(status.scope.isFullRun).toBe(true);
192
+ });
193
+ });