forked from farhoodlabs/paperclip
fix(plugin): harden kubernetes sandbox orchestration
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
const execMock = vi.fn();
|
||||
|
||||
vi.mock("@kubernetes/client-node", () => ({
|
||||
Exec: vi.fn().mockImplementation(() => ({ exec: execMock })),
|
||||
}));
|
||||
|
||||
const { execInPod } = await import("../../src/pod-exec.js");
|
||||
|
||||
describe("execInPod", () => {
|
||||
beforeEach(() => {
|
||||
execMock.mockReset();
|
||||
});
|
||||
|
||||
it("returns success when the Kubernetes exec status callback reports success", async () => {
|
||||
execMock.mockImplementation((_namespace, _pod, _container, _command, stdout, _stderr, _stdin, _tty, statusCallback) => {
|
||||
stdout.write("ok\n");
|
||||
statusCallback({ status: "Success" });
|
||||
return Promise.resolve(new EventEmitter());
|
||||
});
|
||||
|
||||
const result = await execInPod({} as never, "ns", "pod-1", "agent", ["echo", "ok"]);
|
||||
expect(result).toEqual({ exitCode: 0, stdout: "ok\n", stderr: "" });
|
||||
});
|
||||
|
||||
it("returns an execution failure if the websocket closes before a status frame", async () => {
|
||||
const ws = new EventEmitter();
|
||||
execMock.mockResolvedValue(ws);
|
||||
|
||||
const resultPromise = execInPod({} as never, "ns", "pod-1", "agent", ["sleep", "1"]);
|
||||
await Promise.resolve();
|
||||
ws.emit("close", 1006, Buffer.from("connection lost"));
|
||||
|
||||
await expect(resultPromise).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stderr: expect.stringContaining("websocket closed before status frame"),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -150,4 +150,25 @@ describe("ensureTenant", () => {
|
||||
name: "paperclip-egress-allow",
|
||||
});
|
||||
});
|
||||
|
||||
it("handles concurrent first-run create conflicts by rereading and replacing managed resources", async () => {
|
||||
const clients = makeMockClients();
|
||||
const existing = { metadata: { resourceVersion: "rv-race" } };
|
||||
clients.core.createNamespace.mockRejectedValueOnce({ code: 409 });
|
||||
clients.core.readNamespacedServiceAccount
|
||||
.mockRejectedValueOnce({ code: 404 })
|
||||
.mockResolvedValue(existing);
|
||||
clients.core.createNamespacedServiceAccount.mockRejectedValueOnce({ code: 409 });
|
||||
|
||||
await ensureTenant(clients as never, baseInput);
|
||||
|
||||
expect(clients.core.createNamespace).toHaveBeenCalled();
|
||||
expect(clients.core.replaceNamespacedServiceAccount).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: expect.objectContaining({
|
||||
metadata: expect.objectContaining({ resourceVersion: "rv-race" }),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { deriveCompanySlug, deriveNamespaceName, newRunUlidDns, paperclipLabels } from "../../src/utils.js";
|
||||
|
||||
describe("deriveCompanySlug", () => {
|
||||
@@ -28,6 +28,13 @@ describe("newRunUlidDns", () => {
|
||||
const id = newRunUlidDns();
|
||||
expect(id).toMatch(/^[a-z0-9]{26}$/);
|
||||
});
|
||||
|
||||
it("does not use Math.random for the random suffix", () => {
|
||||
const spy = vi.spyOn(Math, "random");
|
||||
newRunUlidDns(() => 1);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("paperclipLabels", () => {
|
||||
|
||||
Reference in New Issue
Block a user