Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
251 lines
8.1 KiB
TypeScript
251 lines
8.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
DEFAULT_UPDATE_BRANCH,
|
|
decideUpdateAvailability,
|
|
detectRestartSupervisor,
|
|
isOfficialRepoUrl,
|
|
normalizeRepoUrl,
|
|
normalizeUpdateBranch,
|
|
OFFICIAL_REPO_URL,
|
|
parseGitStatusPorcelain,
|
|
RESTART_SUPERVISOR_ENV,
|
|
repoIdentity,
|
|
restartSupervisorAdvice,
|
|
shortCommit,
|
|
updateSteps,
|
|
} from "./self-update.js";
|
|
|
|
describe("normalizeRepoUrl", () => {
|
|
it("accepts the official repository and https forks", () => {
|
|
expect(normalizeRepoUrl(OFFICIAL_REPO_URL)).toEqual({
|
|
url: "https://github.com/elie222/rakazo",
|
|
});
|
|
expect(normalizeRepoUrl(" https://github.com/me/rakazo.git/ ")).toEqual({
|
|
url: "https://github.com/me/rakazo.git",
|
|
});
|
|
expect(normalizeRepoUrl("https://git.example.com:8443/team/rakazo.git")).toEqual({
|
|
url: "https://git.example.com:8443/team/rakazo.git",
|
|
});
|
|
});
|
|
|
|
it("accepts ssh remotes in both spellings", () => {
|
|
expect(normalizeRepoUrl("git@github.com:me/rakazo.git")).toEqual({
|
|
url: "git@github.com:me/rakazo.git",
|
|
});
|
|
expect(normalizeRepoUrl("ssh://git@github.com/me/rakazo.git")).toEqual({
|
|
url: "ssh://git@github.com/me/rakazo.git",
|
|
});
|
|
});
|
|
|
|
it("refuses transports and shapes that are not a git remote", () => {
|
|
for (const input of [
|
|
"",
|
|
" ",
|
|
"http://github.com/me/rakazo",
|
|
"git://github.com/me/rakazo",
|
|
"file:///etc/passwd",
|
|
"/srv/rakazo",
|
|
"https://github.com",
|
|
"https://github.com/onlyone",
|
|
"https://github.com/me/../../etc",
|
|
"https://user:secret@github.com/me/rakazo",
|
|
"https://token@github.com/me/rakazo",
|
|
"ssh://bad%0auser@github.com/me/rakazo",
|
|
"https://github.com/me/rakazo?x=1",
|
|
"https://github.com/me/rakazo#frag",
|
|
"https://github.com/me/rak azo",
|
|
"--upload-pack=touch /tmp/pwned",
|
|
"-o ProxyCommand=id",
|
|
`https://github.com/me/rakazo${"x".repeat(400)}`,
|
|
]) {
|
|
expect(normalizeRepoUrl(input), input).toHaveProperty("error");
|
|
}
|
|
});
|
|
|
|
it("rejects an embedded newline that could smuggle a second argument", () => {
|
|
expect(normalizeRepoUrl("https://github.com/me/rakazo\n--exec=id")).toHaveProperty("error");
|
|
});
|
|
});
|
|
|
|
describe("repoIdentity", () => {
|
|
it("treats every spelling of the same remote as one repository", () => {
|
|
const identity = "github.com/elie222/rakazo";
|
|
expect(repoIdentity("https://github.com/elie222/rakazo")).toBe(identity);
|
|
expect(repoIdentity("https://github.com/elie222/rakazo.git")).toBe(identity);
|
|
expect(repoIdentity("git@github.com:elie222/rakazo.git")).toBe(identity);
|
|
expect(repoIdentity("ssh://git@github.com/Elie222/Rakazo")).toBe(identity);
|
|
expect(repoIdentity("not a url")).toBeNull();
|
|
});
|
|
|
|
it("only calls the real upstream official", () => {
|
|
expect(isOfficialRepoUrl("git@github.com:elie222/rakazo.git")).toBe(true);
|
|
expect(isOfficialRepoUrl("https://github.com/attacker/rakazo")).toBe(false);
|
|
expect(isOfficialRepoUrl("https://githubb.com/elie222/rakazo")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("normalizeUpdateBranch", () => {
|
|
it("accepts ordinary branch names", () => {
|
|
expect(normalizeUpdateBranch(" main ")).toEqual({ branch: "main" });
|
|
expect(normalizeUpdateBranch("release/1.2")).toEqual({ branch: "release/1.2" });
|
|
});
|
|
|
|
it("refuses names git would reject or that could be read as a flag", () => {
|
|
for (const input of [
|
|
"",
|
|
"with space",
|
|
"-f",
|
|
"a..b",
|
|
"tip@{1}",
|
|
"bad~1",
|
|
"x:y",
|
|
".hidden",
|
|
"feature/.hidden",
|
|
"feature//nested",
|
|
"trailing.",
|
|
"@",
|
|
"/lead",
|
|
"t/",
|
|
"x.lock",
|
|
]) {
|
|
expect(normalizeUpdateBranch(input), input).toHaveProperty("error");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("parseGitStatusPorcelain", () => {
|
|
it("reports a clean tree for empty output", () => {
|
|
expect(parseGitStatusPorcelain("")).toEqual({ clean: true, changed: [] });
|
|
expect(parseGitStatusPorcelain("\n \n")).toEqual({ clean: true, changed: [] });
|
|
});
|
|
|
|
it("lists tracked modifications", () => {
|
|
expect(parseGitStatusPorcelain(" M apps/api/src/app.ts\nA packages/core/src/x.ts\n")).toEqual({
|
|
clean: false,
|
|
changed: ["apps/api/src/app.ts", "packages/core/src/x.ts"],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("updateSteps", () => {
|
|
it("orders the work so migrations land immediately before the restart", () => {
|
|
const steps = updateSteps({
|
|
remoteUrl: OFFICIAL_REPO_URL,
|
|
branch: DEFAULT_UPDATE_BRANCH,
|
|
targetCommit: "2".repeat(40),
|
|
});
|
|
expect(steps.map((step) => step.id)).toEqual([
|
|
"fetch",
|
|
"merge",
|
|
"install",
|
|
"generate",
|
|
"build",
|
|
"migrate",
|
|
]);
|
|
});
|
|
|
|
it("passes every value as its own argument so nothing reaches a shell", () => {
|
|
const steps = updateSteps({
|
|
remoteUrl: "git@github.com:me/rakazo.git",
|
|
branch: "release/1.2",
|
|
targetCommit: "2".repeat(40),
|
|
repointRemote: true,
|
|
});
|
|
expect(steps[0]).toEqual({
|
|
id: "remote",
|
|
label: "Point the checkout at the chosen repository",
|
|
command: "git",
|
|
args: ["remote", "set-url", "origin", "git@github.com:me/rakazo.git"],
|
|
});
|
|
expect(steps.find((step) => step.id === "merge")?.args).toEqual([
|
|
"merge",
|
|
"--ff-only",
|
|
"2".repeat(40),
|
|
]);
|
|
});
|
|
|
|
it("never rewrites history, so local commits fail the update instead of vanishing", () => {
|
|
const args = updateSteps({
|
|
remoteUrl: OFFICIAL_REPO_URL,
|
|
branch: "main",
|
|
targetCommit: "2".repeat(40),
|
|
}).flatMap((step) => step.args);
|
|
expect(args).not.toContain("--hard");
|
|
expect(args).not.toContain("--force");
|
|
expect(args).toContain("--ff-only");
|
|
});
|
|
});
|
|
|
|
describe("detectRestartSupervisor", () => {
|
|
it("takes the operator's declaration first", () => {
|
|
expect(detectRestartSupervisor({ [RESTART_SUPERVISOR_ENV]: "docker" })).toEqual({
|
|
kind: "declared",
|
|
label: "docker",
|
|
});
|
|
});
|
|
|
|
it("recognises systemd and pm2 from the variables they set", () => {
|
|
expect(detectRestartSupervisor({ INVOCATION_ID: "abc" })).toEqual({
|
|
kind: "systemd",
|
|
label: "systemd",
|
|
});
|
|
expect(detectRestartSupervisor({ pm_id: "0" })).toEqual({ kind: "pm2", label: "pm2" });
|
|
});
|
|
|
|
it("reports none for a bare checkout, and says how to fix it", () => {
|
|
expect(detectRestartSupervisor({})).toEqual({ kind: "none", label: null });
|
|
expect(detectRestartSupervisor({ [RESTART_SUPERVISOR_ENV]: " " })).toEqual({
|
|
kind: "none",
|
|
label: null,
|
|
});
|
|
expect(restartSupervisorAdvice(detectRestartSupervisor({}))).toContain(RESTART_SUPERVISOR_ENV);
|
|
expect(restartSupervisorAdvice({ kind: "systemd", label: "systemd" })).toContain("systemd");
|
|
});
|
|
});
|
|
|
|
describe("decideUpdateAvailability", () => {
|
|
it("refuses when the checkout cannot be updated at all", () => {
|
|
expect(decideUpdateAvailability({ checkoutReason: "Not a git checkout." })).toEqual({
|
|
status: "unavailable",
|
|
reason: "Not a git checkout.",
|
|
});
|
|
expect(decideUpdateAvailability({ commit: "abc" })).toEqual({
|
|
status: "unavailable",
|
|
reason: "Could not read the current commit.",
|
|
});
|
|
});
|
|
|
|
it("refuses a dirty tree before comparing commits", () => {
|
|
expect(
|
|
decideUpdateAvailability({
|
|
commit: "aaa",
|
|
targetCommit: "bbb",
|
|
status: { clean: false, changed: ["apps/api/src/app.ts"] },
|
|
}),
|
|
).toEqual({ status: "dirty", changed: ["apps/api/src/app.ts"] });
|
|
});
|
|
|
|
it("separates up to date from an available update", () => {
|
|
const clean = { clean: true, changed: [] };
|
|
expect(decideUpdateAvailability({ commit: "aaa", targetCommit: "aaa", status: clean })).toEqual(
|
|
{ status: "up-to-date", commit: "aaa" },
|
|
);
|
|
expect(
|
|
decideUpdateAvailability({
|
|
commit: "aaa",
|
|
targetCommit: "bbb",
|
|
behindBy: 4,
|
|
status: clean,
|
|
}),
|
|
).toEqual({ status: "available", commit: "aaa", targetCommit: "bbb", behindBy: 4 });
|
|
});
|
|
});
|
|
|
|
describe("shortCommit", () => {
|
|
it("shortens a sha and names the unknown case", () => {
|
|
expect(shortCommit("3c6e2091a2b3c4")).toBe("3c6e209");
|
|
expect(shortCommit(null)).toBe("unknown");
|
|
expect(shortCommit("")).toBe("unknown");
|
|
});
|
|
});
|