Add tests, free-text model field, and K8s job improvements
- 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>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user