This commit is contained in:
2026-09-03 11:28:30 +07:00 Unverified
parent 26c2d0f015
commit 82d0fe3e0d
93 changed files with 19237 additions and 1285 deletions
+179
View File
@@ -0,0 +1,179 @@
import { describe, expect, test } from "bun:test";
import { listAuditEvents } from "../../command/audit";
import { main } from "../../command/main";
import { getOperation, listOperations } from "../../command/operations";
import {
addUser,
deleteUser,
listUsers,
revokeUserSessions,
setUserDisabled,
updateUser,
} from "../../command/users";
import type { ApiRequestInit } from "../../lib/api";
type Call = { path: string; init?: ApiRequestInit };
function requestReturning<T>(result: T, calls: Call[]) {
return async <R>(path: string, init?: ApiRequestInit): Promise<R> => {
calls.push({ path, init });
return result as unknown as R;
};
}
const user = {
username: "alice",
roles: ["admin"],
disabled: false,
updatedAt: "2026-09-02T10:00:00.000Z",
};
describe("user administration commands", () => {
test("lists and creates users through authenticated API routes", async () => {
const calls: Call[] = [];
expect(
await listUsers(requestReturning({ items: [user] }, calls)),
).toContain("alice");
expect(
await addUser(
"alice",
"secret",
["admin"],
requestReturning(user, calls),
),
).toContain("admin");
expect(calls).toEqual([
{ path: "/users", init: undefined },
{
path: "/users",
init: {
method: "POST",
json: { username: "alice", password: "secret", roles: ["admin"] },
},
},
]);
});
test("updates roles, passwords, and enabled state with PATCH", async () => {
const calls: Call[] = [];
const request = requestReturning(user, calls);
await updateUser(
"alice/example",
{ roles: ["operator"], password: "new" },
request,
);
await setUserDisabled("alice", true, request);
await setUserDisabled("alice", false, request);
expect(calls).toEqual([
{
path: "/users/alice%2Fexample",
init: {
method: "PATCH",
json: { roles: ["operator"], password: "new" },
},
},
{
path: "/users/alice",
init: { method: "PATCH", json: { disabled: true } },
},
{
path: "/users/alice",
init: { method: "PATCH", json: { disabled: false } },
},
]);
});
test("requires confirmation for deletion and supports session revocation", async () => {
const calls: Call[] = [];
const request = requestReturning(undefined, calls);
expect(await deleteUser("alice", false, request)).toBe(
"Deletion cancelled",
);
expect(await deleteUser("alice", true, request)).toBe("Deleted user alice");
expect(
await revokeUserSessions(
"alice",
requestReturning({ username: "alice", revoked: 2 }, calls),
),
).toBe("Revoked 2 sessions for alice");
expect(calls).toEqual([
{ path: "/users/alice", init: { method: "DELETE" } },
{
path: "/users/alice/sessions/revoke",
init: { method: "POST" },
},
]);
});
});
const operation = {
metadata: {
name: "operation-1",
creationTimestamp: "2026-09-02T10:00:00.000Z",
},
spec: { workspaceId: "team/shop", action: "restart" },
status: { state: "succeeded", result: { deployments: ["web"] } },
};
describe("operations and audit commands", () => {
test("lists filtered operations and gets operation details", async () => {
const calls: Call[] = [];
const listing = await listOperations(
"team/shop",
requestReturning({ items: [operation] }, calls),
);
const detail = await getOperation(
"operation/1",
requestReturning(operation, calls),
);
expect(listing).toContain("restart");
expect(detail).toContain('Result: {"deployments":["web"]}');
expect(calls).toEqual([
{ path: "/operations?workspaceId=team%2Fshop", init: undefined },
{ path: "/operations/operation%2F1", init: undefined },
]);
});
test("lists audit events with an optional workspace filter", async () => {
const calls: Call[] = [];
const output = await listAuditEvents(
"team/shop",
requestReturning(
{
items: [
{
metadata: {
name: "audit-1",
creationTimestamp: "2026-09-02T10:00:00.000Z",
},
spec: {
actor: { username: "alice" },
action: "workspace.restart",
workspaceId: "team/shop",
outcome: "success",
},
},
],
},
calls,
),
);
expect(output).toContain("alice");
expect(output).toContain("workspace.restart");
expect(calls).toEqual([
{ path: "/audit?workspaceId=team%2Fshop", init: undefined },
]);
});
test("registers administration command groups", async () => {
const subCommands = await Promise.resolve(main.subCommands);
expect(Object.keys(subCommands ?? {})).toEqual(
expect.arrayContaining(["users", "operations", "audit"]),
);
});
});
+141
View File
@@ -0,0 +1,141 @@
import { describe, expect, test } from "bun:test";
import { getDatabaseCredentials } from "../../command/db";
import { runDown } from "../../command/down";
import { runPs } from "../../command/ps";
import { runRestart } from "../../command/restart";
import { runRollback } from "../../command/rollback";
import { getRemoteS3Credentials } from "../../command/s3";
import { runStop } from "../../command/stop";
import type { ApiRequestInit } from "../../lib/api";
type Call = { path: string; init?: ApiRequestInit };
function requestReturning<T>(result: T, calls: Call[]) {
return async <R>(path: string, init?: ApiRequestInit): Promise<R> => {
calls.push({ path, init });
return result as unknown as R;
};
}
describe("workspace API command runners", () => {
test("ps requests structured status and renders it locally", async () => {
const calls: Call[] = [];
const output = await runPs(
"shop/demo",
true,
requestReturning(
[
{
namespace: "shop-demo",
roots: [
{
id: "Deployment/web",
status: { label: "1/1", level: "good" as const },
children: [],
},
],
},
],
calls,
),
);
expect(calls).toEqual([
{
path: "/workspaces/shop%2Fdemo/status?includeIdle=true",
init: undefined,
},
]);
expect(output).toContain("Deployment/web");
expect(output).toContain("\u001b[42m");
});
test("stop and restart use lifecycle actions with optional targeting", async () => {
const calls: Call[] = [];
const request = requestReturning({ deployments: ["web"] }, calls);
await runStop("shop", request);
await runRestart("shop", undefined, request);
await runRestart("shop", "worker", request);
expect(calls).toEqual([
{
path: "/workspaces/shop/lifecycle",
init: { method: "POST", json: { action: "stop" } },
},
{
path: "/workspaces/shop/lifecycle",
init: { method: "POST", json: { action: "restart" } },
},
{
path: "/workspaces/shop/lifecycle",
init: {
method: "POST",
json: { action: "restart", services: ["worker"] },
},
},
]);
});
test("rollback and down delegate complete server-side operations", async () => {
const calls: Call[] = [];
await runRollback(
"shop",
"web",
45_000,
requestReturning({ deployments: ["web"] }, calls),
);
await runDown(
"shop",
true,
requestReturning({ full: true, retained: [], delete: [] }, calls),
);
expect(calls).toEqual([
{
path: "/workspaces/shop/rollback",
init: {
method: "POST",
json: { services: ["web"], timeoutMs: 45_000 },
},
},
{
path: "/workspaces/shop/down",
init: { method: "POST", json: { full: true } },
},
]);
});
test("database and S3 credential requests send resolved local claims", async () => {
const calls: Call[] = [];
const databaseClaim = {
service: "web",
username: "app",
database: "app",
secretName: "postgres-app",
};
const s3Claim = { service: "web", key: "assets", bucket: "assets" };
await getDatabaseCredentials(
"shop",
databaseClaim,
requestReturning({ username: "app", password: "secret" }, calls),
);
await getRemoteS3Credentials(
"shop",
s3Claim,
requestReturning({ AWS_ACCESS_KEY_ID: "key" }, calls),
);
expect(calls).toEqual([
{
path: "/workspaces/shop/databases/credentials",
init: { method: "POST", json: { claim: databaseClaim } },
},
{
path: "/workspaces/shop/storage/credentials",
init: { method: "POST", json: { claim: s3Claim } },
},
]);
});
});
+57
View File
@@ -0,0 +1,57 @@
import { describe, expect, test } from "bun:test";
import { runExec, type ExecSessionOpener } from "../../command/exec";
import type { ExecApiSession, ExecOutputFrame } from "../../lib/exec-api";
function fakeSession(...frames: ExecOutputFrame[]): ExecApiSession {
return {
sendStdin() {},
resize() {},
close() {},
async *[Symbol.asyncIterator]() {
yield* frames;
},
};
}
describe("exec API command runner", () => {
test("opens the authenticated workspace endpoint and returns remote status", async () => {
let call: Parameters<ExecSessionOpener> | undefined;
const opener: ExecSessionOpener = async (...args) => {
call = args;
return fakeSession({ type: "exit", exitCode: 23 });
};
const controller = new AbortController();
const result = await runExec(
"shop/demo",
"api",
["sh", "-c", "exit 23"],
controller.signal,
opener,
);
expect(result).toBe(23);
expect(call?.[0]).toBe("shop/demo");
expect(call?.[1]).toMatchObject({
deployment: "api",
command: ["sh", "-c", "exit 23"],
tty: false,
});
expect(call?.[2]).toBe(controller.signal);
});
test("validates required arguments before opening a connection", async () => {
let calls = 0;
const opener: ExecSessionOpener = async () => {
calls += 1;
return fakeSession();
};
const signal = new AbortController().signal;
await expect(runExec("shop", "", ["sh"], signal, opener)).rejects.toThrow(
"Deployment name",
);
await expect(runExec("shop", "api", [], signal, opener)).rejects.toThrow(
"Command is required",
);
expect(calls).toBe(0);
});
});
+84
View File
@@ -0,0 +1,84 @@
import { describe, expect, test } from "bun:test";
import {
runLogs,
type LogApiEvent,
type LogEventWriter,
type LogsApiStream,
} from "../../command/logs";
describe("logs API command runner", () => {
test("uses the authenticated workspace NDJSON route and preserves prefixes", async () => {
const calls: Array<{
path: string;
signal?: AbortSignal;
timeout?: number;
}> = [];
const events: LogApiEvent[] = [
{ type: "heartbeat", timestamp: "now" },
{
type: "log",
targetName: "web",
pod: "web-1",
container: "web",
message: "ready",
},
{
type: "error",
pod: "web-1",
container: "web",
message: "disconnected",
retryable: true,
},
];
const stream: LogsApiStream = async function* (path, init, options) {
calls.push({
path,
signal: init?.signal ?? undefined,
timeout: options?.timeoutMs,
});
yield* events as never[];
};
const output: Parameters<LogEventWriter>[] = [];
const controller = new AbortController();
await runLogs(
"shop/demo",
"web api",
true,
controller.signal,
stream,
(...entry) => output.push(entry),
);
expect(calls).toEqual([
{
path: "/workspaces/shop%2Fdemo/logs?service=web+api&follow=true",
signal: controller.signal,
timeout: 0,
},
]);
expect(output).toEqual([
["web", "ready", false],
["web api", "disconnected", true],
]);
});
test("omits follow for finite collection and passes cancellation", async () => {
const controller = new AbortController();
let receivedSignal: AbortSignal | null | undefined;
const stream: LogsApiStream = async function* (path, init, options) {
expect(path).toBe("/workspaces/shop/logs");
expect(options?.timeoutMs).toBeUndefined();
receivedSignal = init?.signal;
yield* [];
};
await runLogs(
"shop",
undefined,
false,
controller.signal,
stream,
() => {},
);
expect(receivedSignal).toBe(controller.signal);
});
});
+9 -1
View File
@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
import { login, logout, whoami } from "../../command/auth";
import { db } from "../../command/db";
import { down } from "../../command/down";
import { exec } from "../../command/exec";
@@ -23,6 +24,9 @@ type Command = {
describe("CLI command definitions", () => {
const commands = [
["up", "Create and start deployments", up],
["login", "Log in to kuber.astrxl.dev", login],
["logout", "Log out of kuber.astrxl.dev", logout],
["whoami", "Show the current kuber user", whoami],
["start", "Start deployments without rebuilding images", start],
["stop", "Scale managed deployments to zero", stop],
["restart", "Roll out a restart for managed deployments", restart],
@@ -30,7 +34,8 @@ describe("CLI command definitions", () => {
["s3", "Inspect managed S3 storage", s3],
["down", "Delete managed resources except ingress and PVCs", down],
["ps", "List deployments", ps],
["logs", "Show deployment logs", logs], ["exec", "Execute a command inside a deployment pod", exec],
["logs", "Show deployment logs", logs],
["exec", "Execute a command inside a deployment pod", exec],
["export", "Write rendered manifests to a YAML file", exportCommand],
["db", "Inspect managed postgres databases", db],
] as const;
@@ -74,6 +79,9 @@ describe("CLI command definitions", () => {
expect((main as Command).args?.config).toMatchObject({
type: "string",
});
expect((login as Command).args?.persist).toMatchObject({
type: "boolean",
});
});
test("defines both database inspection subcommands", () => {
+116
View File
@@ -0,0 +1,116 @@
import { describe, expect, test } from "bun:test";
import type { ApiRequestInit } from "../../lib/api";
import { KuberApiError } from "../../lib/api";
import type { ApiRequester } from "../../lib/build";
import {
ensureWorkspace,
reconcileResources,
workspaceAdoptionRoute,
} from "../../command/up";
import { workspaceManifestDigest } from "../../lib/workspace";
const manifest = { version: 1 as const, files: [] };
const snapshot = {
manifest,
digest: workspaceManifestDigest(manifest),
blobs: [],
};
describe("up API pipeline", () => {
test("creates workspace metadata without embedding source blobs", async () => {
const calls: Array<{ path: string; init?: ApiRequestInit }> = [];
const request: ApiRequester = async <T>(
path: string,
init?: ApiRequestInit,
) => {
calls.push({ path, init });
if (!init) throw new KuberApiError("missing", 404);
return {
metadata: { name: "shop", uid: "uid", resourceVersion: "1" },
} as T;
};
await ensureWorkspace(
"shop",
{ services: { web: { image: "nginx" } } },
snapshot,
request,
);
expect(calls.map(({ path }) => path)).toEqual([
"/workspaces/shop",
"/workspaces",
]);
const body = calls[1]!.init?.json as Record<string, unknown>;
expect(body).toMatchObject({
id: "shop",
source: { uri: `cas://${snapshot.digest}`, digest: snapshot.digest },
});
expect(JSON.stringify(body)).not.toContain('"blob"');
expect(JSON.stringify(body)).not.toContain('"files"');
});
test("updates workspace metadata with the current resource-version ETag", async () => {
const calls: Array<{ path: string; init?: ApiRequestInit }> = [];
const request: ApiRequester = async <T>(
path: string,
init?: ApiRequestInit,
) => {
calls.push({ path, init });
return {
metadata: {
name: "shop",
uid: "uid",
resourceVersion: init ? "8" : "7",
},
} as T;
};
await ensureWorkspace("shop", { services: {} }, snapshot, request);
expect(calls[1]?.init?.method).toBe("PUT");
expect(new Headers(calls[1]?.init?.headers).get("if-match")).toBe('"7"');
});
test("plans, applies, runs the hook, waits, then deletes stale identities", async () => {
const order: string[] = [];
const desired = [
{ apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "web" } },
];
const stale = [
{
apiVersion: "v1",
kind: "Secret",
name: "old",
uid: "secret-uid",
workspaceUid: "workspace-uid",
},
];
const request: ApiRequester = async <T>(path: string) => {
order.push(path.split("/").at(-1)!);
if (path.endsWith("/plan")) return { desired, stale } as T;
return {} as T;
};
await reconcileResources(
"shop",
desired,
42_000,
() => {
order.push("hook");
},
request,
);
expect(order).toEqual(["plan", "apply", "hook", "wait", "delete"]);
});
test("fails closed with the precise missing adoption route", async () => {
const request: ApiRequester = async <T>(path: string) => {
if (path.endsWith("/plan"))
throw new Error(
"Namespace shop is external; refusing workspace mutation",
);
return {} as T;
};
await expect(
reconcileResources("shop", [], 1, undefined, request),
).rejects.toThrow(`POST ${workspaceAdoptionRoute("shop")}`);
});
});