Files
paperclip-adapter-opencode-k8s/src/server/job-manifest.test.ts
T
Chris Farhood 6866da42bf 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>
2026-04-12 08:14:57 -04:00

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");
});
});