Files
kuber/tests/command/up-api.test.ts
T
2026-09-03 11:28:30 +07:00

117 lines
3.5 KiB
TypeScript

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")}`);
});
});