Skip to content

Commit 2e488e8

Browse files
committed
test(issue-117): cover source-ref parsing and closed-source policy; add changeset
1 parent 83a9ce0 commit 2e488e8

3 files changed

Lines changed: 177 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@prover-coder-ai/docker-git": minor
3+
---
4+
5+
Add opt-in automatic deletion of containers whose originating GitHub issue or pull request has been closed. Enable with `DOCKER_GIT_AUTO_DELETE_CLOSED=1` (scan interval configurable via `DOCKER_GIT_AUTO_DELETE_SCAN_INTERVAL_SECONDS`, default 300s). Deletion is conservative: it never runs for open/unknown source states, nor while an agent or live interactive session is active.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { describe, expect, it } from "vitest"
2+
3+
import {
4+
decideProjectClosedSourceAction,
5+
type ProjectClosedSourcePolicyInput
6+
} from "../src/services/project-closed-source-policy.js"
7+
8+
const makeInput = (overrides: Partial<ProjectClosedSourcePolicyInput> = {}): ProjectClosedSourcePolicyInput => ({
9+
sourceState: "closed",
10+
hasActiveAgent: false,
11+
hasLiveInteractiveSession: false,
12+
...overrides
13+
})
14+
15+
describe("project closed-source policy", () => {
16+
it("deletes a closed-source project with no active work", () => {
17+
expect(decideProjectClosedSourceAction(makeInput())).toEqual({ _tag: "Delete" })
18+
})
19+
20+
it("keeps an open-source project", () => {
21+
expect(decideProjectClosedSourceAction(makeInput({ sourceState: "open" }))).toEqual({
22+
_tag: "Keep",
23+
reason: "source-open-or-unknown"
24+
})
25+
})
26+
27+
it("keeps a project whose source state is unknown", () => {
28+
expect(decideProjectClosedSourceAction(makeInput({ sourceState: "unknown" }))).toEqual({
29+
_tag: "Keep",
30+
reason: "source-open-or-unknown"
31+
})
32+
})
33+
34+
it("keeps a closed-source project while an agent is active", () => {
35+
expect(decideProjectClosedSourceAction(makeInput({ hasActiveAgent: true }))).toEqual({
36+
_tag: "Keep",
37+
reason: "active-agent"
38+
})
39+
})
40+
41+
it("keeps a closed-source project while an interactive session is live", () => {
42+
expect(decideProjectClosedSourceAction(makeInput({ hasLiveInteractiveSession: true }))).toEqual({
43+
_tag: "Keep",
44+
reason: "live-interactive-session"
45+
})
46+
})
47+
48+
it("prefers the active-agent reason over a live interactive session", () => {
49+
expect(
50+
decideProjectClosedSourceAction(makeInput({ hasActiveAgent: true, hasLiveInteractiveSession: true }))
51+
).toEqual({ _tag: "Keep", reason: "active-agent" })
52+
})
53+
54+
it("never deletes an open project even with no active work", () => {
55+
expect(decideProjectClosedSourceAction(makeInput({ sourceState: "open" }))._tag).toBe("Keep")
56+
})
57+
})
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { describe, expect, it } from "vitest"
2+
3+
import {
4+
normalizeProjectSourceState,
5+
parseProjectSourceRef,
6+
shouldDeleteForSourceState
7+
} from "../../src/core/project-source-ref.js"
8+
9+
describe("parseProjectSourceRef", () => {
10+
it("recovers a GitHub issue identity from issue-<n>", () => {
11+
expect(parseProjectSourceRef("https://github.com/owner/repo.git", "issue-42")).toEqual({
12+
provider: "github",
13+
owner: "owner",
14+
repo: "repo",
15+
kind: "issue",
16+
number: "42"
17+
})
18+
})
19+
20+
it("recovers a GitHub pull-request identity from refs/pull/<n>/head", () => {
21+
expect(parseProjectSourceRef("https://github.com/owner/repo.git", "refs/pull/7/head")).toEqual({
22+
provider: "github",
23+
owner: "owner",
24+
repo: "repo",
25+
kind: "pull",
26+
number: "7"
27+
})
28+
})
29+
30+
it("recovers a GitLab merge-request identity from refs/merge-requests/<n>/head", () => {
31+
expect(parseProjectSourceRef("https://gitlab.com/group/repo.git", "refs/merge-requests/9/head")).toEqual({
32+
provider: "gitlab",
33+
owner: "group/repo",
34+
repo: "repo",
35+
kind: "pull",
36+
number: "9"
37+
})
38+
})
39+
40+
it("recovers a GitLab issue identity from issue-<n>", () => {
41+
expect(parseProjectSourceRef("https://gitlab.com/group/sub/repo.git", "issue-3")).toEqual({
42+
provider: "gitlab",
43+
owner: "group/sub/repo",
44+
repo: "repo",
45+
kind: "issue",
46+
number: "3"
47+
})
48+
})
49+
50+
it("trims surrounding whitespace from the ref", () => {
51+
expect(parseProjectSourceRef("https://github.com/owner/repo.git", " issue-1 ")?.number).toBe("1")
52+
})
53+
54+
it("returns null for a plain branch ref", () => {
55+
expect(parseProjectSourceRef("https://github.com/owner/repo.git", "main")).toBeNull()
56+
})
57+
58+
it("returns null for an empty ref", () => {
59+
expect(parseProjectSourceRef("https://github.com/owner/repo.git", " ")).toBeNull()
60+
})
61+
62+
it("returns null when the repo URL is not a known provider", () => {
63+
expect(parseProjectSourceRef("https://example.com/owner/repo.git", "issue-1")).toBeNull()
64+
})
65+
66+
it("does not treat a GitHub merge-request style ref as a pull request", () => {
67+
expect(parseProjectSourceRef("https://github.com/owner/repo.git", "refs/merge-requests/9/head")).toBeNull()
68+
})
69+
})
70+
71+
describe("normalizeProjectSourceState", () => {
72+
it("maps GitHub open to open", () => {
73+
expect(normalizeProjectSourceState("open")).toBe("open")
74+
})
75+
76+
it("maps GitLab opened to open", () => {
77+
expect(normalizeProjectSourceState("opened")).toBe("open")
78+
})
79+
80+
it("maps closed to closed", () => {
81+
expect(normalizeProjectSourceState("closed")).toBe("closed")
82+
})
83+
84+
it("maps GitLab merged to closed", () => {
85+
expect(normalizeProjectSourceState("merged")).toBe("closed")
86+
})
87+
88+
it("maps GitLab locked to closed", () => {
89+
expect(normalizeProjectSourceState("locked")).toBe("closed")
90+
})
91+
92+
it("is case- and whitespace-insensitive", () => {
93+
expect(normalizeProjectSourceState(" CLOSED ")).toBe("closed")
94+
})
95+
96+
it("maps unrecognized values to unknown", () => {
97+
expect(normalizeProjectSourceState("draft")).toBe("unknown")
98+
})
99+
100+
it("maps null and undefined to unknown", () => {
101+
expect(normalizeProjectSourceState(null)).toBe("unknown")
102+
expect(normalizeProjectSourceState(undefined)).toBe("unknown")
103+
})
104+
})
105+
106+
describe("shouldDeleteForSourceState", () => {
107+
it("deletes only when the source is closed", () => {
108+
expect(shouldDeleteForSourceState("closed")).toBe(true)
109+
})
110+
111+
it("never deletes for open or unknown", () => {
112+
expect(shouldDeleteForSourceState("open")).toBe(false)
113+
expect(shouldDeleteForSourceState("unknown")).toBe(false)
114+
})
115+
})

0 commit comments

Comments
 (0)