6866da42bf
- Add vitest with 26 passing tests for parse and job-manifest - Set models to undefined for free-text model input - Add fsGroupChangePolicy: "OnRootMismatch" to reduce volume chown delays - Change job name prefix to agent-opencode- for adapter identification - Add .npmrc to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { buildJobManifest, type JobBuildInput } from "./job-manifest.js";
|
|
|
|
const mockSelfPod: JobBuildInput["selfPod"] = {
|
|
namespace: "paperclip",
|
|
image: "paperclip/paperclip:latest",
|
|
imagePullSecrets: [],
|
|
inheritedEnv: {},
|
|
pvcClaimName: null,
|
|
dnsConfig: undefined,
|
|
secretVolumes: [],
|
|
};
|
|
|
|
const mockCtx: JobBuildInput["ctx"] = {
|
|
runId: "run123456",
|
|
agent: { id: "agent-abc", name: "Test Agent", companyId: "co123", adapterType: null, adapterConfig: null },
|
|
runtime: { sessionId: null, sessionParams: {}, sessionDisplayId: null, taskKey: null },
|
|
config: {},
|
|
context: {
|
|
taskId: null,
|
|
issueId: null,
|
|
paperclipWorkspace: null,
|
|
issueIds: null,
|
|
paperclipWorkspaces: null,
|
|
paperclipRuntimeServiceIntents: null,
|
|
paperclipRuntimeServices: null,
|
|
},
|
|
onLog: async () => {},
|
|
};
|
|
|
|
describe("buildJobManifest", () => {
|
|
it("creates job with agent-opencode- prefix in name", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
expect(result.jobName).toMatch(/^agent-opencode-/);
|
|
});
|
|
|
|
it("uses default image from selfPod", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
const container = result.job.spec?.template?.spec?.containers?.[0];
|
|
expect(container?.image).toBe("paperclip/paperclip:latest");
|
|
});
|
|
|
|
it("sets fsGroupChangePolicy to OnRootMismatch", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
const securityContext = result.job.spec?.template?.spec?.securityContext;
|
|
expect(securityContext?.fsGroupChangePolicy).toBe("OnRootMismatch");
|
|
});
|
|
|
|
it("sets runAsNonRoot and runAsUser 1000", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
const securityContext = result.job.spec?.template?.spec?.securityContext;
|
|
expect(securityContext?.runAsNonRoot).toBe(true);
|
|
expect(securityContext?.runAsUser).toBe(1000);
|
|
expect(securityContext?.runAsGroup).toBe(1000);
|
|
expect(securityContext?.fsGroup).toBe(1000);
|
|
});
|
|
|
|
it("maps labels to job metadata", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
expect(result.job.metadata?.labels?.["app.kubernetes.io/managed-by"]).toBe("paperclip");
|
|
expect(result.job.metadata?.labels?.["paperclip.io/adapter-type"]).toBe("opencode_k8s");
|
|
expect(result.job.metadata?.labels?.["paperclip.io/agent-id"]).toBe("agent-abc");
|
|
expect(result.job.metadata?.labels?.["paperclip.io/run-id"]).toBe("run123456");
|
|
});
|
|
|
|
it("creates init container for prompt", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
const initContainers = result.job.spec?.template?.spec?.initContainers;
|
|
expect(initContainers?.length).toBe(1);
|
|
expect(initContainers?.[0].name).toBe("write-prompt");
|
|
expect(initContainers?.[0].image).toBe("busybox:1.36");
|
|
});
|
|
|
|
it("sets HOME to /paperclip", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
const env = result.job.spec?.template?.spec?.containers?.[0].env ?? [];
|
|
const homeEnv = env.find((e) => e.name === "HOME");
|
|
expect(homeEnv?.value).toBe("/paperclip");
|
|
});
|
|
|
|
it("sets OPENCODE_DISABLE_PROJECT_CONFIG=true", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
const env = result.job.spec?.template?.spec?.containers?.[0].env ?? [];
|
|
const opencodeEnv = env.find((e) => e.name === "OPENCODE_DISABLE_PROJECT_CONFIG");
|
|
expect(opencodeEnv?.value).toBe("true");
|
|
});
|
|
|
|
it("applies default ttlSecondsAfterFinished of 300", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
expect(result.job.spec?.ttlSecondsAfterFinished).toBe(300);
|
|
});
|
|
|
|
it("sets backoffLimit to 0", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
expect(result.job.spec?.backoffLimit).toBe(0);
|
|
});
|
|
|
|
it("uses job template restartPolicy Never", () => {
|
|
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod });
|
|
|
|
expect(result.job.spec?.template?.spec?.restartPolicy).toBe("Never");
|
|
});
|
|
});
|