import { readFileSync } from "node:fs"; import { expect, it } from "vitest"; // The Python major is a contract, not a coincidence — the host half. The twin of // `nodeEngine.contract.test.ts`, and written the day after that one was: `0.93.2` shipped because // four machines disagreed about Node, and the Python side turned out to have the identical shape // with nobody watching. Prod (`Dockerfile`), the test image (`backend/Dockerfile.test`) and CI // (`check.yml`) each spelled 3.12 as its own literal, there was no `.python-version`, and the dev // machine had only 3.13 and 3.14 installed — so every ad-hoc host script ran two majors ahead of // production. // // It lives in the FRONTEND suite for one boring reason: this is the only runner that can see the // whole repository. The backend suite runs inside the test image, which mounts `backend/` alone, so // it cannot read the repo-root Dockerfile. It holds the other half of the chain — // including "the interpreter running these tests IS the pinned one", which only it can assert: // // backend/tests/test_python_engine_contract.py : running interpreter == pyrightconfig == ruff.toml // here : .python-version == pyrightconfig == ruff.toml // == Dockerfile == Dockerfile.test // // To move the version: edit `.python-version`, then follow the failures in both files. const readRoot = (rel: string) => readFileSync(new URL(`../../../${rel}`, import.meta.url), "utf8"); function versionIn(source: string, pattern: RegExp, where: string): string { const m = pattern.exec(source); // A rename or a moved line lands here. Re-point the test — the contract did not go away with it. expect(m, `${pattern} not found in ${where}`).not.toBeNull(); return `${m![1]}.${m![2]}`; } const pinned = versionIn(readRoot(".python-version"), /^(\d+)\.(\d+)\s*$/m, ".python-version"); it("pins a plausible Python version", () => { // Guards against a typo'd `.python-version` silently agreeing with nothing. expect(Number(pinned.split(".")[1])).toBeGreaterThanOrEqual(12); expect(pinned.split(".")[0]).toBe("3"); }); it("runs the shipped backend on the pinned version", () => { // The image our users' instance actually runs. expect(versionIn(readRoot("Dockerfile"), /^FROM python:(\d+)\.(\d+)-slim/m, "Dockerfile")).toBe( pinned, ); }); it("tests on the pinned version", () => { // The image the whole pytest suite runs in — including the backend half of this contract, which // asserts its own `sys.version_info` against the pin. expect( versionIn( readRoot("backend/Dockerfile.test"), /^FROM python:(\d+)\.(\d+)-slim/m, "Dockerfile.test", ), ).toBe(pinned); }); it("type-checks against the pinned version", () => { // A type checker aimed at another version green-lights APIs the shipped interpreter does not have // — the same defect as the image split, moved to check time. expect( versionIn( readRoot("backend/pyrightconfig.json"), /"pythonVersion"\s*:\s*"(\d+)\.(\d+)"/, "pyrightconfig.json", ), ).toBe(pinned); }); it("lints against the pinned version", () => { expect( versionIn(readRoot("backend/ruff.toml"), /^target-version = "py(\d)(\d+)"$/m, "ruff.toml"), ).toBe(pinned); }); it("resolves the lock on the pinned version", () => { // The seventh spelling, and the sneakiest: `requirements.lock`'s own regeneration recipe names an // image. Wheels and environment markers are resolved by THAT interpreter, so a recipe left behind // produces a lock that does not match the image installing it — the same split as the others, one // indirection further out, and with nothing red to show for it. expect( versionIn( readRoot("backend/requirements.lock"), /docker run [^\n]*python:(\d+)\.(\d+)-slim/, "requirements.lock", ), ).toBe(pinned); });