164 lines
4.4 KiB
JavaScript
164 lines
4.4 KiB
JavaScript
/**
|
|
* Smoke: workflow revisions, SHA dedup, trash, restore, purge.
|
|
*
|
|
* Run: pnpm --dir packages/server test:workflow-history
|
|
*/
|
|
import assert from "node:assert/strict";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { db, migrate } from "../db.js";
|
|
import { WORKFLOWS_DIR } from "../paths.js";
|
|
import * as fsStore from "../fs-store.js";
|
|
import {
|
|
workflowContentSha,
|
|
workflowIdFromFile,
|
|
newWorkflowFilename,
|
|
} from "../workflow-normalize.js";
|
|
import {
|
|
recordRevision,
|
|
listRevisions,
|
|
getLatestRevision,
|
|
deleteRevisionHistory,
|
|
ensureInitialRevision,
|
|
} from "../workflow-history.js";
|
|
import {
|
|
moveWorkflowToTrash,
|
|
listTrash,
|
|
restoreFromTrash,
|
|
purgeTrashItem,
|
|
TRASH_WORKFLOWS_DIR,
|
|
} from "../workflow-trash.js";
|
|
import { collectWorkflowWarnings } from "../workflow-validate-warnings.js";
|
|
|
|
const owner = "__workflow_history_smoke__";
|
|
const file = newWorkflowFilename();
|
|
const workflowId = workflowIdFromFile(file);
|
|
const ownerDir = path.join(WORKFLOWS_DIR, owner);
|
|
const trashPath = path.join(TRASH_WORKFLOWS_DIR, owner, file);
|
|
|
|
function cleanup() {
|
|
if (fs.existsSync(trashPath)) fs.unlinkSync(trashPath);
|
|
if (fs.existsSync(ownerDir)) fs.rmSync(ownerDir, { recursive: true, force: true });
|
|
}
|
|
|
|
cleanup();
|
|
await migrate();
|
|
|
|
const yamlV1 = `name: smoke test
|
|
scripts:
|
|
- plugin/get-current-time
|
|
triggers:
|
|
- type: HTTP
|
|
method: POST
|
|
path: /smoke
|
|
`;
|
|
|
|
fsStore.writeWorkflowYaml(owner, file, yamlV1);
|
|
fsStore.writeRegisters(owner, [file]);
|
|
|
|
assert.equal(workflowContentSha(yamlV1), workflowContentSha(`${yamlV1}\n\n`));
|
|
|
|
const rev1 = await recordRevision({
|
|
workflowId,
|
|
owner,
|
|
file,
|
|
content: yamlV1,
|
|
reason: "create",
|
|
force: true,
|
|
});
|
|
assert.equal(rev1.skipped, false);
|
|
assert.equal(rev1.revision, 1);
|
|
|
|
const revDup = await recordRevision({
|
|
workflowId,
|
|
owner,
|
|
file,
|
|
content: `${yamlV1}\n\n`,
|
|
reason: "save",
|
|
});
|
|
assert.equal(revDup.skipped, true, "normalized SHA should dedupe blank lines");
|
|
|
|
const yamlV2 = yamlV1.replace("smoke test", "smoke test v2");
|
|
const rev2 = await recordRevision({
|
|
workflowId,
|
|
owner,
|
|
file,
|
|
content: yamlV2,
|
|
reason: "save",
|
|
});
|
|
assert.equal(rev2.revision, 2);
|
|
assert.equal((await listRevisions(workflowId)).length, 2);
|
|
|
|
const yamlDisabled = `${yamlV2}\nenabled: false\n`;
|
|
assert.equal(
|
|
workflowContentSha(yamlV2),
|
|
workflowContentSha(yamlDisabled),
|
|
"enabled-only change should not change content SHA",
|
|
);
|
|
const revDisable = await recordRevision({
|
|
workflowId,
|
|
owner,
|
|
file,
|
|
content: yamlDisabled,
|
|
reason: "disable",
|
|
});
|
|
assert.equal(revDisable.skipped, true, "enable/disable should not create a revision");
|
|
assert.equal((await listRevisions(workflowId)).length, 2);
|
|
|
|
const { startRun, getRun } = await import("../store.js");
|
|
const latest = await getLatestRevision(workflowId);
|
|
assert.equal(latest?.revision, 2);
|
|
const run = await startRun({
|
|
owner,
|
|
workflow: `${owner}/${file}`,
|
|
workflowName: "smoke test v2",
|
|
trigger: { type: "manual", detail: "smoke" },
|
|
input: null,
|
|
workflowRevision: latest?.revision ?? null,
|
|
});
|
|
const loaded = await getRun(run.id);
|
|
assert.equal(loaded.workflow_revision, 2);
|
|
await db("workflow_runs").where({ id: run.id }).del();
|
|
|
|
await deleteRevisionHistory(workflowId);
|
|
assert.equal(await getLatestRevision(workflowId), null);
|
|
const seeded = await ensureInitialRevision({ owner, file });
|
|
assert.ok(seeded);
|
|
assert.equal(seeded.revision, 1);
|
|
assert.equal(seeded.seeded, true);
|
|
const again = await ensureInitialRevision({ owner, file });
|
|
assert.equal(again?.revision, 1);
|
|
assert.equal(again?.seeded, false);
|
|
|
|
const warnings = collectWorkflowWarnings(
|
|
`name: bad\nscripts:\n - unknown-script-xyz\n`,
|
|
);
|
|
assert.ok(warnings.warnings.some((w) => w.code === "unknown_script"));
|
|
|
|
const trashed = await moveWorkflowToTrash({
|
|
workflowId,
|
|
owner,
|
|
file,
|
|
name: "smoke test v2",
|
|
});
|
|
assert.ok(trashed.id);
|
|
assert.equal(fsStore.readWorkflowYaml(owner, file), null);
|
|
assert.ok(fs.existsSync(trashPath));
|
|
|
|
const restored = await restoreFromTrash(trashed.id);
|
|
assert.equal(restored.file, file);
|
|
assert.ok(fsStore.readWorkflowYaml(owner, file));
|
|
|
|
await moveWorkflowToTrash({ workflowId, owner, file, name: "smoke test v2" });
|
|
const trashAgain = (await listTrash()).find((t) => t.file === file);
|
|
assert.ok(trashAgain);
|
|
await purgeTrashItem(trashAgain.id);
|
|
assert.ok(!(await listTrash()).some((t) => t.file === file));
|
|
|
|
await deleteRevisionHistory(workflowId);
|
|
cleanup();
|
|
|
|
console.log("workflow-history-smoke: ok");
|
|
await db.destroy();
|