fix(test): make the template fixture tests self-contained and portable

useScratchTmpdir only redirected TMPDIR, so on Windows os.tmpdir() kept
reading TEMP/TMP and the fallback test asserted on paths outside the
scratch root. It now sets and restores TMPDIR, TEMP and TMP together, and
the fallback test checks the redirect before it asserts on paths.

The timestamp-refresh test assumed an earlier test had published a
template. Under PAPERCLIP_TEST_POSTGRES_TEMPLATE=0 the support probe does
not publish one, so running the test alone threw ENOENT from utimes. It
now starts and cleans up a suite first, which builds the template when
nothing has.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
devinfoleyandClaude Fable 5.1 committed 2026-10-11 08:01:44 +00:00
1 parent b600ed2748
commit e8f8e9c8f3
1 file changed
+22 -6
+22 -6
View File
@@ -235,18 +235,23 @@ const support = await getEmbeddedPostgresTestSupport();
const describeWithPostgres = support.supported ? describe : describe.skip;
// Points `os.tmpdir()` at a private scratch directory so a test can drive the
// cache root from an empty state. POSIX `os.tmpdir()` reads TMPDIR on every
// call. Returns the scratch directory; `restore` puts the environment and the
// cache root from an empty state. `os.tmpdir()` reads the environment on every
// call: TMPDIR on POSIX, TEMP and then TMP on Windows, so all three are set.
// Returns the scratch directory; `restore` puts the environment and the
// fixture's cached state back.
const TMPDIR_VARIABLES = ["TMPDIR", "TEMP", "TMP"] as const;
function useScratchTmpdir(prefix: string): { scratch: string; restore(): void } {
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
const previous = process.env.TMPDIR;
process.env.TMPDIR = scratch;
const previous = new Map(TMPDIR_VARIABLES.map((name) => [name, process.env[name]] as const));
for (const name of TMPDIR_VARIABLES) process.env[name] = scratch;
return {
scratch,
restore() {
if (previous === undefined) delete process.env.TMPDIR;
else process.env.TMPDIR = previous;
for (const [name, value] of previous) {
if (value === undefined) delete process.env[name];
else process.env[name] = value;
}
resetTemplateState();
fs.rmSync(scratch, { recursive: true, force: true });
},
@@ -314,7 +319,15 @@ describeWithPostgres("startEmbeddedPostgresTestDatabase template path", () => {
it("refreshes the template's timestamp on every reuse so idle time drives pruning", { timeout: EMBEDDED_POSTGRES_TEST_TIMEOUT_MS }, async () => {
delete process.env.PAPERCLIP_TEST_POSTGRES_TEMPLATE;
// Make sure a template exists before ageing it: the support probe does
// not publish one under PAPERCLIP_TEST_POSTGRES_TEMPLATE=0, and a name
// filter can run this test on its own.
resetTemplateState();
const first = await startEmbeddedPostgresTestDatabase("paperclip-template-touch-");
await first.cleanup();
expect(lastStartMode()).toBe("template");
const templateDir = path.join(templateRootDir(), computeEmbeddedPostgresTemplateKey());
expect(isUsableTemplate(templateDir)).toBe(true);
// Age the template as if it had not been used for ten days.
const old = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000);
fs.utimesSync(templateDir, old, old);
@@ -449,6 +462,9 @@ describe("support probe when the template cannot be built", () => {
warnings.push(args.map(String).join(" "));
};
try {
// This test runs on every platform, so check the redirect took effect
// before asserting on paths under it.
expect(os.tmpdir()).toBe(scratch.scratch);
// The fake cluster "starts" without listening, so the template build
// fails after the start, at the migration step, the way a broken
// migration would. A real broken migration then fails the suite on