coverage-check 0.1.1 → 0.2.2

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.
Files changed (67) hide show
  1. package/README.md +109 -51
  2. package/bin/coverage-check.mjs +4 -0
  3. package/dist/src/cli.d.mts +1 -0
  4. package/dist/src/cli.mjs +14 -0
  5. package/dist/src/commands/check-args.d.mts +20 -0
  6. package/dist/src/commands/check-args.mjs +89 -0
  7. package/dist/src/commands/check.d.mts +4 -0
  8. package/dist/src/commands/check.mjs +128 -0
  9. package/dist/src/commands/store-put.d.mts +11 -0
  10. package/dist/src/commands/store-put.mjs +104 -0
  11. package/{src/coverage-check.mts → dist/src/coverage-check.d.mts} +3 -9
  12. package/dist/src/coverage-check.mjs +4 -0
  13. package/dist/src/diff-parser.d.mts +17 -0
  14. package/dist/src/diff-parser.mjs +127 -0
  15. package/dist/src/github-comment.d.mts +9 -0
  16. package/dist/src/github-comment.mjs +66 -0
  17. package/dist/src/lcov-merge.d.mts +5 -0
  18. package/dist/src/lcov-merge.mjs +29 -0
  19. package/dist/src/lcov-parser.d.mts +8 -0
  20. package/dist/src/lcov-parser.mjs +44 -0
  21. package/dist/src/load-artifacts.d.mts +9 -0
  22. package/dist/src/load-artifacts.mjs +41 -0
  23. package/dist/src/patch-coverage.d.mts +5 -0
  24. package/dist/src/patch-coverage.mjs +65 -0
  25. package/dist/src/report.d.mts +4 -0
  26. package/dist/src/report.mjs +65 -0
  27. package/dist/src/rules.d.mts +4 -0
  28. package/dist/src/rules.mjs +30 -0
  29. package/dist/src/s3-suite-store.d.mts +28 -0
  30. package/dist/src/s3-suite-store.mjs +147 -0
  31. package/dist/src/s3-utils.d.mts +2 -0
  32. package/dist/src/s3-utils.mjs +14 -0
  33. package/dist/src/step-summary.d.mts +9 -0
  34. package/dist/src/step-summary.mjs +70 -0
  35. package/dist/src/store-factory.d.mts +11 -0
  36. package/dist/src/store-factory.mjs +23 -0
  37. package/dist/src/suite-store.d.mts +51 -0
  38. package/dist/src/suite-store.mjs +154 -0
  39. package/dist/src/types.d.mts +36 -0
  40. package/dist/src/types.mjs +1 -0
  41. package/package.json +20 -5
  42. package/bin/coverage-check.mts +0 -6
  43. package/src/cli.mts +0 -15
  44. package/src/cli.test.mts +0 -45
  45. package/src/commands/check.mts +0 -200
  46. package/src/commands/check.test.mts +0 -642
  47. package/src/commands/store-put.mts +0 -100
  48. package/src/commands/store-put.test.mts +0 -154
  49. package/src/diff-parser.mts +0 -127
  50. package/src/diff-parser.test.mts +0 -178
  51. package/src/github-comment.mts +0 -74
  52. package/src/github-comment.test.mts +0 -64
  53. package/src/lcov-merge.mts +0 -34
  54. package/src/lcov-merge.test.mts +0 -57
  55. package/src/lcov-parser.mts +0 -46
  56. package/src/lcov-parser.test.mts +0 -86
  57. package/src/load-artifacts.mts +0 -42
  58. package/src/load-artifacts.test.mts +0 -115
  59. package/src/patch-coverage.mts +0 -82
  60. package/src/patch-coverage.test.mts +0 -91
  61. package/src/report.mts +0 -87
  62. package/src/report.test.mts +0 -159
  63. package/src/rules.mts +0 -34
  64. package/src/rules.test.mts +0 -98
  65. package/src/suite-store.mts +0 -62
  66. package/src/suite-store.test.mts +0 -115
  67. package/src/types.mts +0 -43
