Files
pikasTech-unidesk/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts
T
2026-07-14 13:15:28 +02:00

153 lines
8.3 KiB
TypeScript

import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import type { GiteaConfig, GiteaMirrorRepository } from "./platform-infra-gitea-config";
import { renderMirrorBootstrap } from "./platform-infra-gitea-render";
import { pacBootstrapYamlMatchFailure, projectPacBootstrapResult, renderPacBootstrap, resolvePacBootstrapMirrorRepository } from "./platform-infra-pipelines-as-code-bootstrap";
import { runPlatformInfraPipelinesAsCodeCommand } from "./platform-infra-pipelines-as-code";
const mirror: GiteaMirrorRepository = {
key: "widgets-nc01",
targetId: "NC01",
upstream: { repository: "acme/widgets", cloneUrl: "https://github.com/acme/widgets.git", branch: "main", visibility: "public" },
gitea: { owner: "mirrors", name: "acme-widgets", mirrorMode: "controlled-push", publicRead: true, readUrl: "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/acme-widgets.git" },
gitops: { branch: "main", flushDisposition: "not-a-gitops-branch" },
snapshot: { prefix: "refs/unidesk/snapshots/widgets-main-nc01" },
legacyGitMirror: null,
};
function fixtureConfig(repositories: GiteaMirrorRepository[]): GiteaConfig {
return { sourceAuthority: { repositories } } as unknown as GiteaConfig;
}
test("PaC bootstrap uses one exact YAML-owned Gitea repository", () => {
const selected = resolvePacBootstrapMirrorRepository(fixtureConfig([mirror]), "nc01", {
id: "widgets-nc01",
owner: "mirrors",
repo: "acme-widgets",
cloneUrl: `${mirror.gitea.readUrl}/`,
sourceBranch: "main",
});
expect(selected.key).toBe("widgets-nc01");
const releaseMirror = { ...mirror, key: "widgets-release-nc01", upstream: { ...mirror.upstream, branch: "release" } };
expect(resolvePacBootstrapMirrorRepository(fixtureConfig([mirror, releaseMirror]), "NC01", {
id: "widgets-release-nc01",
owner: "mirrors",
repo: "acme-widgets",
cloneUrl: mirror.gitea.readUrl,
sourceBranch: "release",
}).key).toBe("widgets-release-nc01");
expect(() => resolvePacBootstrapMirrorRepository(fixtureConfig([]), "NC01", {
id: "widgets-nc01",
owner: "mirrors",
repo: "acme-widgets",
cloneUrl: mirror.gitea.readUrl,
sourceBranch: "main",
})).toThrow("no exact match");
expect(() => resolvePacBootstrapMirrorRepository(fixtureConfig([mirror, { ...mirror }]), "NC01", {
id: "widgets-nc01",
owner: "mirrors",
repo: "acme-widgets",
cloneUrl: mirror.gitea.readUrl,
sourceBranch: "main",
})).toThrow("2 exact matches");
});
test("Gitea mirror bootstrap defaults to a visible dry-run instead of an empty refusal", () => {
const rendered = renderMirrorBootstrap({
ok: true,
action: "platform-infra-gitea-mirror-bootstrap",
mode: "dry-run",
mutation: false,
target: { id: "NC01" },
repositories: [{ key: "widgets-nc01", giteaOwner: "mirrors", giteaRepo: "acme-widgets" }],
blockers: [],
next: { confirm: "bun scripts/cli.ts platform-infra gitea mirror bootstrap --target NC01 --repo widgets-nc01 --confirm" },
}).renderedText;
expect(rendered).toContain("MODE MUTATION TARGET");
expect(rendered).toContain("widgets-nc01");
expect(rendered).toContain("--repo widgets-nc01 --confirm");
expect(rendered).not.toContain("missing-confirm");
});
test("PaC apply checks the Gitea repository before dry-run return and cluster mutation", () => {
const remote = readFileSync(resolve(import.meta.dir, "platform-infra-pipelines-as-code-remote.sh"), "utf8");
const action = remote.indexOf("apply_action() {");
const preflight = remote.indexOf("if ! repository_preflight", action);
const dryRun = remote.indexOf('if [ "$UNIDESK_PAC_DRY_RUN" = "1" ]', action);
const release = remote.indexOf(" apply_release", action);
expect(action).toBeGreaterThanOrEqual(0);
expect(preflight).toBeGreaterThan(action);
expect(dryRun).toBeGreaterThan(preflight);
expect(release).toBeGreaterThan(dryRun);
expect(remote).toContain('"error":"gitea-repository-missing"');
});
test("platform bootstrap help advertises the composite entry before low-level apply", async () => {
const help = await runPlatformInfraPipelinesAsCodeCommand({} as never, ["help", "platform-bootstrap"]);
const usage = (help as Record<string, unknown>).usage as string[];
expect(usage[0]).toContain("pipelines-as-code bootstrap");
expect(usage).toContain("bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target <NODE> --consumer <consumer> --confirm");
});
test("PaC bootstrap projects typed stages and compact JSON", () => {
const result = {
ok: true,
action: "platform-infra-pipelines-as-code-bootstrap",
mutation: false,
mode: "dry-run",
target: { id: "NC01" },
consumer: { id: "widgets", node: "NC01", lane: "v01" },
repository: { id: "widgets", namespace: "ci" },
mirrorRepository: { key: mirror.key, upstreamRepository: mirror.upstream.repository, upstreamBranch: mirror.upstream.branch, owner: mirror.gitea.owner, repo: mirror.gitea.name },
githubPreflight: { ok: true, mutation: false, repositories: [{ key: mirror.key, code: "github-upstream-available", ok: true }] },
giteaBootstrap: { ok: true, mutation: false, mode: "dry-run", blockers: [] },
pipelinesAsCodeApply: { ok: true, mutation: false, mode: "dry-run", remote: { ok: true, preflight: { repositoryExists: true } } },
};
const projection = projectPacBootstrapResult(result);
expect(projection.ok).toBe(true);
expect(JSON.stringify(projection)).not.toContain("pipelinesAsCodeApply");
expect((projection.stages as Record<string, unknown>[]).map((item) => item.id)).toEqual(["gitea-init", "pac-controller", "tekton-consumer", "gitops-argo"]);
const rendered = renderPacBootstrap(result).renderedText;
expect(rendered).toContain("PRECONDITIONS");
expect(rendered).toContain("yaml-repository-exact-match");
expect(rendered).toContain("confirm:");
});
test("PaC bootstrap returns fix-yaml next for zero YAML matches", () => {
const result = pacBootstrapYamlMatchFailure("NC01", "widgets", new Error("cannot resolve repository: no exact match"));
expect(projectPacBootstrapResult(result)).toBe(result);
expect((result.blockers as Record<string, unknown>[])[0]?.code).toBe("yaml-repository-no-match");
expect(result.next).toEqual({
kind: "fix-yaml",
command: "检查 config/platform-infra/gitea.yaml 与 config/platform-infra/pipelines-as-code.yaml 的 target、owner/name 和 read URL 精确匹配",
});
});
test("PaC bootstrap returns fix-yaml next for multiple YAML matches", () => {
const result = pacBootstrapYamlMatchFailure("NC01", "widgets", new Error("cannot resolve repository: 2 exact matches"));
expect(result.mutation).toBe(false);
expect((result.blockers as Record<string, unknown>[])[0]?.code).toBe("yaml-repository-multiple-matches");
expect((result.next as Record<string, unknown>).kind).toBe("fix-yaml");
});
test("PaC bootstrap distinguishes GitHub access failure from Gitea and PaC stages", () => {
const projection = projectPacBootstrapResult({
ok: false,
action: "platform-infra-pipelines-as-code-bootstrap",
mutation: false,
mode: "dry-run",
target: { id: "NC01" },
consumer: { id: "widgets", node: "NC01", lane: "v01" },
repository: { id: "widgets", namespace: "ci" },
mirrorRepository: { key: mirror.key, upstreamRepository: mirror.upstream.repository, upstreamBranch: mirror.upstream.branch, owner: mirror.gitea.owner, repo: mirror.gitea.name },
githubPreflight: { ok: false, mutation: false, repositories: [{ key: mirror.key, code: "github-repository-not-found-or-no-access", errorTail: "repository unavailable" }] },
giteaBootstrap: { ok: false, mutation: false, mode: "skipped", githubPreflight: { ok: false, repositories: [{ code: "github-repository-not-found-or-no-access", errorTail: "repository unavailable" }] } },
pipelinesAsCodeApply: { ok: false, mutation: false, mode: "skipped" },
});
expect((projection.blockers as Record<string, unknown>[])).toEqual([{ code: "github-repository-not-found-or-no-access", stage: "github-upstream", reason: "repository unavailable" }]);
expect((projection.preconditions as Record<string, unknown>[])[2]).toMatchObject({ ok: null, code: "gitea-bootstrap-skipped" });
expect((projection.stages as Record<string, unknown>[]).map((item) => item.state)).toEqual(["skipped", "skipped", "skipped", "skipped"]);
expect((projection.next as Record<string, unknown>).kind).toBe("read-only-status");
});