381 lines
23 KiB
TypeScript
381 lines
23 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { spawnSync } from "node:child_process";
|
|
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 { PacReleaseManifestRenderError, parsePacConfigDocument, readPacConfig, renderPacReleaseManifest, runPlatformInfraPipelinesAsCodeCommand, validatePacConfig, validPacConsumers } from "./platform-infra-pipelines-as-code";
|
|
import { materializeYamlComposition } from "./yaml-composition";
|
|
|
|
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;
|
|
}
|
|
|
|
function pacConfigDocument(): Record<string, unknown> {
|
|
const source = Bun.YAML.parse(readFileSync(resolve(import.meta.dir, "../../config/platform-infra/pipelines-as-code.yaml"), "utf8")) as Record<string, unknown>;
|
|
return structuredClone(materializeYamlComposition(source, { label: "config/platform-infra/pipelines-as-code.yaml" }).value);
|
|
}
|
|
|
|
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("selfmedia consumers share one PaC Repository while keeping branch parameters isolated", () => {
|
|
const config = Bun.YAML.parse(readFileSync(resolve(import.meta.dir, "../../config/platform-infra/pipelines-as-code.yaml"), "utf8")) as Record<string, unknown>;
|
|
const repositories = config.repositories as Array<Record<string, unknown>>;
|
|
const consumers = config.consumers as Array<Record<string, unknown>>;
|
|
const selfmediaRepositories = repositories.filter((item) => item.url === "https://gitea.pikapython.com/mirrors/pikainc-selfmedia");
|
|
expect(selfmediaRepositories).toHaveLength(1);
|
|
const development = consumers.find((item) => item.id === "selfmedia-nc01");
|
|
const production = consumers.find((item) => item.id === "selfmedia-production-nc01");
|
|
expect(development?.repositoryRef).toBe("selfmedia-nc01");
|
|
expect(production?.repositoryRef).toBe("selfmedia-nc01");
|
|
expect(development?.namespace).toBe("selfmedia-ci");
|
|
expect(production?.namespace).toBe("selfmedia-ci");
|
|
expect((selfmediaRepositories[0]?.params as Record<string, unknown>).source_branch).toBe("master");
|
|
expect((production?.params as Record<string, unknown>).source_branch).toBe("release");
|
|
expect((production?.params as Record<string, unknown>).pipeline_name).toBe("selfmedia-production-nc01-pac");
|
|
});
|
|
|
|
test("HWLAB v0.3 and production share one NC01 PaC Repository while keeping lane parameters isolated", () => {
|
|
const config = pacConfigDocument();
|
|
const repositories = config.repositories as Array<Record<string, unknown>>;
|
|
const consumers = config.consumers as Array<Record<string, unknown>>;
|
|
const nc01Repositories = repositories.filter((item) => item.id === "hwlab-nc01-v03" || item.id === "hwlab-nc01-production");
|
|
expect(nc01Repositories.map((item) => item.id)).toEqual(["hwlab-nc01-v03"]);
|
|
const development = consumers.find((item) => item.id === "hwlab-nc01-v03");
|
|
const production = consumers.find((item) => item.id === "hwlab-nc01-production");
|
|
expect(development?.repositoryRef).toBe("hwlab-nc01-v03");
|
|
expect(production?.repositoryRef).toBe("hwlab-nc01-v03");
|
|
expect((nc01Repositories[0]?.params as Record<string, unknown>).source_branch).toBe("v0.3");
|
|
expect((production?.params as Record<string, unknown>).source_branch).toBe("release");
|
|
expect((production?.params as Record<string, unknown>).gitops_branch).toBe("release-gitops");
|
|
expect((production?.params as Record<string, unknown>).pipeline_name).toBe("hwlab-nc01-production-ci-image-publish");
|
|
expect(readPacConfig({ consumerId: "hwlab-nc01-production" }).validationWarnings).toEqual([]);
|
|
});
|
|
|
|
test("selected PaC consumer rejects duplicate URL authority conflicts before mutation", () => {
|
|
const conflictMutations: Array<[string, (repository: Record<string, unknown>) => void]> = [
|
|
["namespace", (repository) => { repository.namespace = "other-ci"; }],
|
|
["SecretRef", (repository) => { repository.secretName = "pac-gitea-hwlab-conflict"; }],
|
|
["providerType", (repository) => { repository.providerType = "github"; }],
|
|
];
|
|
for (const [field, mutate] of conflictMutations) {
|
|
const config = structuredClone(readPacConfig({ consumerId: "hwlab-nc01-production" }));
|
|
const sharedRepository = config.repositories.find((repository) => repository.id === "hwlab-nc01-v03");
|
|
const developmentConsumer = config.consumers.find((consumer) => consumer.id === "hwlab-nc01-v03");
|
|
if (sharedRepository === undefined || developmentConsumer === undefined) throw new Error("HWLAB NC01 fixtures are required");
|
|
const duplicateRepository = structuredClone(sharedRepository) as unknown as Record<string, unknown>;
|
|
duplicateRepository.id = "hwlab-nc01-conflict";
|
|
duplicateRepository.name = "hwlab-nc01-conflict";
|
|
mutate(duplicateRepository);
|
|
config.repositories.push(duplicateRepository as never);
|
|
config.consumers.push({ ...structuredClone(developmentConsumer), id: "hwlab-nc01-conflict", repositoryRef: "hwlab-nc01-conflict" });
|
|
expect(() => validatePacConfig(config, new Set(["hwlab-nc01-production"]))).toThrow(field);
|
|
}
|
|
});
|
|
|
|
test("unselected production validation is a non-blocking warning while selected production fails closed", () => {
|
|
const config = structuredClone(readPacConfig({ consumerId: "selfmedia-nc01" }));
|
|
const development = config.consumers.find((consumer) => consumer.id === "selfmedia-nc01");
|
|
const production = config.consumers.find((consumer) => consumer.id === "selfmedia-production-nc01");
|
|
expect(development?.deliveryProvenance).not.toBeNull();
|
|
expect(production?.deliveryProvenance).not.toBeNull();
|
|
if (development?.deliveryProvenance === null || development?.deliveryProvenance === undefined || production?.deliveryProvenance === null || production?.deliveryProvenance === undefined) {
|
|
throw new Error("selfmedia delivery provenance fixtures are required");
|
|
}
|
|
production.params.gitops_secret_name = "invalid-production-secret";
|
|
production.deliveryProvenance.markerValue = development.deliveryProvenance.markerValue;
|
|
config.consumers = [production, development, ...config.consumers.filter((consumer) => consumer !== production && consumer !== development)];
|
|
|
|
const warnings = validatePacConfig(config, new Set(["selfmedia-nc01"]));
|
|
const warning = warnings.find((item) => item.consumer === "selfmedia-production-nc01");
|
|
expect(warning).toEqual({
|
|
code: "pac-unselected-consumer-invalid",
|
|
object: { kind: "consumer", id: "selfmedia-production-nc01" },
|
|
consumer: "selfmedia-production-nc01",
|
|
configPath: "config/platform-infra/pipelines-as-code.yaml#consumers.selfmedia-production-nc01",
|
|
blocking: false,
|
|
evidence: expect.stringMatching(/^type=string,length=\d+,sha256=[0-9a-f]{64}$/u),
|
|
});
|
|
expect(() => validatePacConfig(config, new Set(["selfmedia-production-nc01"]))).toThrow();
|
|
});
|
|
|
|
test("unselected parse and repository errors remain warnings while selected objects fail closed", () => {
|
|
const invalidConsumerDocument = pacConfigDocument();
|
|
const invalidConsumer = (invalidConsumerDocument.consumers as Array<Record<string, unknown>>).find((item) => item.id === "selfmedia-production-nc01");
|
|
if (invalidConsumer === undefined) throw new Error("production consumer fixture is required");
|
|
invalidConsumer.namespace = 42;
|
|
const development = parsePacConfigDocument(invalidConsumerDocument, { consumerId: "selfmedia-nc01" });
|
|
expect(development.consumers.some((consumer) => consumer.id === "selfmedia-production-nc01")).toBe(false);
|
|
expect(development.validationWarnings).toContainEqual({
|
|
code: "pac-unselected-consumer-parse-invalid",
|
|
object: { kind: "consumer", id: "selfmedia-production-nc01" },
|
|
consumer: "selfmedia-production-nc01",
|
|
configPath: expect.stringMatching(/#consumers\[\d+\]$/u),
|
|
blocking: false,
|
|
evidence: expect.stringMatching(/^type=string,length=\d+,sha256=[0-9a-f]{64}$/u),
|
|
});
|
|
expect(() => parsePacConfigDocument(invalidConsumerDocument, { consumerId: "selfmedia-production-nc01" })).toThrow("namespace");
|
|
|
|
const invalidRepositoryDocument = pacConfigDocument();
|
|
const invalidRepository = (invalidRepositoryDocument.repositories as Array<Record<string, unknown>>).find((item) => item.id === "pikaoa-test-nc01");
|
|
if (invalidRepository === undefined) throw new Error("pikaoa repository fixture is required");
|
|
invalidRepository.cloneUrl = "https://gitea.example.invalid/mirrors/pikaoa.git";
|
|
const isolated = parsePacConfigDocument(invalidRepositoryDocument, { consumerId: "selfmedia-nc01" });
|
|
expect(isolated.validationWarnings).toContainEqual({
|
|
code: "pac-unselected-repository-invalid",
|
|
object: { kind: "repository", id: "pikaoa-test-nc01" },
|
|
consumer: null,
|
|
configPath: "config/platform-infra/pipelines-as-code.yaml#repositories.pikaoa-test-nc01",
|
|
blocking: false,
|
|
evidence: expect.stringMatching(/^type=string,length=\d+,sha256=[0-9a-f]{64}$/u),
|
|
});
|
|
expect(validPacConsumers(isolated).some((consumer) => consumer.repositoryRef === "pikaoa-test-nc01")).toBe(false);
|
|
expect(() => parsePacConfigDocument(invalidRepositoryDocument, { consumerId: "pikaoa-nc01" })).toThrow("cloneUrl");
|
|
|
|
const duplicateSelectedRepository = pacConfigDocument();
|
|
const repositories = duplicateSelectedRepository.repositories as Array<Record<string, unknown>>;
|
|
const selfmediaRepository = repositories.find((item) => item.id === "selfmedia-nc01");
|
|
if (selfmediaRepository === undefined) throw new Error("selfmedia repository fixture is required");
|
|
repositories.push(structuredClone(selfmediaRepository));
|
|
expect(() => parsePacConfigDocument(duplicateSelectedRepository, { consumerId: "selfmedia-nc01" })).toThrow("must match exactly one YAML object");
|
|
});
|
|
|
|
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);
|
|
const admission = remote.indexOf(" apply_admission_manifest", release);
|
|
const rbac = remote.indexOf(" rbac_tmp=$(mktemp)", admission);
|
|
expect(action).toBeGreaterThanOrEqual(0);
|
|
expect(preflight).toBeGreaterThan(action);
|
|
expect(dryRun).toBeGreaterThan(preflight);
|
|
expect(release).toBeGreaterThan(dryRun);
|
|
expect(admission).toBeGreaterThan(release);
|
|
expect(rbac).toBeGreaterThan(admission);
|
|
expect(remote).toContain('"admission":{"applied":%s,"ready":%s}');
|
|
expect(remote).toContain('"admissionProvenance":%s');
|
|
expect(remote).toContain('"error":"gitea-repository-missing"');
|
|
const statusAction = remote.slice(remote.indexOf("status_action() {"), remote.indexOf("history_action() {"));
|
|
const bootstrapReadyAssignment = statusAction.split("\n").find((line) => line.includes("bootstrap_ready=$("));
|
|
expect(bootstrapReadyAssignment).toBeDefined();
|
|
expect(remote).toContain("status_progress() {");
|
|
expect(remote).toContain('stepTimeoutSeconds');
|
|
expect(remote).toContain('pac_read kubectl');
|
|
expect(remote).toContain('export UNIDESK_PAC_ARGO_FILE="$argo_file"');
|
|
expect(remote).toContain('get pipelinerun -l "pipelinesascode.tekton.dev/repository=$UNIDESK_PAC_REPOSITORY_NAME"');
|
|
expect(statusAction).toContain("status_progress artifact-evidence");
|
|
expect(statusAction).toContain("status_progress diagnostics");
|
|
expect(statusAction).toContain("status_progress complete");
|
|
expect(statusAction).toContain("output.code === 'pac-gitops-missing'");
|
|
expect(statusAction).toContain("output.code = 'pac-artifact-log-evidence-deferred'");
|
|
expect(statusAction).toContain("blocking: false");
|
|
expect(statusAction).toContain('[ "$bootstrap_ready" = "true" ]');
|
|
expect(statusAction).toContain("output.code = 'pac-consumer-bootstrap-not-ready'");
|
|
expect(statusAction).toContain("output.ok = false");
|
|
expect(statusAction).toContain("code: 'pac-admission-provenance-not-ready'");
|
|
expect(statusAction).toContain("blocking: false");
|
|
for (const fixture of [
|
|
{ state: { configured: false, ready: null }, expected: true },
|
|
{ state: { configured: true, ready: true }, expected: true },
|
|
{ state: { configured: true, ready: false }, expected: false },
|
|
]) {
|
|
const smoke = spawnSync("sh", ["-eu", "-c", [
|
|
"consumer_bootstrap=$UNIDESK_PAC_BOOTSTRAP_STATE",
|
|
bootstrapReadyAssignment!.trim(),
|
|
`printf '{"ok":%s}\\n' "$( [ "$bootstrap_ready" = "true" ] && echo true || echo false )"`,
|
|
].join("\n")], {
|
|
encoding: "utf8",
|
|
env: { ...process.env, UNIDESK_PAC_BOOTSTRAP_STATE: JSON.stringify(fixture.state) },
|
|
});
|
|
expect(smoke).toMatchObject({ status: 0, stderr: "" });
|
|
expect(JSON.parse(smoke.stdout)).toEqual({ ok: fixture.expected });
|
|
}
|
|
});
|
|
|
|
test("PaC release manifest renders YAML-owned watcher startup backlog probes", () => {
|
|
const pac = readPacConfig({ consumerId: "hwlab-nc01-v03", selectDefault: true });
|
|
const source = readFileSync(resolve(import.meta.dir, "fixtures/pac/release-leading-comment-only.yaml"), "utf8");
|
|
const manifest = renderPacReleaseManifest(pac, source);
|
|
const documents = manifest.split(/^---\s*$/mu).map((item) => item.trim()).filter(Boolean).map((item) => Bun.YAML.parse(item) as any);
|
|
expect(documents).toHaveLength(2);
|
|
const watcher = documents[0].spec.template.spec.containers[0];
|
|
expect(watcher.startupProbe).toEqual(pac.release.workloads.watcher.startupProbe);
|
|
expect(watcher.readinessProbe).toEqual(pac.release.workloads.watcher.readinessProbe);
|
|
expect(watcher.livenessProbe).toEqual(pac.release.workloads.watcher.livenessProbe);
|
|
expect(documents[1].kind).toBe("Service");
|
|
});
|
|
|
|
test("PaC release manifest rejects resources without typed object identity", () => {
|
|
const pac = readPacConfig({ consumerId: "hwlab-nc01-v03", selectDefault: true });
|
|
const source = readFileSync(resolve(import.meta.dir, "fixtures/pac/release-missing-object-identity.yaml"), "utf8");
|
|
try {
|
|
renderPacReleaseManifest(pac, source);
|
|
throw new Error("expected release manifest renderer to reject the invalid resource");
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(PacReleaseManifestRenderError);
|
|
expect(error).toMatchObject({
|
|
code: "pac-release-manifest-object-identity-invalid",
|
|
documentIndex: 2,
|
|
missingFields: ["apiVersion", "kind"],
|
|
});
|
|
}
|
|
});
|
|
|
|
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", "pac-admission", "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 keeps admission drift as a non-blocking warning", () => {
|
|
const result = {
|
|
ok: true,
|
|
action: "platform-infra-pipelines-as-code-bootstrap",
|
|
mutation: true,
|
|
mode: "confirmed",
|
|
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: "skipped", blockers: [] },
|
|
pipelinesAsCodeApply: {
|
|
ok: true,
|
|
mutation: true,
|
|
mode: "confirmed",
|
|
remote: {
|
|
ok: true,
|
|
admission: { applied: true, ready: false },
|
|
admissionProvenance: { policyName: "unidesk-pac-delivery-provenance-v2", reasons: ["policy-generation-not-observed"] },
|
|
},
|
|
},
|
|
};
|
|
const projection = projectPacBootstrapResult(result);
|
|
expect(projection.ok).toBe(true);
|
|
expect((projection.stages as Record<string, unknown>[]).find((item) => item.id === "pac-admission")).toMatchObject({ ok: true, state: "warning", blocking: false });
|
|
expect(projection.warnings).toEqual([expect.objectContaining({ code: "pac-admission-provenance-not-ready", blocking: false })]);
|
|
});
|
|
|
|
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", "skipped"]);
|
|
expect((projection.next as Record<string, unknown>).kind).toBe("read-only-status");
|
|
});
|