Merge pull request #2084 from pikasTech/feat/pikaoa-test-nc01-runtime
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / platform-infra-gitea-nc01- Success
Pipelines as Code CI / unidesk-host- Success

PikaOA:声明 NC01 YAML-first 测试运行面
This commit is contained in:
Lyon
2026-07-15 10:28:30 +08:00
committed by GitHub
8 changed files with 840 additions and 295 deletions
+141 -33
View File
@@ -21,10 +21,9 @@ test("PikaOA test target submits once and exposes bounded typed status", async (
const secretValues = ["fixture-db-password", "fixture-admin-password", "fixture-employee-password", "fixture-session-secret"];
for (const [name, value] of [
["pikaoa-test-database-password.txt", secretValues[0]],
["pikaoa-test.env", `DATABASE_URL=postgresql://pikaoa_test:${secretValues[0]}@db.example.invalid:5432/pikaoa_test?sslmode=require\nPIKAOA_SESSION_SECRET=${secretValues[3]}`],
["pikaoa-test-admin-password.txt", secretValues[1]],
["pikaoa-test-employee-password.txt", secretValues[2]],
["pikaoa-test-session-secret.txt", secretValues[3]],
]) writeFileSync(join(secretsDir, name), `${value}\n`, { mode: 0o600 });
const fakeKubectl = join(binDir, "kubectl");
@@ -53,18 +52,20 @@ if printf '%s' "$args" | grep -Eq '(^| )apply( |$)'; then
previous="$argument"
done
if grep -q '"kind":"Namespace"' "$file"; then
applied_namespace="$(python3 - "$file" <<'PY'
applied_identity="$(python3 - "$file" <<'PY'
import json, pathlib, sys
for line in pathlib.Path(sys.argv[1]).read_text().splitlines():
if not line or line == "---":
continue
item = json.loads(line)
if item.get("kind") == "Namespace":
print(item["metadata"]["name"])
print(item["metadata"]["name"] + "|" + item["metadata"]["labels"]["pikaoa.unidesk.io/instance"])
break
PY
)"
: >"$root/namespaces/$applied_namespace"
applied_namespace="\${applied_identity%%|*}"
applied_instance="\${applied_identity##*|}"
printf '%s' "$applied_instance" >"$root/namespaces/$applied_namespace"
fi
[ ! -f "$root/slow" ] || sleep 0.2
exit 0
@@ -88,14 +89,14 @@ if printf '%s' "$args" | grep -Eq '(^| )get namespace '; then
printf 'Error from server (NotFound): namespaces "%s" not found\n' "$name" >&2
exit 1
fi
instance="\${name#pikaoa-test-}"
instance="$(cat "$root/namespaces/$name")"
if printf '%s' "$args" | grep -q 'managed-by'; then printf '%s' 'unidesk-pikaoa-test-target'; exit 0; fi
if printf '%s' "$args" | grep -q 'unidesk.*target'; then printf '%s' 'TEST01'; exit 0; fi
if printf '%s' "$args" | grep -q 'unidesk.*instance'; then printf '%s' "$instance"; exit 0; fi
printf '{"metadata":{"name":"%s","labels":{"app.kubernetes.io/managed-by":"unidesk-pikaoa-test-target","pikaoa.unidesk.io/target":"TEST01","pikaoa.unidesk.io/instance":"%s"}}}\n' "$name" "$instance"
exit 0
fi
if printf '%s' "$args" | grep -q ' get deployment,statefulset,job,service,persistentvolumeclaim,pod '; then
if printf '%s' "$args" | grep -q ' get deployment,job,service,persistentvolumeclaim,pod '; then
if [ -f "$root/fail-runtime-resource-query" ]; then
printf '%s\n' 'resource query failed' >&2
exit 1
@@ -137,19 +138,26 @@ exit 64
lastCapture = { stdout, stderr, exitCode };
return lastCapture;
};
const args = (action: string, instance: string, withCommit = false): string[] => [
const args = (action: string, instance: string, withCommit = false, step = "all"): string[] => [
"test-target", action, "--config", fixturePath, "--target", "TEST01", "--instance", instance,
...(withCommit ? ["--commit", "0123456789abcdef"] : []),
"--step", step,
...((action === "start" || action === "stop") ? ["--confirm"] : []),
"--output", "json",
];
const projection = async (action: string, instance: string, withCommit = false): Promise<Record<string, unknown>> => {
const result = await runPikaoaCommand(config, args(action, instance, withCommit), localCapture);
const projection = async (action: string, instance: string, withCommit = false, step = "all"): Promise<Record<string, unknown>> => {
const result = await runPikaoaCommand(config, args(action, instance, withCommit, step), localCapture);
return JSON.parse(result.renderedText) as Record<string, unknown>;
};
const waitFor = async (instance: string, expectedCode: string): Promise<Record<string, unknown>> => {
const projectionWithCommit = async (action: string, instance: string, commit: string, step = "all"): Promise<Record<string, unknown>> => {
const commandArgs = args(action, instance, true, step);
commandArgs[commandArgs.indexOf("--commit") + 1] = commit;
const result = await runPikaoaCommand(config, commandArgs, localCapture);
return JSON.parse(result.renderedText) as Record<string, unknown>;
};
const waitFor = async (instance: string, expectedCode: string, step = "all"): Promise<Record<string, unknown>> => {
for (let attempt = 0; attempt < 80; attempt += 1) {
const value = await projection("status", instance);
const value = await projection("status", instance, false, step);
const status = value.status as Record<string, unknown>;
if (status.code === expectedCode) return value;
await Bun.sleep(25);
@@ -158,11 +166,23 @@ exit 64
};
try {
const missing = await projection("status", "missing");
const planned = await projection("plan", "runtime", true);
const plan = planned.plan as Record<string, unknown>;
const secret = plan.secret as Record<string, unknown>;
const sources = secret.sources as Array<Record<string, unknown>>;
assert.equal(sources.find((source) => source.purpose === "database.connection")?.presence, true);
const objects = plan.objects as Array<Record<string, unknown>>;
assert.equal(objects.some((object) => object.kind === "StatefulSet" || object.name === "pikaoa-postgres"), false);
assert.equal(objects.some((object) => object.kind === "Deployment" && object.name === "pikaoa-web"), true);
assert.equal(objects.find((object) => object.kind === "Deployment" && object.name === "pikaoa-api")?.podFsGroup, 65532);
assert.deepEqual(plan.exposure, { serviceType: "NodePort", serviceName: "pikaoa-web", hostIP: "192.0.2.10", port: 32080 });
assert.deepEqual(plan.webApiUpstream, { envName: "PIKAOA_API_UPSTREAM", value: "http://pikaoa-api:8080", scope: "same-namespace" });
const missing = await projection("status", "runtime", false, "api");
assert.equal((missing.status as Record<string, unknown>).code, "start-not-found");
writeFileSync(join(fakeRoot, "slow"), "1\n");
const submitted = await projection("start", "success", true);
const submitted = await projection("start", "runtime", true);
assert.equal(submitted.code, "start-submitted", JSON.stringify(lastCapture));
assert.equal(submitted.mutation, true);
assert.deepEqual((submitted.target as Record<string, unknown>).taskState, {
@@ -170,27 +190,32 @@ exit 64
statusRequestTimeoutSeconds: 31,
logTailLines: 101,
});
const running = await projection("status", "success");
const running = await projection("status", "runtime");
assert.equal((running.status as Record<string, unknown>).code, "start-running");
const concurrentCommit = "fedcba9876543210";
const concurrent = await projectionWithCommit("start", "runtime", concurrentCommit);
assert.equal(concurrent.code, "start-instance-conflict");
assert.equal(concurrent.mutation, false);
assert.equal(concurrent.previousTaskReset, false);
rmSync(join(fakeRoot, "slow"));
const succeeded = await waitFor("success", "start-succeeded");
const succeeded = await waitFor("runtime", "start-succeeded");
const succeededStatus = succeeded.status as Record<string, unknown>;
assert.equal((succeededStatus.task as Record<string, unknown>).exitCode, 0);
assert.ok((succeededStatus.logTail as unknown[]).length <= 101);
writeFileSync(join(fakeRoot, "fail-runtime-query"), "1\n");
const runtimeQueryFailed = await projection("status", "success");
const runtimeQueryFailed = await projection("status", "runtime");
const runtimeQueryFailedStatus = runtimeQueryFailed.status as Record<string, unknown>;
assert.equal(runtimeQueryFailed.ok, false);
assert.equal(runtimeQueryFailedStatus.code, "runtime-query-failed");
assert.equal(runtimeQueryFailedStatus.taskCode, "start-succeeded");
assert.equal((runtimeQueryFailedStatus.runtime as Record<string, unknown>).code, "runtime-query-failed");
const runtimeQueryFailedText = await runPikaoaCommand(config, args("status", "success").slice(0, -2), localCapture);
const runtimeQueryFailedText = await runPikaoaCommand(config, args("status", "runtime").slice(0, -2), localCapture);
assert.match(runtimeQueryFailedText.renderedText, /status=runtime-query-failed/);
rmSync(join(fakeRoot, "fail-runtime-query"));
writeFileSync(join(fakeRoot, "fail-runtime-resource-query"), "1\n");
const runtimeResourceQueryFailed = await projection("status", "success");
const runtimeResourceQueryFailed = await projection("status", "runtime");
const runtimeResourceQueryFailedStatus = runtimeResourceQueryFailed.status as Record<string, unknown>;
assert.equal(runtimeResourceQueryFailed.ok, false);
assert.equal(runtimeResourceQueryFailedStatus.code, "runtime-resource-query-failed");
@@ -198,32 +223,99 @@ exit 64
assert.equal((runtimeResourceQueryFailedStatus.runtime as Record<string, unknown>).code, "runtime-resource-query-failed");
rmSync(join(fakeRoot, "fail-runtime-resource-query"));
const repeated = await projection("start", "success", true);
const repeated = await projection("start", "runtime", true);
assert.equal(repeated.code, "start-already-succeeded");
assert.equal(repeated.mutation, false);
const rerunSubmitted = await projectionWithCommit("start", "runtime", concurrentCommit);
assert.equal(rerunSubmitted.code, "start-submitted");
assert.equal(rerunSubmitted.previousTaskReset, true);
const rerunSucceeded = await waitFor("runtime", "start-succeeded");
assert.equal(((rerunSucceeded.status as Record<string, unknown>).task as Record<string, unknown>).sourceCommit, concurrentCommit);
writeFileSync(fixturePath, readFileSync(fixturePath, "utf8").replace(" livenessPath: /healthz\n", " livenessPath: /healthz-v2\n"));
const manifestRerunSubmitted = await projectionWithCommit("start", "runtime", concurrentCommit);
assert.equal(manifestRerunSubmitted.code, "start-submitted");
assert.equal(manifestRerunSubmitted.previousTaskReset, true);
const manifestRerunSucceeded = await waitFor("runtime", "start-succeeded");
await Bun.sleep(5);
const sameRequestAfterRenderTimeChanged = await projectionWithCommit("start", "runtime", concurrentCommit);
assert.equal(sameRequestAfterRenderTimeChanged.code, "start-already-succeeded");
assert.equal(sameRequestAfterRenderTimeChanged.previousTaskReset, false);
writeFileSync(join(fakeRoot, "slow"), "1\n");
const workerMissingSubmitted = await projection("start", "runtime", true, "api");
const workerPid = Number((workerMissingSubmitted.remote as Record<string, unknown>).pid);
assert.equal(Number.isSafeInteger(workerPid) && workerPid > 0, true);
process.kill(workerPid, "SIGKILL");
await Bun.sleep(25);
const workerMissingReset = await projectionWithCommit("start", "runtime", concurrentCommit, "api");
assert.equal(workerMissingReset.code, "start-submitted");
assert.equal(workerMissingReset.previousTaskReset, true);
rmSync(join(fakeRoot, "slow"));
const workerMissingSucceeded = await waitFor("runtime", "start-succeeded", "api");
writeFileSync(join(fakeRoot, "fail-migration"), "1\n");
await projection("start", "failed", true);
const failed = await waitFor("failed", "start-failed");
await projection("start", "runtime", true, "migration");
const failed = await waitFor("runtime", "start-failed", "migration");
const failedTask = ((failed.status as Record<string, unknown>).task as Record<string, unknown>);
assert.equal(failedTask.code, "migration-wait-failed");
assert.equal(failedTask.exitCode, 46);
rmSync(join(fakeRoot, "fail-migration"));
writeFileSync(join(fakeRoot, "slow"), "1\n");
await projection("start", "cancel", true);
const stopped = await projection("stop", "cancel");
await projection("start", "runtime", true, "web");
const stopped = await projection("stop", "runtime", false, "web");
assert.equal(stopped.startTaskDisposition, "cancel-requested");
assert.equal(stopped.mutation, true);
await waitFor("cancel", "start-canceled");
await waitFor("runtime", "start-canceled", "web");
const serialized = JSON.stringify({ submitted, succeeded, failed, stopped });
for (const name of [
"pikaoa-test.env",
"pikaoa-test-admin-password.txt",
"pikaoa-test-employee-password.txt",
]) rmSync(join(secretsDir, name));
const foundationPlan = await projection("plan", "foundation", false, "foundation");
assert.equal(foundationPlan.readyForMutation, true);
assert.deepEqual(foundationPlan.missingFields, []);
assert.deepEqual((foundationPlan.blockers as unknown[]), []);
const foundationObjects = ((foundationPlan.plan as Record<string, unknown>).objects as Array<Record<string, unknown>>);
assert.deepEqual(foundationObjects.map((object) => object.kind), ["Namespace", "PersistentVolumeClaim"]);
assert.equal(((foundationPlan.plan as Record<string, unknown>).secret as Record<string, unknown>).requiredForStep, false);
const foundationSubmitted = await projection("start", "foundation", false, "foundation");
assert.equal(foundationSubmitted.code, "start-submitted", JSON.stringify(lastCapture));
const foundationSucceeded = await waitFor("foundation", "start-succeeded", "foundation");
const foundationRepeated = await projection("start", "foundation", false, "foundation");
assert.equal(foundationRepeated.code, "start-already-succeeded");
const foundationRerun = await projectionWithCommit("start", "foundation", concurrentCommit, "foundation");
assert.equal(foundationRerun.code, "start-submitted");
assert.equal(foundationRerun.previousTaskReset, true);
const foundationRerunSucceeded = await waitFor("foundation", "start-succeeded", "foundation");
const serialized = JSON.stringify({
submitted,
succeeded,
concurrent,
rerunSubmitted,
rerunSucceeded,
manifestRerunSubmitted,
manifestRerunSucceeded,
sameRequestAfterRenderTimeChanged,
workerMissingSubmitted,
workerMissingReset,
workerMissingSucceeded,
failed,
stopped,
foundationSubmitted,
foundationSucceeded,
foundationRepeated,
foundationRerun,
foundationRerunSucceeded,
});
for (const value of secretValues) assert.equal(serialized.includes(value), false);
assert.equal(serialized.includes("postgres://pikaoa_test:"), false);
assert.equal(serialized.includes("postgresql://pikaoa_test:"), false);
} finally {
rmSync(root, { recursive: true, force: true });
}
}, 15_000);
}, 30_000);
test("validation-only fixture never invokes remote capture", async () => {
let calls = 0;
@@ -245,7 +337,16 @@ test("validation-only fixture never invokes remote capture", async () => {
const planPayload = JSON.parse(planResult.renderedText) as Record<string, unknown>;
const plan = planPayload.plan as Record<string, unknown>;
assert.deepEqual(plan.database, {
configRef: "config/platform-db/postgres-pk01.yaml#exports.connectionStrings.pikaoa-test-database-url",
name: "pikaoa_test",
username: "pikaoa_test",
schema: "pikaoa",
connection: {
sourceRef: "./secrets/pikaoa-test.env",
sourceKey: "DATABASE_URL",
targetKey: "DATABASE_URL",
valuesPrinted: false,
},
dsnParameterPresence: { sslmode: true, search_path: true },
valuesPrinted: false,
});
@@ -277,9 +378,16 @@ test("validation-only fixture never invokes remote capture", async () => {
rmSync(missingSchemaRoot, { recursive: true, force: true });
}
const unconfigured = await runPikaoaCommand({} as UniDeskConfig, ["test-target", "status", "--output", "json"], forbiddenCapture);
const unconfiguredPayload = JSON.parse(unconfigured.renderedText) as Record<string, unknown>;
assert.equal(calls, 0);
assert.equal(unconfiguredPayload.configured, false);
assert.equal(unconfiguredPayload.remoteQueried, false);
const unconfiguredRoot = mkdtempSync(join(tmpdir(), "pikaoa-test-target-unconfigured-"));
const unconfiguredPath = join(unconfiguredRoot, "pikaoa.yaml");
writeFileSync(unconfiguredPath, "version: 1\nkind: pikaoa-platform-delivery\ntestRuntime:\n defaultTargetId: null\n targets: {}\n");
try {
const unconfigured = await runPikaoaCommand({} as UniDeskConfig, ["test-target", "status", "--config", unconfiguredPath, "--output", "json"], forbiddenCapture);
const unconfiguredPayload = JSON.parse(unconfigured.renderedText) as Record<string, unknown>;
assert.equal(calls, 0);
assert.equal(unconfiguredPayload.configured, false);
assert.equal(unconfiguredPayload.remoteQueried, false);
} finally {
rmSync(unconfiguredRoot, { recursive: true, force: true });
}
});
File diff suppressed because it is too large Load Diff