Files
learnharness/tests/upstream-merge.test.mjs
T

99 lines
5.1 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { execFileSync, spawnSync } from "node:child_process";
import { lstatSync, readFileSync, readlinkSync } from "node:fs";
const UPSTREAM = "dc4c398d5167865b84b0ccb3ec809eb88bf8f65f";
const FORK_HEAD = "83f5f590155fe349f80a0e09ba10295e7bbee5d0";
const LOCAL_TAG = "b7f41a6b76578427d2d121ada02f80da49297423";
const LOCAL_TAG_COMMIT = "2da091d6e9df841476fe0812c5b3baee163e196c";
const git = (...args) => execFileSync("git", args, { encoding: "utf8", timeout: 30_000 });
const isProjectPath = (path) => !/(^|\/)(?:\.gsd|\.planning|\.audits|\.gitignore|node_modules)(?:\/|$)/.test(path);
const tracked = new Set(git("ls-files", "-z").split("\0").filter((path) => path && isProjectPath(path)));
function source(path) {
assert.ok(tracked.has(path), `Not a tracked project file: ${path}`);
return readFileSync(path, "utf8");
}
const marker = /^(?:<{7}(?: |$)|={7}$|>{7}(?: |$)|\|{7}(?: |$))/m;
function assertRelease(manifest, lock) {
assert.equal(manifest.version, "0.6.0");
assert.equal(lock.version, "0.6.0");
assert.equal(lock.packages[""].version, "0.6.0");
const command = manifest.scripts["desktop:release:linux"];
assert.match(command, /electron-builder --config electron-builder\.gitea\.yml --linux --x64 --publish never(?:\s|$)/);
}
function assertUpstream(commit) {
assert.equal(commit, UPSTREAM);
assert.equal(git("cat-file", "-t", commit).trim(), "commit");
}
test("merge has no unmerged index entries", () => {
assert.equal(git("ls-files", "-u"), "");
});
test("tracked project files have no conflict marker lines", () => {
// Scan only tracked project files, never workflow or ignored local artifacts.
for (const path of tracked) {
// Git tracks a symlink's target text, not the directory/file it points
// into. Never follow skill links into ignored or external artifacts.
const data = lstatSync(path).isSymbolicLink() ? Buffer.from(readlinkSync(path)) : readFileSync(path);
if (data.includes(0)) continue; // binary assets are not source marker candidates
assert.equal(marker.test(data.toString("utf8")), false, `Conflict marker in ${path}`);
}
});
test("release retains fork version and local-only Linux/Gitea command", () => {
assertRelease(JSON.parse(source("package.json")), JSON.parse(source("package-lock.json")));
});
test("Gitea updater identity and release tooling are preserved", () => {
const config = source("electron-builder.gitea.yml");
assert.match(config, /extends: \.\/electron-builder\.yml/);
assert.match(config, /provider: generic/);
assert.match(config, /url: https:\/\/server2\.sorokin-oleg-edu\.xyz\/replicated\/learnharness\/releases\/download\/latest/);
assert.ok(tracked.has("scripts/gitea-release.mjs"));
});
test("exact upstream exists without moving the distinct local v0.4.0 tag", () => {
assertUpstream(UPSTREAM);
assert.equal(git("rev-parse", "refs/tags/v0.4.0").trim(), LOCAL_TAG);
assert.equal(git("rev-parse", "refs/tags/v0.4.0^{}").trim(), LOCAL_TAG_COMMIT);
assert.notEqual(LOCAL_TAG_COMMIT, UPSTREAM);
});
function assertParents(parents) {
assert.deepEqual(parents, [FORK_HEAD, UPSTREAM]);
}
test("pending or committed merge targets exactly the approved histories", () => {
const pending = spawnSync("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"], { encoding: "utf8", timeout: 30_000 });
if (pending.status === 0) {
assertParents([git("rev-parse", "HEAD").trim(), pending.stdout.trim()]);
} else {
assert.equal(pending.status, 1, pending.stderr || pending.error?.message);
// Keep this regression valid after later commits: the exact two-parent
// integration commit must remain in HEAD's ancestry, not be HEAD forever.
const parents = git("log", "--ancestry-path", "--merges", "--format=%P", `${FORK_HEAD}..HEAD`).trim().split("\n");
assert.ok(parents.includes(`${FORK_HEAD} ${UPSTREAM}`), "Exact upstream merge missing from HEAD ancestry");
git("merge-base", "--is-ancestor", UPSTREAM, "HEAD");
}
});
test("audit guards reject markers, wrong versions and wrong targets using inline fixtures", () => {
for (const line of ["<<<<<<< ours", "=======", ">>>>>>> theirs", "||||||| base"]) assert.ok(marker.test(line));
assert.equal(marker.test("ordinary source"), false);
const manifest = { version: "0.6.0", scripts: { "desktop:release:linux": "electron-builder --config electron-builder.gitea.yml --linux --x64 --publish never" } };
const lock = { version: "0.6.0", packages: { "": { version: "0.6.0" } } };
assertRelease(manifest, lock);
assert.throws(() => assertRelease({ ...manifest, version: "0.4.0" }, lock));
assert.throws(() => assertRelease(manifest, { ...lock, version: "0.4.0" }));
assert.throws(() => assertRelease(manifest, { ...lock, packages: { "": { version: "0.4.0" } } }));
assert.throws(() => assertRelease({ ...manifest, scripts: { "desktop:release:linux": "electron-builder --publish always" } }, lock));
assert.throws(() => assertUpstream(LOCAL_TAG_COMMIT));
assert.throws(() => assertParents([FORK_HEAD, LOCAL_TAG_COMMIT]));
assert.throws(() => assertParents([UPSTREAM, FORK_HEAD]));
assert.throws(() => assertParents([FORK_HEAD]));
assert.throws(() => source(".gsd/not-a-project-file"));
});