423 lines
21 KiB
TypeScript
423 lines
21 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.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]],
|
|
]) 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_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"] + "|" + item["metadata"]["labels"]["pikaoa.unidesk.io/instance"])
|
|
break
|
|
PY
|
|
)"
|
|
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
|
|
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 ' get job/pikaoa-migrate '; then
|
|
if [ -f "$root/fail-migration-query" ]; then printf '%s\n' 'job query failed' >&2; exit 1; fi
|
|
if [ -f "$root/failure-target-migration" ]; then
|
|
printf '%s\n' '{"status":{"conditions":[{"type":"FailureTarget","status":"True","reason":"BackoffLimitExceeded"}]}}'
|
|
elif [ -f "$root/fail-migration" ]; then
|
|
printf '%s\n' '{"status":{"conditions":[{"type":"Failed","status":"True","reason":"BackoffLimitExceeded"}]}}'
|
|
else
|
|
printf '%s\n' '{"status":{"conditions":[{"type":"Complete","status":"True"}]}}'
|
|
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="$(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,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, 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, 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 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, false, step);
|
|
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 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", "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, {
|
|
rootPath: stateRoot,
|
|
statusRequestTimeoutSeconds: 31,
|
|
logTailLines: 101,
|
|
});
|
|
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("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", "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", "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", "runtime");
|
|
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", "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", "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-job-failed");
|
|
assert.equal(failedTask.exitCode, 46);
|
|
rmSync(join(fakeRoot, "fail-migration"));
|
|
|
|
writeFileSync(join(fakeRoot, "failure-target-migration"), "1\n");
|
|
await projectionWithCommit("start", "runtime", concurrentCommit, "migration");
|
|
const failureTarget = await waitFor("runtime", "start-failed", "migration");
|
|
const failureTargetTask = ((failureTarget.status as Record<string, unknown>).task as Record<string, unknown>);
|
|
assert.equal(failureTargetTask.code, "migration-job-failure-target");
|
|
assert.equal(failureTargetTask.exitCode, 46);
|
|
rmSync(join(fakeRoot, "failure-target-migration"));
|
|
|
|
writeFileSync(join(fakeRoot, "fail-migration-query"), "1\n");
|
|
await projectionWithCommit("start", "runtime", "abcdef0123456789", "migration");
|
|
const migrationQueryFailed = await waitFor("runtime", "start-failed", "migration");
|
|
const migrationQueryFailedTask = ((migrationQueryFailed.status as Record<string, unknown>).task as Record<string, unknown>);
|
|
assert.equal(migrationQueryFailedTask.code, "migration-job-query-failed");
|
|
assert.equal(migrationQueryFailedTask.exitCode, 46);
|
|
rmSync(join(fakeRoot, "fail-migration-query"));
|
|
|
|
await projectionWithCommit("start", "runtime", "1234567890abcdef", "migration");
|
|
const migrationSucceeded = await waitFor("runtime", "start-succeeded", "migration");
|
|
assert.equal((((migrationSucceeded.status as Record<string, unknown>).task as Record<string, unknown>).exitCode), 0);
|
|
|
|
writeFileSync(join(fakeRoot, "slow"), "1\n");
|
|
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("runtime", "start-canceled", "web");
|
|
|
|
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,
|
|
failureTarget,
|
|
migrationQueryFailed,
|
|
migrationSucceeded,
|
|
stopped,
|
|
foundationSubmitted,
|
|
foundationSucceeded,
|
|
foundationRepeated,
|
|
foundationRerun,
|
|
foundationRerunSucceeded,
|
|
});
|
|
for (const value of secretValues) assert.equal(serialized.includes(value), false);
|
|
assert.equal(serialized.includes("postgresql://pikaoa_test:"), false);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
}, 30_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 planResult = await runPikaoaCommand({} as UniDeskConfig, [
|
|
"test-target", "plan", "--config", "config/fixtures/pikaoa-test-target.yaml", "--target", "TEST01", "--instance", "validation-only", "--commit", "0123456789abcdef", "--output", "json",
|
|
], forbiddenCapture);
|
|
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,
|
|
});
|
|
assert.deepEqual(plan.runtimeConfig, {
|
|
secretName: "pikaoa-test-runtime",
|
|
key: "pikaoa.yaml",
|
|
mountPath: "/etc/pikaoa/pikaoa.yaml",
|
|
consumers: ["migration", "api", "worker"],
|
|
valuesPrinted: false,
|
|
});
|
|
assert.equal(planPayload.valuesPrinted, false);
|
|
assert.equal(JSON.stringify(planPayload).includes("fixture-db-password"), false);
|
|
assert.equal(JSON.stringify(planPayload).includes("postgres://"), false);
|
|
|
|
const missingSchemaRoot = mkdtempSync(join(tmpdir(), "pikaoa-test-target-missing-schema-"));
|
|
const missingSchemaPath = join(missingSchemaRoot, "pikaoa.yaml");
|
|
const missingSchemaFixture = readFileSync(rootPath("config", "fixtures", "pikaoa-test-target.yaml"), "utf8")
|
|
.replace(" schema: pikaoa\n", "");
|
|
writeFileSync(missingSchemaPath, missingSchemaFixture);
|
|
try {
|
|
await assert.rejects(
|
|
runPikaoaCommand({} as UniDeskConfig, [
|
|
"test-target", "status", "--config", missingSchemaPath, "--target", "TEST01", "--instance", "missing-schema", "--output", "json",
|
|
], forbiddenCapture),
|
|
/testRuntime\.targets\.TEST01\.database\.schema 必须是非空字符串/u,
|
|
);
|
|
assert.equal(calls, 0);
|
|
} finally {
|
|
rmSync(missingSchemaRoot, { recursive: true, force: true });
|
|
}
|
|
|
|
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 });
|
|
}
|
|
});
|