Files
kilocode/packages/opencode/test/cli/pr-status.test.ts
T
Igor Šćekić 90a93a7aa2 feat(opencode): link sessions to their pull request (#13137)
* feat(opencode): add PR link detect and parse helpers

detectPrLink runs gh pr view, parsePrUrl accepts GitHub and GitLab PR
URLs, and a Storage helper holds the manual override.

* feat(opencode): add kilo pr link, unlink, and status commands

kilo pr becomes a parent command with checkout preserved. link and
unlink write the Storage override; the next heartbeat persists it.

* feat(opencode): advertise prLink on the heartbeat and ingest it

getSessions resolves the Storage override, cleared, or detected link,
puts prLink on the heartbeat, and syncs the session_pr_link item.

* fix(opencode): encode worktree in pr link override storage key

The manual override key used the raw absolute worktree path. Storage
builds the file with path.join, so a Windows drive colon made an invalid
filename and kilo pr link failed. Encode the worktree so the key is one
valid path segment on both platforms.

* test(opencode): cover kilo pr status outputs

Extract the status handler body so it is testable, then assert the four
outputs: stored link, cleared, detected, and no link. Split captured
output on os.EOL so the test passes on Windows.

* refactor(opencode): remove dead pr-link code found in simplify pass

detectPrLink now reuses parsePrUrl instead of hand-parsing the URL and
number, drop the dead github.com special case, and inline the
prLinkTripleKey helper.

* fix(opencode): clear pr-link dedupe map on session delete

* docs(kilo-docs): regenerate CLI reference for kilo pr subcommands

* chore: add session-pr-link changeset and bump facade allowlist
2026-08-16 09:33:14 +01:00

74 lines
2.5 KiB
TypeScript

// kilocode_change - new file
import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test"
import { EOL } from "node:os"
import { Effect } from "effect"
// Mock @/kilo-sessions/pr-link before importing the command so the status
// handler reads the override/detection from these stubs instead of spawning
// `gh` or touching real Storage.
const realPrLink = await import("@/kilo-sessions/pr-link")
let override: { platform: string; prUrl: string; prNumber: number } | { cleared: true } | undefined
let detected: { platform: string; prUrl: string; prNumber: number } | undefined
const readOverride = mock(async (_worktree: string) => override)
const detect = mock(async () => detected)
void mock.module("@/kilo-sessions/pr-link", () => ({
...realPrLink,
readPrLinkOverride: readOverride,
detectPrLink: detect,
}))
import { prStatusHandler } from "../../src/cli/cmd/pr"
import { InstanceRef } from "../../src/effect/instance-ref"
import type { InstanceContext } from "../../src/project/instance-context"
const writeSpy = spyOn(process.stderr, "write")
function lines() {
return writeSpy.mock.calls
.map((call) => String(call[0]))
.join("")
.split(EOL)
.filter(Boolean)
}
function runStatus(worktree: string) {
const ctx = { directory: worktree, worktree, project: {} } as unknown as InstanceContext
return Effect.runPromise(prStatusHandler().pipe(Effect.provideService(InstanceRef, ctx)))
}
describe("pr status", () => {
beforeEach(() => {
override = undefined
detected = undefined
readOverride.mockClear()
detect.mockClear()
writeSpy.mockClear()
})
test("prints the stored link", async () => {
override = { platform: "github", prUrl: "https://github.com/owner/repo/pull/123", prNumber: 123 }
await runStatus("/tmp/foo")
expect(lines()).toEqual(["Linked PR #123 (github)", "https://github.com/owner/repo/pull/123"])
})
test("prints cleared", async () => {
override = { cleared: true }
await runStatus("/tmp/foo")
expect(lines()).toEqual(["PR link cleared"])
})
test("prints the detected link", async () => {
detected = { platform: "gitlab", prUrl: "https://gitlab.com/group/proj/-/merge_requests/45", prNumber: 45 }
await runStatus("/tmp/foo")
expect(lines()).toEqual(["Detected PR #45 (gitlab)", "https://gitlab.com/group/proj/-/merge_requests/45"])
})
test("prints no PR linked", async () => {
await runStatus("/tmp/foo")
expect(lines()).toEqual(["no PR linked"])
})
})