feat: v2
This commit is contained in:
@@ -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")}`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user