QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 7s
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Failing after 6s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
Save the current implementation and README, status, and known-issue documentation at the user-requested stop. No completed release or deployment is claimed. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: GPT-6 Codex <noreply@openai.com>
107 lines
4.4 KiB
JavaScript
107 lines
4.4 KiB
JavaScript
/* Copyright (C) 2026 Kingsrook, LLC.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
import assert from "node:assert/strict";
|
|
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { AttemptJournal, ATTEMPT_LABEL, OWNER_LABEL, cleanAttempt, reapExpired } from "../dist/lifecycle.js";
|
|
|
|
async function fixture(t)
|
|
{
|
|
const directory = await mkdtemp(path.join(tmpdir(), "qqq-journal-test-"));
|
|
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
// Windows test host lacks POSIX directory fsync; inject only that operation.
|
|
// File writes still use fsync. Production's default never skips directory fsync.
|
|
const journal = new AttemptJournal(directory, async () => undefined);
|
|
return { directory, journal };
|
|
}
|
|
|
|
function identity(attempt)
|
|
{
|
|
return { id: "a".repeat(64), name: "/" + attempt.name, labels: { [OWNER_LABEL]: attempt.owner, [ATTEMPT_LABEL]: attempt.id } };
|
|
}
|
|
|
|
test("restart recovers a create whose ID response was lost and removes exact owned ID", async t =>
|
|
{
|
|
const { directory, journal } = await fixture(t);
|
|
const attempt = await journal.prepare("deployment-a", 1000);
|
|
const restarted = new AttemptJournal(directory, async () => undefined);
|
|
const removed = [];
|
|
const control = {
|
|
inspect: async name =>
|
|
{
|
|
assert.equal(name, attempt.name);
|
|
return identity(attempt);
|
|
},
|
|
remove: async id => removed.push(id),
|
|
};
|
|
assert.deepEqual(await reapExpired(restarted, "deployment-a", control, 61000), { cleaned: 1, failed: 0 });
|
|
assert.deepEqual(removed, ["a".repeat(64)]);
|
|
assert.deepEqual(await readdir(path.join(directory, "active")), []);
|
|
const events = await Promise.all((await readdir(path.join(directory, "evidence"))).map(async filename => JSON.parse(await readFile(path.join(directory, "evidence", filename), "utf8"))));
|
|
assert.deepEqual(new Set(events.map(event => event.event)), new Set(["PREPARED", "REMOVAL_REQUESTED", "REMOVED"]));
|
|
assert.ok(events.every(event => event.id === attempt.id && event.owner === "deployment-a"));
|
|
});
|
|
|
|
test("reaper never inspects live or another deployment's leases", async t =>
|
|
{
|
|
const { journal } = await fixture(t);
|
|
await journal.prepare("other", 0);
|
|
await journal.prepare("deployment-a", 60000);
|
|
const unexpected = async () =>
|
|
{
|
|
throw new Error("must not inspect/remove");
|
|
};
|
|
assert.deepEqual(await reapExpired(journal, "deployment-a", { inspect: unexpected, remove: unexpected }, 61000), { cleaned: 0, failed: 0 });
|
|
});
|
|
|
|
test("ownership mismatch never removes a reused name and retains retry evidence", async t =>
|
|
{
|
|
const { journal } = await fixture(t);
|
|
const attempt = await journal.prepare("deployment-a", 0);
|
|
let removals = 0;
|
|
const control = {
|
|
inspect: async () => ({ ...identity(attempt), labels: { [OWNER_LABEL]: "another-deployment", [ATTEMPT_LABEL]: attempt.id } }),
|
|
remove: async () => removals++,
|
|
};
|
|
assert.deepEqual(await reapExpired(journal, "deployment-a", control, 60000), { cleaned: 0, failed: 1 });
|
|
assert.equal(removals, 0);
|
|
assert.equal((await journal.expired("deployment-a", 60000)).length, 1);
|
|
});
|
|
|
|
test("failed removal remains retryable after restart, missing container closes lease", async t =>
|
|
{
|
|
const { directory, journal } = await fixture(t);
|
|
const attempt = await journal.prepare("deployment-a", 0);
|
|
await assert.rejects(cleanAttempt(journal, attempt, {
|
|
inspect: async () => identity(attempt),
|
|
remove: async () =>
|
|
{
|
|
throw new Error("daemon disconnected");
|
|
},
|
|
}), /disconnected/);
|
|
const restarted = new AttemptJournal(directory, async () => undefined);
|
|
const result = await reapExpired(restarted, "deployment-a", {
|
|
inspect: async () => undefined,
|
|
remove: async () =>
|
|
{
|
|
throw new Error("must not remove missing container");
|
|
},
|
|
}, 60000);
|
|
assert.deepEqual(result, { cleaned: 1, failed: 0 });
|
|
assert.deepEqual(await restarted.expired("deployment-a", 60000), []);
|
|
});
|
|
|
|
test("durability failure prevents preparation of an active lease", async t =>
|
|
{
|
|
const { directory } = await fixture(t);
|
|
const failing = new AttemptJournal(directory, async () =>
|
|
{
|
|
throw new Error("fsync unsupported");
|
|
});
|
|
await assert.rejects(failing.prepare("deployment-a"), /fsync unsupported/);
|
|
assert.deepEqual(await readdir(path.join(directory, "active")), []);
|
|
});
|