@@ -1,642 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
- import { main, runCheck } from "./check.mts";
7
- import { FileSystemSuiteStore } from "../suite-store.mts";
8
-
9
- describe("main argument validation", () => {
10
- it("returns exit code 2 on unknown flags", async () => {
11
- expect(await main(["--unknown-flag"])).toBe(2);
12
- });
13
-
14
- it("returns exit code 2 when --pr is not a number", async () => {
15
- expect(await main(["--pr", "abc"])).toBe(2);
16
- });
17
-
18
- it("returns exit code 2 when --pr is zero", async () => {
19
- expect(await main(["--pr", "0"])).toBe(2);
20
- });
21
-
22
- it("returns exit code 2 when --pr has trailing non-digit chars", async () => {
23
- expect(await main(["--pr", "42abc"])).toBe(2);
24
- });
25
-
26
- it("returns exit code 2 when --pr is a decimal", async () => {
27
- expect(await main(["--pr", "42.5"])).toBe(2);
28
- });
29
-
30
- it("returns exit code 2 when a flag is missing its value", async () => {
31
- expect(await main(["--rules"])).toBe(2);
32
- });
33
- });
34
-
35
- describe("main integration", () => {
36
- let tmpDir: string;
37
- let rulesPath: string;
38
- let artifactsDir: string;
39
-
40
- beforeEach(() => {
41
- tmpDir = mkdtempSync(join(tmpdir(), "coverage-check-main-"));
42
- rulesPath = join(tmpDir, "rules.yml");
43
- artifactsDir = join(tmpDir, "artifacts");
44
- mkdirSync(artifactsDir);
45
- writeFileSync(rulesPath, "rules:\n - paths: backend/**\n patch_coverage_min: 90\n");
46
- });
47
-
48
- afterEach(() => {
49
- rmSync(tmpDir, { recursive: true, force: true });
50
- });
51
-
52
- it("returns 2 when rules file is missing", async () => {
53
- expect(
54
- await main(["--rules", join(tmpDir, "nonexistent.yml"), "--artifacts", artifactsDir]),
55
- ).toBe(2);
56
- });
57
-
58
- it("accepts --pr and --repo flags (parse succeeds, fails on missing rules)", async () => {
59
- expect(
60
- await main([
61
- "--rules",
62
- join(tmpDir, "nonexistent.yml"),
63
- "--pr",
64
- "42",
65
- "--repo",
66
- "owner/repo",
67
- "--artifacts",
68
- artifactsDir,
69
- ]),
70
- ).toBe(2);
71
- });
72
-
73
- it("accepts --strip-prefix flag", async () => {
74
- expect(
75
- await main([
76
- "--rules",
77
- join(tmpDir, "nonexistent.yml"),
78
- "--strip-prefix",
79
- "/some/path",
80
- "--artifacts",
81
- artifactsDir,
82
- ]),
83
- ).toBe(2);
84
- });
85
-
86
- it("accepts --suite flag", async () => {
87
- expect(
88
- await main([
89
- "--rules",
90
- join(tmpDir, "nonexistent.yml"),
91
- "--suite",
92
- "backend",
93
- "--artifacts",
94
- artifactsDir,
95
- ]),
96
- ).toBe(2);
97
- });
98
-
99
- it("returns 0 when no coverage data found — skips git entirely", async () => {
100
- expect(await main(["--rules", rulesPath, "--artifacts", artifactsDir])).toBe(0);
101
- });
102
-
103
- it("returns 0 when artifacts directory does not exist (ENOENT silenced)", async () => {
104
- expect(await main(["--rules", rulesPath, "--artifacts", join(tmpDir, "does-not-exist")])).toBe(
105
- 0,
106
- );
107
- });
108
-
109
- it("returns 2 when git diff fails due to invalid refs", async () => {
110
- writeFileSync(join(artifactsDir, "lcov.info"), "SF:backend/foo.mts\nDA:1,1\nend_of_record\n");
111
- expect(
112
- await main([
113
- "--rules",
114
- rulesPath,
115
- "--artifacts",
116
- artifactsDir,
117
- "--base",
118
- "INVALID_SHA_XXXXXX",
119
- "--head",
120
- "INVALID_SHA_YYYYYY",
121
- ]),
122
- ).toBe(2);
123
- });
124
-
125
- it("collects lcov files from nested subdirectories before git diff", async () => {
126
- const subDir = join(artifactsDir, "subdir");
127
- mkdirSync(subDir);
128
- writeFileSync(join(subDir, "lcov.info"), "SF:backend/foo.mts\nDA:1,1\nend_of_record\n");
129
- expect(
130
- await main([
131
- "--rules",
132
- rulesPath,
133
- "--artifacts",
134
- artifactsDir,
135
- "--base",
136
- "INVALID_SHA_XXXXXX",
137
- "--head",
138
- "INVALID_SHA_YYYYYY",
139
- ]),
140
- ).toBe(2);
141
- });
142
- });
143
-
144
- describe("runCheck with suite store", () => {
145
- let tmpDir: string;
146
- let rulesPath: string;
147
- let artifactsDir: string;
148
- let storeDir: string;
149
- let store: FileSystemSuiteStore;
150
- let origCwd: string;
151
- let savedGitEnv: Record<string, string | undefined>;
152
-
153
- beforeEach(() => {
154
- tmpDir = mkdtempSync(join(tmpdir(), "coverage-check-store-"));
155
- rulesPath = join(tmpDir, "rules.yml");
156
- artifactsDir = join(tmpDir, "artifacts");
157
- storeDir = join(tmpDir, "store");
158
- mkdirSync(artifactsDir);
159
- mkdirSync(storeDir);
160
- writeFileSync(rulesPath, "rules:\n - paths: backend/**\n patch_coverage_min: 90\n");
161
- store = new FileSystemSuiteStore(storeDir);
162
-
163
- // Set up a minimal git repo so HEAD is valid for diff tests
164
- const repoDir = join(tmpDir, "repo");
165
- mkdirSync(join(repoDir, "backend"), { recursive: true });
166
-
167
- savedGitEnv = {};
168
- for (const key of Object.keys(process.env)) {
169
- if (key.startsWith("GIT_")) {
170
- savedGitEnv[key] = process.env[key];
171
- delete process.env[key];
172
- }
173
- }
174
-
175
- const git = (cmd: string) =>
176
- execSync(cmd, {
177
- cwd: repoDir,
178
- shell: "/bin/sh",
179
- encoding: "utf8",
180
- env: {
181
- ...process.env,
182
- GIT_AUTHOR_NAME: "T",
183
- GIT_AUTHOR_EMAIL: "t@t.com",
184
- GIT_COMMITTER_NAME: "T",
185
- GIT_COMMITTER_EMAIL: "t@t.com",
186
- },
187
- });
188
- git("git init");
189
- writeFileSync(join(repoDir, "backend", "foo.mts"), "const a = 1\n");
190
- git('git add . && git commit -m "init"');
191
-
192
- origCwd = process.cwd();
193
- process.chdir(repoDir);
194
- });
195
-
196
- afterEach(() => {
197
- process.chdir(origCwd);
198
- for (const [key, val] of Object.entries(savedGitEnv)) {
199
- if (val !== undefined) process.env[key] = val;
200
- }
201
- rmSync(tmpDir, { recursive: true, force: true });
202
- });
203
-
204
- it("returns 0 when store has no suites and artifacts is empty", async () => {
205
- expect(
206
- await runCheck({
207
- rules: rulesPath,
208
- artifacts: artifactsDir,
209
- base: "HEAD",
210
- head: "HEAD",
211
- pr: null,
212
- repo: "",
213
- json: null,
214
- stripPrefixes: [],
215
- store,
216
- suite: null,
217
- }),
218
- ).toBe(0);
219
- });
220
-
221
- it("returns 0 when store has suites but diff is empty (base=head)", async () => {
222
- await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"));
223
- expect(
224
- await runCheck({
225
- rules: rulesPath,
226
- artifacts: artifactsDir,
227
- base: "HEAD",
228
- head: "HEAD",
229
- pr: null,
230
- repo: "",
231
- json: null,
232
- stripPrefixes: [],
233
- store,
234
- suite: null,
235
- }),
236
- ).toBe(0);
237
- });
238
-
239
- it("excludes the current suite from the store during check", async () => {
240
- // Store has "backend" suite with coverage; current artifacts is empty
241
- // With --suite=backend, the stored backend coverage should be excluded
242
- await store.put("backend", Buffer.from("SF:backend/foo.mts\nDA:1,1\nend_of_record\n"));
243
- await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"));
244
-
245
- expect(
246
- await runCheck({
247
- rules: rulesPath,
248
- artifacts: artifactsDir, // empty
249
- base: "HEAD",
250
- head: "HEAD",
251
- pr: null,
252
- repo: "",
253
- json: null,
254
- stripPrefixes: [],
255
- store,
256
- suite: "backend", // excludes the stored backend suite
257
- }),
258
- ).toBe(0);
259
- });
260
-
261
- it("includes non-current suites from the store", async () => {
262
- // Put a "frontend" suite in the store; artifacts has backend coverage
263
- await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"));
264
- writeFileSync(join(artifactsDir, "lcov.info"), "SF:backend/foo.mts\nDA:1,1\nend_of_record\n");
265
- // With suite=backend, frontend from store should still be merged
266
- expect(
267
- await runCheck({
268
- rules: rulesPath,
269
- artifacts: artifactsDir,
270
- base: "HEAD",
271
- head: "HEAD",
272
- pr: null,
273
- repo: "",
274
- json: null,
275
- stripPrefixes: [],
276
- store,
277
- suite: "backend",
278
- }),
279
- ).toBe(0);
280
- });
281
-
282
- it("handles a store that returns null from get() gracefully", async () => {
283
- // Custom store: list returns a suite, but get returns null (e.g. file was deleted)
284
- const nullStore = {
285
- async list() {
286
- return ["backend"];
287
- },
288
- async get(_suite: string) {
289
- return null;
290
- },
291
- async put() {},
292
- };
293
- // No local artifacts + store returns null → no reports → return 0
294
- expect(
295
- await runCheck({
296
- rules: rulesPath,
297
- artifacts: artifactsDir,
298
- base: "HEAD",
299
- head: "HEAD",
300
- pr: null,
301
- repo: "",
302
- json: null,
303
- stripPrefixes: [],
304
- store: nullStore,
305
- suite: null,
306
- }),
307
- ).toBe(0);
308
- });
309
-
310
- it("constructs a real runUrl when GITHUB_SERVER_URL and GITHUB_RUN_ID are set", async () => {
311
- await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"));
312
-
313
- const calls: string[][] = [];
314
- const gh = async (args: string[]) => {
315
- calls.push(args);
316
- return "";
317
- };
318
-
319
- const origServer = process.env["GITHUB_SERVER_URL"];
320
- const origRunId = process.env["GITHUB_RUN_ID"];
321
- process.env["GITHUB_SERVER_URL"] = "https://github.com";
322
- process.env["GITHUB_RUN_ID"] = "12345";
323
-
324
- try {
325
- // With frontend from store and empty diff (HEAD=HEAD), passes
326
- await runCheck({
327
- rules: rulesPath,
328
- artifacts: artifactsDir,
329
- base: "HEAD",
330
- head: "HEAD",
331
- pr: 1,
332
- repo: "owner/repo",
333
- json: null,
334
- stripPrefixes: [],
335
- store,
336
- suite: null,
337
- gh,
338
- });
339
- // gh was called (lookup for existing comment)
340
- expect(calls.length).toBeGreaterThanOrEqual(1);
341
- } finally {
342
- if (origServer === undefined) delete process.env["GITHUB_SERVER_URL"];
343
- else process.env["GITHUB_SERVER_URL"] = origServer;
344
- if (origRunId === undefined) delete process.env["GITHUB_RUN_ID"];
345
- else process.env["GITHUB_RUN_ID"] = origRunId;
346
- }
347
- });
348
-
349
- it("accepts --store and --suite flags via main()", async () => {
350
- // --store and --suite valid flags, no lcov → returns 0
351
- expect(
352
- await main([
353
- "--rules",
354
- rulesPath,
355
- "--artifacts",
356
- artifactsDir,
357
- "--store",
358
- storeDir,
359
- "--suite",
360
- "backend",
361
- ]),
362
- ).toBe(0);
363
- });
364
- });
365
-
366
- describe("with a real git repo and a known diff", () => {
367
- let tmpDir: string;
368
- let rulesPath: string;
369
- let artifactsDir: string;
370
- let repoDir: string;
371
- let origCwd: string;
372
- let baseSha: string;
373
- let headSha: string;
374
- let savedGitEnv: Record<string, string | undefined>;
375
-
376
- beforeEach(() => {
377
- tmpDir = mkdtempSync(join(tmpdir(), "coverage-check-git-"));
378
- rulesPath = join(tmpDir, "rules.yml");
379
- artifactsDir = join(tmpDir, "artifacts");
380
- mkdirSync(artifactsDir);
381
- writeFileSync(rulesPath, "rules:\n - paths: backend/**\n patch_coverage_min: 90\n");
382
-
383
- repoDir = join(tmpDir, "repo");
384
- mkdirSync(join(repoDir, "backend"), { recursive: true });
385
-
386
- savedGitEnv = {};
387
- for (const key of Object.keys(process.env)) {
388
- if (key.startsWith("GIT_")) {
389
- savedGitEnv[key] = process.env[key];
390
- delete process.env[key];
391
- }
392
- }
393
-
394
- const git = (cmd: string) =>
395
- execSync(cmd, {
396
- cwd: repoDir,
397
- shell: "/bin/sh",
398
- encoding: "utf8",
399
- env: {
400
- ...process.env,
401
- GIT_AUTHOR_NAME: "T",
402
- GIT_AUTHOR_EMAIL: "t@t.com",
403
- GIT_COMMITTER_NAME: "T",
404
- GIT_COMMITTER_EMAIL: "t@t.com",
405
- },
406
- });
407
-
408
- git("git init");
409
- writeFileSync(join(repoDir, "backend/foo.mts"), "const a = 1\n");
410
- git('git add . && git commit -m "base"');
411
- baseSha = git("git rev-parse HEAD").trim();
412
-
413
- writeFileSync(join(repoDir, "backend/foo.mts"), "const a = 1\nconst b = 2\n");
414
- git('git add . && git commit -m "head"');
415
- headSha = git("git rev-parse HEAD").trim();
416
-
417
- origCwd = process.cwd();
418
- process.chdir(repoDir);
419
- });
420
-
421
- afterEach(() => {
422
- process.chdir(origCwd);
423
- for (const [key, val] of Object.entries(savedGitEnv)) {
424
- if (val !== undefined) process.env[key] = val;
425
- }
426
- rmSync(tmpDir, { recursive: true, force: true });
427
- });
428
-
429
- it("returns 0 when diff is empty (base equals head)", async () => {
430
- writeFileSync(join(artifactsDir, "lcov.info"), "SF:backend/foo.mts\nDA:1,1\nend_of_record\n");
431
- expect(
432
- await main([
433
- "--rules",
434
- rulesPath,
435
- "--artifacts",
436
- artifactsDir,
437
- "--base",
438
- "HEAD",
439
- "--head",
440
- "HEAD",
441
- ]),
442
- ).toBe(0);
443
- });
444
-
445
- it("returns 1 when new lines are uncovered and below threshold", async () => {
446
- writeFileSync(
447
- join(artifactsDir, "lcov.info"),
448
- "SF:backend/foo.mts\nDA:1,1\nDA:2,0\nend_of_record\n",
449
- );
450
- expect(
451
- await main([
452
- "--rules",
453
- rulesPath,
454
- "--artifacts",
455
- artifactsDir,
456
- "--base",
457
- baseSha,
458
- "--head",
459
- headSha,
460
- ]),
461
- ).toBe(1);
462
- });
463
-
464
- it("returns 0 when all new lines are covered and above threshold", async () => {
465
- writeFileSync(
466
- join(artifactsDir, "lcov.info"),
467
- "SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
468
- );
469
- expect(
470
- await main([
471
- "--rules",
472
- rulesPath,
473
- "--artifacts",
474
- artifactsDir,
475
- "--base",
476
- baseSha,
477
- "--head",
478
- headSha,
479
- ]),
480
- ).toBe(0);
481
- });
482
-
483
- it("writes json output to the path specified by --json", async () => {
484
- writeFileSync(
485
- join(artifactsDir, "lcov.info"),
486
- "SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
487
- );
488
- const jsonPath = join(tmpDir, "result.json");
489
- await main([
490
- "--rules",
491
- rulesPath,
492
- "--artifacts",
493
- artifactsDir,
494
- "--base",
495
- baseSha,
496
- "--head",
497
- headSha,
498
- "--json",
499
- jsonPath,
500
- ]);
501
- const result = JSON.parse(readFileSync(jsonPath, "utf8"));
502
- expect(result.passed).toBe(true);
503
- expect(Array.isArray(result.buckets)).toBe(true);
504
- });
505
-
506
- it("posts PR comment via injectable gh runner on failure", async () => {
507
- writeFileSync(
508
- join(artifactsDir, "lcov.info"),
509
- "SF:backend/foo.mts\nDA:1,1\nDA:2,0\nend_of_record\n",
510
- );
511
- const calls: string[][] = [];
512
- const gh = async (args: string[]) => {
513
- calls.push(args);
514
- return "";
515
- };
516
- const result = await runCheck({
517
- rules: rulesPath,
518
- artifacts: artifactsDir,
519
- base: baseSha,
520
- head: headSha,
521
- pr: 42,
522
- repo: "owner/repo",
523
- json: null,
524
- stripPrefixes: [],
525
- store: null,
526
- suite: null,
527
- gh,
528
- });
529
- expect(result).toBe(1);
530
- // gh was called: first to look up existing comment, then to post
531
- expect(calls.length).toBeGreaterThanOrEqual(1);
532
- });
533
-
534
- it("posts PR comment via injectable gh runner on pass", async () => {
535
- writeFileSync(
536
- join(artifactsDir, "lcov.info"),
537
- "SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
538
- );
539
- const calls: string[][] = [];
540
- const gh = async (args: string[]) => {
541
- calls.push(args);
542
- return "";
543
- };
544
- const result = await runCheck({
545
- rules: rulesPath,
546
- artifacts: artifactsDir,
547
- base: baseSha,
548
- head: headSha,
549
- pr: 42,
550
- repo: "owner/repo",
551
- json: null,
552
- stripPrefixes: [],
553
- store: null,
554
- suite: null,
555
- gh,
556
- });
557
- expect(result).toBe(0);
558
- // On pass with no existing comment, gh is called once (lookup only)
559
- expect(calls.length).toBe(1);
560
- });
561
-
562
- it("handles gh error gracefully (stderr write, still returns correct exit code)", async () => {
563
- writeFileSync(
564
- join(artifactsDir, "lcov.info"),
565
- "SF:backend/foo.mts\nDA:1,1\nDA:2,0\nend_of_record\n",
566
- );
567
- const gh = async (_args: string[]): Promise<string> => {
568
- throw new Error("network error");
569
- };
570
- const result = await runCheck({
571
- rules: rulesPath,
572
- artifacts: artifactsDir,
573
- base: baseSha,
574
- head: headSha,
575
- pr: 42,
576
- repo: "owner/repo",
577
- json: null,
578
- stripPrefixes: [],
579
- store: null,
580
- suite: null,
581
- gh,
582
- });
583
- // Still returns 1 even though gh call failed
584
- expect(result).toBe(1);
585
- });
586
-
587
- it.skipIf(
588
- process.env.PR_AUTHOR === "dependabot[bot]" ||
589
- (!process.env.GH_TOKEN && !process.env.GITHUB_TOKEN),
590
- )("attempts pr comment on failure and handles gh error gracefully", async () => {
591
- writeFileSync(
592
- join(artifactsDir, "lcov.info"),
593
- "SF:backend/foo.mts\nDA:1,1\nDA:2,0\nend_of_record\n",
594
- );
595
- expect(
596
- await main([
597
- "--rules",
598
- rulesPath,
599
- "--artifacts",
600
- artifactsDir,
601
- "--base",
602
- baseSha,
603
- "--head",
604
- headSha,
605
- "--pr",
606
- "1",
607
- "--repo",
608
- "owner/NONEXISTENT_REPO_FOR_TEST",
609
- ]),
610
- ).toBe(1);
611
- });
612
-
613
- it("combines store suites with current artifacts for coverage check", async () => {
614
- const storeDir = join(tmpDir, "store");
615
- mkdirSync(storeDir);
616
- const store = new FileSystemSuiteStore(storeDir);
617
-
618
- // Store has frontend coverage (unrelated to the diff)
619
- await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"));
620
-
621
- // Current artifacts has backend coverage (line 2 now covered)
622
- writeFileSync(
623
- join(artifactsDir, "lcov.info"),
624
- "SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
625
- );
626
-
627
- expect(
628
- await runCheck({
629
- rules: rulesPath,
630
- artifacts: artifactsDir,
631
- base: baseSha,
632
- head: headSha,
633
- pr: null,
634
- repo: "",
635
- json: null,
636
- stripPrefixes: [],
637
- store,
638
- suite: "backend",
639
- }),
640
- ).toBe(0);
641
- });
642
- });