Files
pikasTech-unidesk/scripts/src/pikaoa-test-target-async.test.ts
T
2026-07-14 20:48:33 +02:00

248 lines
11 KiB
TypeScript

import assert from "node:assert/strict";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "bun:test";
import type { UniDeskConfig } from "./config";
import { rootPath } from "./config";
import { runPikaoaCommand } from "./pikaoa-test-target";
import type { SshCaptureResult } from "./ssh";
test("PikaOA test target submits once and exposes bounded typed status", async () => {
const root = mkdtempSync(join(tmpdir(), "pikaoa-test-target-"));
const stateRoot = join(root, "state");
const fakeRoot = join(root, "fake-k8s");
const binDir = join(root, "bin");
const secretsDir = join(root, "secrets");
mkdirSync(binDir, { recursive: true });
mkdirSync(secretsDir, { recursive: true });
mkdirSync(join(fakeRoot, "namespaces"), { recursive: true });
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-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");
writeFileSync(fakeKubectl, `#!/bin/sh
set -u
root="$FAKE_K8S_ROOT"
args="$*"
namespace=""
previous=""
for argument in "$@"; do
if [ "$previous" = "-n" ]; then namespace="$argument"; fi
previous="$argument"
done
namespace_after_resource() {
previous=""
for argument in "$@"; do
if [ "$previous" = "namespace" ]; then printf '%s' "$argument"; return; fi
previous="$argument"
done
}
if printf '%s' "$args" | grep -Eq '(^| )apply( |$)'; then
file=""
previous=""
for argument in "$@"; do
if [ "$previous" = "-f" ]; then file="$argument"; fi
previous="$argument"
done
if grep -q '"kind":"Namespace"' "$file"; then
applied_namespace="$(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"])
break
PY
)"
: >"$root/namespaces/$applied_namespace"
fi
[ ! -f "$root/slow" ] || sleep 0.2
exit 0
fi
if printf '%s' "$args" | grep -q ' rollout status '; then
[ ! -f "$root/slow" ] || sleep 0.4
exit 0
fi
if printf '%s' "$args" | grep -q ' wait '; then
[ ! -f "$root/slow" ] || sleep 0.4
if [ -f "$root/fail-migration" ]; then printf '%s\n' 'migration failed' >&2; exit 1; fi
exit 0
fi
if printf '%s' "$args" | grep -Eq '(^| )get namespace '; then
name="$(namespace_after_resource "$@")"
if [ -f "$root/fail-runtime-query" ]; then
printf '%s\n' 'unable to connect to the server' >&2
exit 1
fi
if [ ! -f "$root/namespaces/$name" ]; then
printf 'Error from server (NotFound): namespaces "%s" not found\n' "$name" >&2
exit 1
fi
instance="\${name#pikaoa-test-}"
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 [ -f "$root/fail-runtime-resource-query" ]; then
printf '%s\n' 'resource query failed' >&2
exit 1
fi
printf '%s\n' '{"items":[]}'
exit 0
fi
if printf '%s' "$args" | grep -Eq '(^| )delete namespace '; then
name="$(namespace_after_resource "$@")"
rm -f "$root/namespaces/$name"
exit 0
fi
printf 'unsupported fake kubectl call: %s\n' "$args" >&2
exit 64
`, { mode: 0o700 });
chmodSync(fakeKubectl, 0o700);
const fixturePath = join(root, "pikaoa.yaml");
const fixture = readFileSync(rootPath("config", "fixtures", "pikaoa-test-target.yaml"), "utf8")
.replace("validationOnly: true", "validationOnly: false")
.replace("statusRequestTimeoutSeconds: 3", "statusRequestTimeoutSeconds: 31")
.replace("logTailLines: 8", "logTailLines: 101")
.replace("/tmp/unidesk-pikaoa-test-target-fixture", stateRoot);
writeFileSync(fixturePath, fixture);
const config = {} as UniDeskConfig;
let lastCapture: SshCaptureResult | null = null;
const localCapture = async (_config: UniDeskConfig, _route: string, args: string[], stdin: string): Promise<SshCaptureResult> => {
assert.deepEqual(args, ["sh"]);
const child = Bun.spawn(["sh"], {
env: { ...process.env, PATH: `${binDir}:${process.env.PATH ?? ""}`, FAKE_K8S_ROOT: fakeRoot },
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
child.stdin.write(stdin);
child.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
lastCapture = { stdout, stderr, exitCode };
return lastCapture;
};
const args = (action: string, instance: string, withCommit = false): string[] => [
"test-target", action, "--config", fixturePath, "--target", "TEST01", "--instance", instance,
...(withCommit ? ["--commit", "0123456789abcdef"] : []),
...((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);
return JSON.parse(result.renderedText) as Record<string, unknown>;
};
const waitFor = async (instance: string, expectedCode: string): Promise<Record<string, unknown>> => {
for (let attempt = 0; attempt < 80; attempt += 1) {
const value = await projection("status", instance);
const status = value.status as Record<string, unknown>;
if (status.code === expectedCode) return value;
await Bun.sleep(25);
}
assert.fail(`status did not reach ${expectedCode}`);
};
try {
const missing = await projection("status", "missing");
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);
assert.equal(submitted.code, "start-submitted", JSON.stringify(lastCapture));
assert.equal(submitted.mutation, true);
assert.deepEqual((submitted.target as Record<string, unknown>).taskState, {
rootPath: stateRoot,
statusRequestTimeoutSeconds: 31,
logTailLines: 101,
});
const running = await projection("status", "success");
assert.equal((running.status as Record<string, unknown>).code, "start-running");
rmSync(join(fakeRoot, "slow"));
const succeeded = await waitFor("success", "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 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);
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 runtimeResourceQueryFailedStatus = runtimeResourceQueryFailed.status as Record<string, unknown>;
assert.equal(runtimeResourceQueryFailed.ok, false);
assert.equal(runtimeResourceQueryFailedStatus.code, "runtime-resource-query-failed");
assert.equal(runtimeResourceQueryFailedStatus.taskCode, "start-succeeded");
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);
assert.equal(repeated.code, "start-already-succeeded");
assert.equal(repeated.mutation, false);
writeFileSync(join(fakeRoot, "fail-migration"), "1\n");
await projection("start", "failed", true);
const failed = await waitFor("failed", "start-failed");
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");
assert.equal(stopped.startTaskDisposition, "cancel-requested");
assert.equal(stopped.mutation, true);
await waitFor("cancel", "start-canceled");
const serialized = JSON.stringify({ submitted, succeeded, failed, stopped });
for (const value of secretValues) assert.equal(serialized.includes(value), false);
assert.equal(serialized.includes("postgres://pikaoa_test:"), false);
} finally {
rmSync(root, { recursive: true, force: true });
}
}, 15_000);
test("validation-only fixture never invokes remote capture", async () => {
let calls = 0;
const forbiddenCapture = async (): Promise<SshCaptureResult> => {
calls += 1;
throw new Error("validation-only fixture attempted remote capture");
};
const result = await runPikaoaCommand({} as UniDeskConfig, [
"test-target", "status", "--config", "config/fixtures/pikaoa-test-target.yaml", "--target", "TEST01", "--instance", "validation-only", "--output", "json",
], forbiddenCapture);
assert.equal(calls, 0);
const payload = JSON.parse(result.renderedText) as Record<string, unknown>;
assert.equal(payload.remoteQueried, false);
assert.equal((payload.status as Record<string, unknown>).remoteQueried, false);
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);
});