Files
kuber/tests/lib/apply.test.ts
T

134 lines
4.1 KiB
TypeScript

import { afterEach, describe, expect, mock, spyOn, test } from "bun:test";
import type { KubernetesObject } from "@kubernetes/client-node";
import { applyResource, getResourceKey, sortResources } from "../../lib/apply";
import { objectApi } from "../../lib/k8s";
afterEach(() => mock.restore());
function resource(kind: string, name = kind): KubernetesObject {
return { apiVersion: "v1", kind, metadata: { name, namespace: "project" } };
}
describe("resource ordering", () => {
test("sorts creation dependencies in deterministic order", () => {
const input = [
resource("Database"),
resource("IngressRoute"),
resource("Deployment"),
resource("GarageKey"),
resource("HorizontalPodAutoscaler"),
resource("Service"),
resource("GarageBucket"),
resource("ConfigMap"),
resource("Secret"),
resource("PersistentVolumeClaim"),
resource("StorageClass"),
resource("RoleBinding"),
resource("ClusterRoleBinding"),
resource("Role"),
resource("ClusterRole"),
resource("ServiceAccount"),
resource("Namespace"),
resource("Ingress"),
];
expect(sortResources(input).map((item) => item.kind)).toEqual([
"Namespace",
"GarageBucket",
"GarageKey",
"ServiceAccount",
"ClusterRole",
"Role",
"ClusterRoleBinding",
"RoleBinding",
"StorageClass",
"PersistentVolumeClaim",
"Secret",
"ConfigMap",
"Service",
"Deployment",
"HorizontalPodAutoscaler",
"Ingress",
"IngressRoute",
"Database",
]);
expect(input[0]?.kind).toBe("Database");
});
test("preserves order for unknown resource kinds", () => {
expect(
sortResources([resource("First"), resource("Second")]).map(
(item) => item.kind,
),
).toEqual(["First", "Second"]);
});
test("builds keys with kind, namespace, and name", () => {
expect(getResourceKey(resource("Secret", "app-env"))).toBe(
"Secret:project:app-env",
);
expect(
getResourceKey({
apiVersion: "v1",
kind: "Namespace",
metadata: { name: "app" },
}),
).toBe("Namespace::app");
});
});
describe("resource apply", () => {
test("does not send server-managed fields from a resource read from Kubernetes", async () => {
const returned = {
apiVersion: "postgresql.cnpg.io/v1",
kind: "Database",
metadata: {
name: "orders",
namespace: "database",
labels: { "kuber.dev/project": "demo" },
annotations: { "example.dev/setting": "enabled" },
managedFields: [{ manager: "kube-controller-manager" }],
resourceVersion: "42",
uid: "database-uid",
creationTimestamp: new Date("2026-09-08T00:00:00Z"),
generation: 3,
selfLink:
"/apis/postgresql.cnpg.io/v1/namespaces/database/databases/orders",
},
spec: { name: "orders" },
status: { phase: "Ready" },
} as KubernetesObject;
const patch = spyOn(objectApi, "patch").mockImplementation(
async (resource) => resource as never,
);
await applyResource(returned);
expect(patch).toHaveBeenCalledWith(
expect.objectContaining({
metadata: {
name: "orders",
namespace: "database",
labels: { "kuber.dev/project": "demo" },
annotations: { "example.dev/setting": "enabled" },
},
spec: { name: "orders" },
}),
undefined,
undefined,
"kuber",
true,
"application/apply-patch+yaml",
);
const sent = patch.mock.calls[0]?.[0] as typeof returned;
expect(sent.metadata).not.toHaveProperty("managedFields");
expect(sent.metadata).not.toHaveProperty("resourceVersion");
expect(sent.metadata).not.toHaveProperty("uid");
expect(sent.metadata).not.toHaveProperty("creationTimestamp");
expect(sent.metadata).not.toHaveProperty("generation");
expect(sent.metadata).not.toHaveProperty("selfLink");
expect(sent).not.toHaveProperty("status");
expect(returned.metadata).toHaveProperty("managedFields");
expect(returned).toHaveProperty("status");
});
});