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).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 --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[]).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[])[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[])[0]?.code).toBe("yaml-repository-multiple-matches"); expect((result.next as Record).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[])).toEqual([{ code: "github-repository-not-found-or-no-access", stage: "github-upstream", reason: "repository unavailable" }]); expect((projection.preconditions as Record[])[2]).toMatchObject({ ok: null, code: "gitea-bootstrap-skipped" }); expect((projection.stages as Record[]).map((item) => item.state)).toEqual(["skipped", "skipped", "skipped", "skipped"]); expect((projection.next as Record).kind).toBe("read-only-status"); });