test: add coverage for deletionTimestamp concurrency guard bypass (FAR-34)

Verifies that a terminating K8s job (deletionTimestamp set, no
Complete/Failed condition) is skipped by the concurrency guard so
subsequent heartbeat runs are not incorrectly blocked.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-24 19:57:10 +00:00
parent 357f035418
commit 986f2fc7fa
2 changed files with 23 additions and 1 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "paperclip-adapter-claude-k8s",
"version": "0.1.43",
"version": "0.1.44",
"description": "Paperclip adapter plugin that runs Claude Code agents as Kubernetes Jobs",
"license": "MIT",
"repository": {
+22
View File
@@ -577,6 +577,28 @@ describe("execute: concurrency guard", () => {
expect(result.errorMessage).toContain("still running for this agent");
});
it("ignores terminating jobs (deletionTimestamp set) and proceeds past the concurrency guard", async () => {
// A job being force-deleted has deletionTimestamp set but no Complete/Failed condition.
// The guard must treat it as terminal so subsequent runs are not blocked.
const terminating: k8s.V1Job = {
metadata: {
name: "terminating-job",
namespace: "paperclip",
labels: { "paperclip.io/agent-id": "agent-abc", "paperclip.io/adapter-type": "claude_k8s" },
deletionTimestamp: new Date(),
},
status: { conditions: [] },
};
mockBatchListJobs.mockResolvedValue({ items: [terminating] });
// Guard passes → next failure is job creation (no further mocks set up)
mockBatchCreateJob.mockRejectedValue(new Error("quota exceeded"));
mockPrepareBundle.mockResolvedValue(makeBundle());
const result = await execute(makeCtx());
// Must NOT be a concurrency error — the guard let us through
expect(result.errorCode).not.toBe("k8s_concurrent_run_blocked");
expect(result.errorCode).toBe("k8s_job_create_failed");
});
it("reattaches to a matching orphan and returns k8s_pod_reattach_failed when pod is missing", async () => {
// Orphan with matching taskId and sessionId → reattach classification → reattachTarget is set
const orphan = makeJob({