Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 10s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 21s
review-verdict/h10 Review-verdict: MERGEABLE @ bd69b4f (base: main)
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
Review verdict / Set review-verdict status (pull_request_target) Successful in 7s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 11s
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 7m23s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m3s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m52s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m58s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 6s
Round 15 returned MERGEABLE — no blocker, no HIGH, no MEDIUM, the mechanism re-verified end to end and residual (6)'s correction measurably true in every clause. Two LOW prose inaccuracies, both of the two-copy-drift class that has blocked six rounds, so they are fixed rather than shipped. `trackedSourceFiles.realgit.test.ts`'s header was the fourth copy of the checkout-versus-binary framing that `docker/Dockerfile` and `docs/testing.md` both explicitly retract — the reading that broke the Docker stage once already. It is also the header a reader consults when changing that exclusion, which is exactly the scenario the repair was needed for. Measured again here: with `.git` present and git off `PATH`, the guard still fails with the plugin's refusal, so it needs a checkout AND, through it, the binary. The realgit pair is the half that needs the binary and NO checkout. `docs/guard-inventory.md`'s scope item said "four files" and then named five — in the paragraph whose whole subject is a hand-written mirror going stale, and one table down from the FIVE-vs-SIX count the previous commit fixed. Refs: #819
98 lines
5.0 KiB
TypeScript
98 lines
5.0 KiB
TypeScript
import { execFileSync } from 'node:child_process';
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { afterAll, describe, expect, it } from 'vitest';
|
|
import { type GitRunner, resolveTrackedSourceFiles } from './trackedSourceFiles';
|
|
|
|
/**
|
|
* SEPARATE FILE because these are the only tests in the SPA suite that need the `git` BINARY but NO
|
|
* CHECKOUT — they build their own repository (ersatztv#819). That is not the same as saying the
|
|
* guard needs only a checkout: it needs a checkout AND, through it, the binary. Measured — with
|
|
* `.git` present and git off `PATH`, `pageSizeCallSites.guard.test.ts` still fails with the
|
|
* plugin's refusal. Reading the two exclusions as checkout-versus-binary is what broke the Docker
|
|
* stage once already; see `docker/Dockerfile`, which retracts that framing.
|
|
*
|
|
* `docker/Dockerfile`'s web-build stage runs on `node:22-bookworm-slim`, which ships no git at all,
|
|
* so this file is excluded there alongside the guard. Keeping them apart means
|
|
* that exclusion costs two files rather than the thirteen injected-runner tests in
|
|
* `trackedSourceFiles.test.ts`, which run fine with no git and no repository.
|
|
*
|
|
* Do NOT make these skip when git is missing. A check that answers "could not tell" by passing is a
|
|
* permanent no-op, which is the whole failure mode this plugin exists to avoid.
|
|
*/
|
|
describe('resolveTrackedSourceFiles against a REAL repository (#819)', () => {
|
|
// Every test in the sibling `trackedSourceFiles.test.ts` injects a fake `GitRunner`, which proves
|
|
// the derivation's SHAPE and never that
|
|
// it works against git. `scripts/tests/test_guard_populations_derive_from_git.py` makes exactly
|
|
// this point about its own primitive — "It proves the mechanism by EXECUTING it rather than by
|
|
// recognising its shape — no monkeypatching, no stand-in for git" — and the population this
|
|
// plugin feeds is asserted for completeness, so it earns the same treatment.
|
|
const repos: string[] = [];
|
|
|
|
afterAll(() => {
|
|
for (const dir of repos) {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function repoWithOneTrackedAndOneUntracked(): string {
|
|
const dir = mkdtempSync(join(tmpdir(), 'etv-819-'));
|
|
repos.push(dir);
|
|
const git = (...args: string[]) => execFileSync('git', ['-C', dir, ...args], { encoding: 'utf8' });
|
|
|
|
git('init', '-q');
|
|
mkdirSync(join(dir, 'src'), { recursive: true });
|
|
writeFileSync(join(dir, 'src', 'tracked.ts'), 'export const a = 1;\n');
|
|
writeFileSync(join(dir, 'src', 'untracked.ts'), 'export const b = 2;\n');
|
|
// STAGED, not committed. `ls-files` reads the INDEX, so a commit adds nothing — and committing
|
|
// makes the fixture depend on the developer's global git config: under a `commit.gpgsign = true`
|
|
// that many people set, `git commit` fails and these tests go red on their machine while CI
|
|
// stays green. That is the very shape #819 exists to remove, so it must not be reintroduced by
|
|
// #819's own tests. `git add` is unaffected by signing.
|
|
git('add', 'src/tracked.ts');
|
|
return dir;
|
|
}
|
|
|
|
it('really separates a tracked file from an identical untracked sibling', () => {
|
|
const result = resolveTrackedSourceFiles(repoWithOneTrackedAndOneUntracked());
|
|
|
|
expect(result.tracked).toEqual(['src/tracked.ts']);
|
|
expect(result.others).toEqual(['src/untracked.ts']);
|
|
expect(result.absentFromDisk).toEqual([]);
|
|
});
|
|
|
|
it('really reports a tracked file deleted from the working tree as absent', () => {
|
|
const dir = repoWithOneTrackedAndOneUntracked();
|
|
rmSync(join(dir, 'src', 'tracked.ts'));
|
|
|
|
const result = resolveTrackedSourceFiles(dir);
|
|
|
|
expect(result.tracked).toEqual(['src/tracked.ts']);
|
|
expect(result.absentFromDisk).toEqual(['src/tracked.ts']);
|
|
});
|
|
|
|
// Residual: this asserts the message the MISSING-BINARY path also emits, so in an environment with
|
|
// no git it passes for the wrong reason. Not silent overall — its two siblings fail ENOENT there,
|
|
// loudly — and that is exactly the environment this file is excluded from in `docker/Dockerfile`.
|
|
it('really throws outside a repository, rather than returning an empty population', () => {
|
|
const notARepo = mkdtempSync(join(tmpdir(), 'etv-819-bare-'));
|
|
repos.push(notARepo);
|
|
mkdirSync(join(notARepo, 'src'), { recursive: true });
|
|
|
|
// `GIT_CEILING_DIRECTORIES` stops git walking up into an enclosing repository — without it this
|
|
// test depends on where TMPDIR happens to point, and reports the wrong throw when TMPDIR sits
|
|
// inside a checkout.
|
|
const ceiling: GitRunner = (args) =>
|
|
execFileSync('git', [...args], {
|
|
encoding: 'utf8',
|
|
// Same stderr suppression `runGitDefault` sets, for the same reason: without it git's own
|
|
// `fatal: not a git repository` prints on every run of this file.
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
env: { ...process.env, GIT_CEILING_DIRECTORIES: tmpdir() }
|
|
});
|
|
|
|
expect(() => resolveTrackedSourceFiles(notARepo, ceiling)).toThrow(/could not read the git index/);
|
|
});
|
|
});
|