Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 78 additions & 31 deletions test/docs-set.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";

Expand Down Expand Up @@ -55,6 +55,37 @@ function graded(files: Record<string, string>, arms: readonly ArmName[]): Findin
}
}

/**
* Grade a control against a `package.json` that is this repository's own with only its `version`
* replaced, so the package-agreement controls say the same thing whatever version the tree is at.
* The gate reads a `0.0.x` version as not yet on the public registry and anything later as on it,
* so each control is graded on both sides of that line rather than on whichever side the tree
* happens to sit.
*/
function gradedAtVersion(
files: Record<string, string>,
arms: readonly ArmName[],
version: string,
): Finding[] {
const dir = mkdtempSync(join(tmpdir(), "fhir-docs-control-"));
const manifestDir = mkdtempSync(join(tmpdir(), "fhir-docs-manifest-"));
try {
for (const [name, body] of Object.entries(files)) seed(dir, name, body);
const manifest = JSON.parse(readFileSync(join(REPO_ROOT, "package.json"), "utf8")) as object;
const packageJsonPath = join(manifestDir, "package.json");
writeFileSync(packageJsonPath, JSON.stringify({ ...manifest, version }));
return checkDocsSet(dir, { arms, packageJsonPath });
} finally {
rmSync(dir, { recursive: true, force: true });
rmSync(manifestDir, { recursive: true, force: true });
}
}

/** A `0.0.x` version, which the gate reads as not on the public registry. */
const UNPUBLISHED_LINE = "0.0.11";
/** A version past `0.0.x`, which the gate reads as on the public registry. */
const PUBLISHED_LINE = "0.1.0";

/**
* Grade a control against an allow-list that declares nothing.
*
Expand Down Expand Up @@ -234,45 +265,61 @@ describe("every arm goes red on a seeded control", () => {
expect(messages(findings)).toContain("every sidebar item must be a page id string");
});

/** An installation page stating a Node range and exactly one registry claim. */
const installationPage = (nodeRange: string, claim: string): Record<string, string> => ({
"installation.md": page(
"installation",
"Installation",
[
"Install `@cosyte/fhir` with `pnpm add @cosyte/fhir`.",
"",
`It needs Node \`${nodeRange}\`, ships ESM, and also offers CommonJS.`,
"",
claim,
].join("\n"),
),
});
const INSTALLABLE = "The package is installable from the public npm registry today.";
const NOT_INSTALLABLE = "The package is not installable from the public npm registry today.";

