117 lines
3.5 KiB
TypeScript
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")}`);
|
|
});
|
|
});
|