266 lines
8.3 KiB
TypeScript
266 lines
8.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
// Mock the kube-client module so the plugin handlers run against injected
|
|
// fake API clients instead of a real cluster. h.clients is swapped per test.
|
|
const h = vi.hoisted(() => ({ clients: {} as Record<string, unknown> }));
|
|
|
|
vi.mock("../../src/kube-client.js", () => ({
|
|
createKubeConfig: vi.fn(() => ({})),
|
|
makeKubeClients: vi.fn(() => h.clients),
|
|
}));
|
|
|
|
import plugin from "../../src/plugin.js";
|
|
|
|
const CONFIG = { inCluster: true, backend: "sandbox-cr" };
|
|
|
|
function leaseMetadata(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
return {
|
|
namespace: "paperclip-acme",
|
|
jobName: "pc-abc",
|
|
podName: "pc-abc-pod",
|
|
secretName: "pc-abc-env",
|
|
phase: "Pending",
|
|
backend: "sandbox-cr",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function notFound(): Error {
|
|
return Object.assign(new Error("not found"), { code: 404 });
|
|
}
|
|
|
|
function readySandboxCr(podName: string): Record<string, unknown> {
|
|
return {
|
|
metadata: { uid: "uid-1" },
|
|
status: {
|
|
conditions: [{ type: "Ready", status: "True" }],
|
|
podName,
|
|
},
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
h.clients = {};
|
|
});
|
|
|
|
describe("onEnvironmentResumeLease", () => {
|
|
it("is implemented (Daytona feature parity)", () => {
|
|
expect(plugin.definition.onEnvironmentResumeLease).toBeTypeOf("function");
|
|
expect(plugin.definition.onEnvironmentDestroyLease).toBeTypeOf("function");
|
|
});
|
|
|
|
it("returns a valid lease handle for a live sandbox-cr lease", async () => {
|
|
h.clients = {
|
|
custom: {
|
|
getNamespacedCustomObject: vi.fn().mockResolvedValue(readySandboxCr("pc-abc-pod")),
|
|
},
|
|
core: {
|
|
readNamespacedPod: vi.fn().mockResolvedValue({
|
|
metadata: {},
|
|
status: { phase: "Running" },
|
|
}),
|
|
},
|
|
};
|
|
|
|
const lease = await plugin.definition.onEnvironmentResumeLease!({
|
|
driverKey: "kubernetes",
|
|
companyId: "acme",
|
|
environmentId: "env-1",
|
|
config: CONFIG,
|
|
providerLeaseId: "pc-abc",
|
|
leaseMetadata: leaseMetadata(),
|
|
});
|
|
|
|
expect(lease.providerLeaseId).toBe("pc-abc");
|
|
expect(lease.metadata).toEqual(
|
|
expect.objectContaining({
|
|
namespace: "paperclip-acme",
|
|
jobName: "pc-abc",
|
|
podName: "pc-abc-pod",
|
|
secretName: "pc-abc-env",
|
|
phase: "Running",
|
|
backend: "sandbox-cr",
|
|
resumedLease: true,
|
|
// sandbox-cr has a pod-exec channel, so native file sync stays enabled.
|
|
nativeFileSyncUnsupported: false,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("flags a resumed job-backend lease as native-sync-unsupported so the server keeps the base64 fallback", async () => {
|
|
h.clients = {
|
|
batch: {
|
|
readNamespacedJobStatus: vi.fn().mockResolvedValue({ status: { active: 1 } }),
|
|
},
|
|
core: {
|
|
listNamespacedPod: vi.fn().mockResolvedValue({
|
|
items: [{ metadata: { name: "pc-job-pod" }, status: { phase: "Running" } }],
|
|
}),
|
|
},
|
|
};
|
|
|
|
const lease = await plugin.definition.onEnvironmentResumeLease!({
|
|
driverKey: "kubernetes",
|
|
companyId: "acme",
|
|
environmentId: "env-1",
|
|
config: { inCluster: true, backend: "job" },
|
|
providerLeaseId: "pc-job",
|
|
leaseMetadata: leaseMetadata({ jobName: "pc-job", backend: "job", podName: "pc-job-pod" }),
|
|
});
|
|
|
|
expect(lease.providerLeaseId).toBe("pc-job");
|
|
expect(lease.metadata).toEqual(
|
|
expect.objectContaining({
|
|
backend: "job",
|
|
// The job backend has no exec channel; its native sync hook rejects, so
|
|
// the lease must fall back to the byte-identical base64 transport.
|
|
nativeFileSyncUnsupported: true,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("returns providerLeaseId null (expired) when the Sandbox CR is gone, so the caller falls back to acquireLease", async () => {
|
|
h.clients = {
|
|
custom: { getNamespacedCustomObject: vi.fn().mockRejectedValue(notFound()) },
|
|
core: { readNamespacedPod: vi.fn() },
|
|
};
|
|
|
|
const lease = await plugin.definition.onEnvironmentResumeLease!({
|
|
driverKey: "kubernetes",
|
|
companyId: "acme",
|
|
environmentId: "env-1",
|
|
config: CONFIG,
|
|
providerLeaseId: "pc-abc",
|
|
leaseMetadata: leaseMetadata(),
|
|
});
|
|
|
|
expect(lease.providerLeaseId).toBeNull();
|
|
expect(lease.metadata?.expired).toBe(true);
|
|
expect(lease.metadata?.reason).toMatch(/no longer exists/);
|
|
});
|
|
|
|
it("returns providerLeaseId null when the backing pod is gone", async () => {
|
|
h.clients = {
|
|
custom: {
|
|
getNamespacedCustomObject: vi.fn().mockResolvedValue(readySandboxCr("pc-abc-pod")),
|
|
},
|
|
core: { readNamespacedPod: vi.fn().mockRejectedValue(notFound()) },
|
|
};
|
|
|
|
const lease = await plugin.definition.onEnvironmentResumeLease!({
|
|
driverKey: "kubernetes",
|
|
companyId: "acme",
|
|
environmentId: "env-1",
|
|
config: CONFIG,
|
|
providerLeaseId: "pc-abc",
|
|
leaseMetadata: leaseMetadata(),
|
|
});
|
|
|
|
expect(lease.providerLeaseId).toBeNull();
|
|
expect(lease.metadata?.expired).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("onEnvironmentDestroyLease", () => {
|
|
it("deletes the Sandbox CR, pod, and per-run Secret", async () => {
|
|
const deleteCr = vi.fn().mockResolvedValue({});
|
|
const deletePod = vi.fn().mockResolvedValue({});
|
|
const deleteSecret = vi.fn().mockResolvedValue({});
|
|
h.clients = {
|
|
custom: { deleteNamespacedCustomObject: deleteCr },
|
|
core: { deleteNamespacedPod: deletePod, deleteNamespacedSecret: deleteSecret },
|
|
batch: { deleteNamespacedJob: vi.fn() },
|
|
};
|
|
|
|
await plugin.definition.onEnvironmentDestroyLease!({
|
|
driverKey: "kubernetes",
|
|
companyId: "acme",
|
|
environmentId: "env-1",
|
|
config: CONFIG,
|
|
providerLeaseId: "pc-abc",
|
|
leaseMetadata: leaseMetadata(),
|
|
});
|
|
|
|
expect(deleteCr).toHaveBeenCalledWith(
|
|
expect.objectContaining({ namespace: "paperclip-acme", name: "pc-abc" }),
|
|
);
|
|
expect(deletePod).toHaveBeenCalledWith({
|
|
namespace: "paperclip-acme",
|
|
name: "pc-abc-pod",
|
|
});
|
|
expect(deleteSecret).toHaveBeenCalledWith({
|
|
namespace: "paperclip-acme",
|
|
name: "pc-abc-env",
|
|
});
|
|
});
|
|
|
|
it("is idempotent: resolves cleanly when every resource is already gone (404)", async () => {
|
|
h.clients = {
|
|
custom: { deleteNamespacedCustomObject: vi.fn().mockRejectedValue(notFound()) },
|
|
core: {
|
|
deleteNamespacedPod: vi.fn().mockRejectedValue(notFound()),
|
|
deleteNamespacedSecret: vi.fn().mockRejectedValue(notFound()),
|
|
},
|
|
batch: { deleteNamespacedJob: vi.fn() },
|
|
};
|
|
|
|
await expect(
|
|
plugin.definition.onEnvironmentDestroyLease!({
|
|
driverKey: "kubernetes",
|
|
companyId: "acme",
|
|
environmentId: "env-1",
|
|
config: CONFIG,
|
|
providerLeaseId: "pc-abc",
|
|
leaseMetadata: leaseMetadata(),
|
|
}),
|
|
).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("is a no-op when providerLeaseId is null", async () => {
|
|
const deleteCr = vi.fn();
|
|
h.clients = {
|
|
custom: { deleteNamespacedCustomObject: deleteCr },
|
|
core: { deleteNamespacedPod: vi.fn(), deleteNamespacedSecret: vi.fn() },
|
|
batch: { deleteNamespacedJob: vi.fn() },
|
|
};
|
|
|
|
await plugin.definition.onEnvironmentDestroyLease!({
|
|
driverKey: "kubernetes",
|
|
companyId: "acme",
|
|
environmentId: "env-1",
|
|
config: CONFIG,
|
|
providerLeaseId: null,
|
|
leaseMetadata: undefined,
|
|
});
|
|
|
|
expect(deleteCr).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("deletes the Job for job-backend leases", async () => {
|
|
const deleteJob = vi.fn().mockResolvedValue({});
|
|
const deleteCr = vi.fn();
|
|
h.clients = {
|
|
custom: { deleteNamespacedCustomObject: deleteCr },
|
|
core: {
|
|
deleteNamespacedPod: vi.fn().mockResolvedValue({}),
|
|
deleteNamespacedSecret: vi.fn().mockResolvedValue({}),
|
|
},
|
|
batch: { deleteNamespacedJob: deleteJob },
|
|
};
|
|
|
|
await plugin.definition.onEnvironmentDestroyLease!({
|
|
driverKey: "kubernetes",
|
|
companyId: "acme",
|
|
environmentId: "env-1",
|
|
config: { inCluster: true, backend: "job" },
|
|
providerLeaseId: "pc-job",
|
|
leaseMetadata: leaseMetadata({ jobName: "pc-job", backend: "job", podName: "pc-job-pod", secretName: "pc-job-env" }),
|
|
});
|
|
|
|
expect(deleteJob).toHaveBeenCalledWith(
|
|
expect.objectContaining({ namespace: "paperclip-acme", name: "pc-job" }),
|
|
);
|
|
expect(deleteCr).not.toHaveBeenCalled();
|
|
});
|
|
});
|