it("package-agreement: a Node range and a registry claim that disagree with package.json", () => {
const findings = graded(
{
"installation.md": page(
"installation",
"Installation",
[
"Install `@cosyte/fhir` with `pnpm add @cosyte/fhir`.",
"",
"It needs Node `>=18.0.0`, ships ESM, and also offers CommonJS.",
"",
"The package is installable from the public npm registry today.",
].join("\n"),
),
},
const findings = gradedAtVersion(
installationPage(">=18.0.0", INSTALLABLE),
["package-agreement"],
UNPUBLISHED_LINE,
);
expect(messages(findings)).toContain('does not state the supported Node range ">=22.0.0"');
expect(messages(findings)).toContain("disagrees with package.json");
});

it("package-agreement: the truthful page passes the same arm", () => {
const findings = graded(
{
"installation.md": page(
"installation",
"Installation",
[
"Install `@cosyte/fhir` with `pnpm add @cosyte/fhir`.",
"",
"It needs Node `>=22.0.0`, ships ESM, and also offers CommonJS.",
"",
"The package is not installable from the public npm registry today.",
].join("\n"),
),
},
it("package-agreement: the opposite registry claim disagrees once the version leaves 0.0.x", () => {
const findings = gradedAtVersion(
installationPage(">=22.0.0", NOT_INSTALLABLE),
["package-agreement"],
PUBLISHED_LINE,
);
expect(messages(findings)).toBe("");
expect(messages(findings)).toContain("disagrees with package.json");
});

it("package-agreement: the truthful page passes the same arm", () => {
expect(
messages(
gradedAtVersion(
installationPage(">=22.0.0", NOT_INSTALLABLE),
["package-agreement"],
UNPUBLISHED_LINE,
),
),
).toBe("");
expect(
messages(
gradedAtVersion(
installationPage(">=22.0.0", INSTALLABLE),
["package-agreement"],
PUBLISHED_LINE,
),
),
).toBe("");
});

it("synthetic-identifiers: every identifying element is graded against the declaration", () => {
Expand Down
47 changes: 41 additions & 6 deletions test/release-readiness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1466,19 +1466,54 @@ describe("the exports map publishes exactly the certified subpaths", () => {
describe("this tree performs no publication and makes none inevitable", () => {
/**
* The version measured on this tree when the audit was written. It is asserted rather than
* derived so that a bump ANYWHERE reds here: the point is that this change did not move it.
* `test/sanity.test.ts` separately asserts VERSION against package.json; this is the different
* question of whether either one MOVED, and the two coexist.
* derived so that a bump the audit did not certify reds here. `test/sanity.test.ts` separately
* asserts VERSION against package.json; this is the different question of whether either one
* MOVED, and the two coexist.
*/
const MEASURED_VERSION = "0.0.11";

it("leaves package.json at the version this work measured", () => {
expect(PACKAGE_JSON.version).toBe(MEASURED_VERSION);
/**
* The one move the audit sanctions: the release it certified, read from the committed surface
* inventory rather than restated, and patches on that release's line. Changesets' version commit
* moves `package.json` and the `VERSION` export together, so the release commit reads the
* certified version and must pass here. A patch cannot change a public observable under the
* classification rule above, and the surface tests hold the certified surface exactly in every
* state, so a patch on the certified line keeps the certification. A new minor (or any other
* version) is a release the audit never certified, and it reds until the surface is certified
* for it.
*/
function isCertifiedMove(version: string): boolean {
const certified = /^(\d+)\.(\d+)\.(\d+)$/.exec(INVENTORY.certifiedFor);
const candidate = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
if (certified === null || candidate === null) return false;
return (
candidate[1] === certified[1] &&
candidate[2] === certified[2] &&
Number(candidate[3]) >= Number(certified[3])
);
}

const accountedFor = (version: string): boolean =>
version === MEASURED_VERSION || isCertifiedMove(version);

it("leaves package.json at the version this work measured, or on the release line it certified", () => {
expect(
accountedFor(PACKAGE_JSON.version),
`package.json reads ${PACKAGE_JSON.version}, which is neither the measured ${MEASURED_VERSION} nor on the ${INVENTORY.certifiedFor} line the audit certified`,
).toBe(true);
});

it("leaves the VERSION export agreeing with it", () => {
expect(VERSION).toBe(PACKAGE_JSON.version);
expect(VERSION).toBe(MEASURED_VERSION);
expect(accountedFor(VERSION)).toBe(true);
});

it("has teeth: a version the audit neither measured nor certified is refused", () => {
expect(accountedFor(MEASURED_VERSION)).toBe(true);
expect(accountedFor(INVENTORY.certifiedFor)).toBe(true);
for (const uncertified of ["0.0.12", "0.0.10", "0.2.0", "1.0.0", "0.1.0-next.0", "0.1"]) {
expect(accountedFor(uncertified), uncertified).toBe(false);
}
});

it("leaves every classified changeset still pending in .changeset/", () => {
Expand Down
7 changes: 5 additions & 2 deletions test/sanity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ describe("@cosyte/fhir scaffold", () => {
expect(VERSION).toBe(pkg.version);
});

it("starts on the v0.0.x pre-alpha ladder", () => {
expect(VERSION).toMatch(/^0\.0\.\d+$/);
it("stays on the 0.x line, where a breaking change bumps the minor version", () => {
// The operator moved every package to 0.1.x, so the ladder this pins is the 0.x one: a plain
// `0.minor.patch`, never a 1.0, a major or a prerelease. The classification rule in
// `test/release-readiness.test.ts` is what keeps a break off the patch position.
expect(VERSION).toMatch(/^0\.\d+\.\d+$/);
});
});
Loading