test(agentic): add logic_test.go — 66 tests for 10 pure functions

Covers agentCommand, containerCommand, buildAutoPRBody, emitEvent,
countFileRefs, modelVariant, baseAgent, resolveWorkspace,
findWorkspaceByPR, and extractPRNumber with _Good/_Bad/_Ugly cases.
All 66 pass. Uses t.TempDir() + t.Setenv("CORE_WORKSPACE") for
filesystem-dependent tests.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-24 22:59:42 +00:00
parent 60b0b0b63b
commit 80b827b7c8

664
pkg/agentic/logic_test.go Normal file
View file

@ -0,0 +1,664 @@
// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"bufio"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- agentCommand ---
func TestAgentCommand_Good_Gemini(t *testing.T) {
cmd, args, err := agentCommand("gemini", "do the thing")
require.NoError(t, err)
assert.Equal(t, "gemini", cmd)
assert.Contains(t, args, "-p")
assert.Contains(t, args, "do the thing")
assert.Contains(t, args, "--yolo")
assert.Contains(t, args, "--sandbox")
}
func TestAgentCommand_Good_GeminiWithModel(t *testing.T) {
cmd, args, err := agentCommand("gemini:flash", "my prompt")
require.NoError(t, err)
assert.Equal(t, "gemini", cmd)
assert.Contains(t, args, "-m")
assert.Contains(t, args, "gemini-2.5-flash")
}
func TestAgentCommand_Good_Codex(t *testing.T) {
cmd, args, err := agentCommand("codex", "fix the tests")
require.NoError(t, err)
assert.Equal(t, "codex", cmd)
assert.Contains(t, args, "exec")
assert.Contains(t, args, "--dangerously-bypass-approvals-and-sandbox")
assert.Contains(t, args, "fix the tests")
}
func TestAgentCommand_Good_CodexReview(t *testing.T) {
cmd, args, err := agentCommand("codex:review", "")
require.NoError(t, err)
assert.Equal(t, "codex", cmd)
assert.Contains(t, args, "exec")
// Review mode should NOT include -o flag
for _, a := range args {
assert.NotEqual(t, "-o", a)
}
}
func TestAgentCommand_Good_CodexWithModel(t *testing.T) {
cmd, args, err := agentCommand("codex:gpt-5.4", "refactor this")
require.NoError(t, err)
assert.Equal(t, "codex", cmd)
assert.Contains(t, args, "--model")
assert.Contains(t, args, "gpt-5.4")
}
func TestAgentCommand_Good_Claude(t *testing.T) {
cmd, args, err := agentCommand("claude", "add tests")
require.NoError(t, err)
assert.Equal(t, "claude", cmd)
assert.Contains(t, args, "-p")
assert.Contains(t, args, "add tests")
assert.Contains(t, args, "--dangerously-skip-permissions")
}
func TestAgentCommand_Good_ClaudeWithModel(t *testing.T) {
cmd, args, err := agentCommand("claude:haiku", "write docs")
require.NoError(t, err)
assert.Equal(t, "claude", cmd)
assert.Contains(t, args, "--model")
assert.Contains(t, args, "haiku")
}
func TestAgentCommand_Good_CodeRabbit(t *testing.T) {
cmd, args, err := agentCommand("coderabbit", "")
require.NoError(t, err)
assert.Equal(t, "coderabbit", cmd)
assert.Contains(t, args, "review")
assert.Contains(t, args, "--plain")
}
func TestAgentCommand_Good_Local(t *testing.T) {
cmd, args, err := agentCommand("local", "do stuff")
require.NoError(t, err)
assert.Equal(t, "sh", cmd)
assert.Equal(t, "-c", args[0])
// Script should contain socat proxy setup
assert.Contains(t, args[1], "socat")
assert.Contains(t, args[1], "devstral-24b")
}
func TestAgentCommand_Good_LocalWithModel(t *testing.T) {
cmd, args, err := agentCommand("local:mistral-nemo", "do stuff")
require.NoError(t, err)
assert.Equal(t, "sh", cmd)
assert.Contains(t, args[1], "mistral-nemo")
}
func TestAgentCommand_Bad_Unknown(t *testing.T) {
cmd, args, err := agentCommand("robot-from-the-future", "take over")
assert.Error(t, err)
assert.Empty(t, cmd)
assert.Nil(t, args)
}
func TestAgentCommand_Ugly_EmptyAgent(t *testing.T) {
cmd, args, err := agentCommand("", "prompt")
assert.Error(t, err)
assert.Empty(t, cmd)
assert.Nil(t, args)
}
// --- containerCommand ---
func TestContainerCommand_Good_Codex(t *testing.T) {
t.Setenv("AGENT_DOCKER_IMAGE", "")
t.Setenv("DIR_HOME", "/home/dev")
cmd, args := containerCommand("codex", "codex", []string{"exec", "--dangerously-bypass-approvals-and-sandbox", "do it"}, "/ws/repo", "/ws/.meta")
assert.Equal(t, "docker", cmd)
assert.Contains(t, args, "run")
assert.Contains(t, args, "--rm")
assert.Contains(t, args, "/ws/repo:/workspace")
assert.Contains(t, args, "/ws/.meta:/workspace/.meta")
assert.Contains(t, args, "codex")
// Should use default image
assert.Contains(t, args, defaultDockerImage)
}
func TestContainerCommand_Good_CustomImage(t *testing.T) {
t.Setenv("AGENT_DOCKER_IMAGE", "my-custom-image:latest")
t.Setenv("DIR_HOME", "/home/dev")
cmd, args := containerCommand("codex", "codex", []string{"exec"}, "/ws/repo", "/ws/.meta")
assert.Equal(t, "docker", cmd)
assert.Contains(t, args, "my-custom-image:latest")
}
func TestContainerCommand_Good_ClaudeMountsConfig(t *testing.T) {
t.Setenv("AGENT_DOCKER_IMAGE", "")
t.Setenv("DIR_HOME", "/home/dev")
_, args := containerCommand("claude", "claude", []string{"-p", "do it"}, "/ws/repo", "/ws/.meta")
joined := strings.Join(args, " ")
assert.Contains(t, joined, ".claude:/home/dev/.claude:ro")
}
func TestContainerCommand_Good_GeminiMountsConfig(t *testing.T) {
t.Setenv("AGENT_DOCKER_IMAGE", "")
t.Setenv("DIR_HOME", "/home/dev")
_, args := containerCommand("gemini", "gemini", []string{"-p", "do it"}, "/ws/repo", "/ws/.meta")
joined := strings.Join(args, " ")
assert.Contains(t, joined, ".gemini:/home/dev/.gemini:ro")
}
func TestContainerCommand_Good_CodexNoClaudeMount(t *testing.T) {
t.Setenv("AGENT_DOCKER_IMAGE", "")
t.Setenv("DIR_HOME", "/home/dev")
_, args := containerCommand("codex", "codex", []string{"exec"}, "/ws/repo", "/ws/.meta")
joined := strings.Join(args, " ")
// codex agent must NOT mount .claude config
assert.NotContains(t, joined, ".claude:/home/dev/.claude:ro")
}
func TestContainerCommand_Good_APIKeysPassedByRef(t *testing.T) {
t.Setenv("AGENT_DOCKER_IMAGE", "")
t.Setenv("DIR_HOME", "/home/dev")
_, args := containerCommand("codex", "codex", []string{"exec"}, "/ws/repo", "/ws/.meta")
joined := strings.Join(args, " ")
assert.Contains(t, joined, "OPENAI_API_KEY")
assert.Contains(t, joined, "ANTHROPIC_API_KEY")
assert.Contains(t, joined, "GEMINI_API_KEY")
}
func TestContainerCommand_Ugly_EmptyDirs(t *testing.T) {
t.Setenv("AGENT_DOCKER_IMAGE", "")
t.Setenv("DIR_HOME", "")
// Should not panic with empty paths
cmd, args := containerCommand("codex", "codex", []string{"exec"}, "", "")
assert.Equal(t, "docker", cmd)
assert.NotEmpty(t, args)
}
// --- buildAutoPRBody ---
func TestBuildAutoPRBody_Good_Basic(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{
Task: "Fix the login bug",
Agent: "codex",
Branch: "agent/fix-login-bug",
}
body := s.buildAutoPRBody(st, 3)
assert.Contains(t, body, "Fix the login bug")
assert.Contains(t, body, "codex")
assert.Contains(t, body, "3")
assert.Contains(t, body, "agent/fix-login-bug")
assert.Contains(t, body, "Co-Authored-By: Virgil <virgil@lethean.io>")
}
func TestBuildAutoPRBody_Good_WithIssue(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{
Task: "Add rate limiting",
Agent: "claude",
Branch: "agent/add-rate-limiting",
Issue: 42,
}
body := s.buildAutoPRBody(st, 1)
assert.Contains(t, body, "Closes #42")
}
func TestBuildAutoPRBody_Good_NoIssue(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{
Task: "Refactor internals",
Agent: "gemini",
Branch: "agent/refactor-internals",
}
body := s.buildAutoPRBody(st, 5)
assert.NotContains(t, body, "Closes #")
}
func TestBuildAutoPRBody_Good_CommitCount(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{Agent: "codex", Branch: "agent/foo"}
body1 := s.buildAutoPRBody(st, 1)
body5 := s.buildAutoPRBody(st, 5)
assert.Contains(t, body1, "**Commits:** 1")
assert.Contains(t, body5, "**Commits:** 5")
}
func TestBuildAutoPRBody_Bad_EmptyTask(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{
Task: "",
Agent: "codex",
Branch: "agent/something",
}
// Should not panic; body should still have the structure
body := s.buildAutoPRBody(st, 0)
assert.Contains(t, body, "## Task")
assert.Contains(t, body, "**Agent:** codex")
}
func TestBuildAutoPRBody_Ugly_ZeroCommits(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{Agent: "codex", Branch: "agent/test"}
body := s.buildAutoPRBody(st, 0)
assert.Contains(t, body, "**Commits:** 0")
}
// --- emitEvent ---
func TestEmitEvent_Good_WritesJSONL(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK)
emitEvent("agent_completed", "codex", "core/go-io/task-5", "completed")
eventsFile := filepath.Join(root, "workspace", "events.jsonl")
r := fs.Read(eventsFile)
require.True(t, r.OK, "events.jsonl should exist after emitEvent")
content := r.Value.(string)
assert.Contains(t, content, "agent_completed")
assert.Contains(t, content, "codex")
assert.Contains(t, content, "core/go-io/task-5")
assert.Contains(t, content, "completed")
}
func TestEmitEvent_Good_ValidJSON(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK)
emitEvent("agent_started", "claude", "core/agent/task-1", "running")
eventsFile := filepath.Join(root, "workspace", "events.jsonl")
f, err := os.Open(eventsFile)
require.NoError(t, err)
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
var ev CompletionEvent
require.NoError(t, json.Unmarshal([]byte(line), &ev), "each line must be valid JSON")
assert.Equal(t, "agent_started", ev.Type)
}
}
func TestEmitEvent_Good_Appends(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK)
emitEvent("agent_started", "codex", "core/go-io/task-1", "running")
emitEvent("agent_completed", "codex", "core/go-io/task-1", "completed")
eventsFile := filepath.Join(root, "workspace", "events.jsonl")
r := fs.Read(eventsFile)
require.True(t, r.OK)
lines := 0
for _, line := range strings.Split(strings.TrimSpace(r.Value.(string)), "\n") {
if line != "" {
lines++
}
}
assert.Equal(t, 2, lines, "both events should be in the log")
}
func TestEmitEvent_Good_StartHelper(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK)
emitStartEvent("gemini", "core/go-log/task-3")
eventsFile := filepath.Join(root, "workspace", "events.jsonl")
r := fs.Read(eventsFile)
require.True(t, r.OK)
assert.Contains(t, r.Value.(string), "agent_started")
assert.Contains(t, r.Value.(string), "running")
}
func TestEmitEvent_Good_CompletionHelper(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK)
emitCompletionEvent("claude", "core/agent/task-7", "failed")
eventsFile := filepath.Join(root, "workspace", "events.jsonl")
r := fs.Read(eventsFile)
require.True(t, r.OK)
assert.Contains(t, r.Value.(string), "agent_completed")
assert.Contains(t, r.Value.(string), "failed")
}
func TestEmitEvent_Bad_NoWorkspaceDir(t *testing.T) {
// CORE_WORKSPACE points to a directory that doesn't allow writing events.jsonl
// because workspace/ subdir doesn't exist. Should not panic.
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// Do NOT create workspace/ subdir — emitEvent must handle this gracefully
assert.NotPanics(t, func() {
emitEvent("agent_completed", "codex", "test", "completed")
})
}
func TestEmitEvent_Ugly_EmptyFields(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK)
// Should not panic with all empty fields
assert.NotPanics(t, func() {
emitEvent("", "", "", "")
})
}
// --- countFileRefs ---
func TestCountFileRefs_Good_GoRefs(t *testing.T) {
body := "Found issue in `pkg/core/app.go:42` and `pkg/core/service.go:100`."
assert.Equal(t, 2, countFileRefs(body))
}
func TestCountFileRefs_Good_PHPRefs(t *testing.T) {
body := "See `src/Core/Boot.php:15` for details."
assert.Equal(t, 1, countFileRefs(body))
}
func TestCountFileRefs_Good_Mixed(t *testing.T) {
body := "Go file: `main.go:1`, PHP file: `index.php:99`, plain text ref."
assert.Equal(t, 2, countFileRefs(body))
}
func TestCountFileRefs_Good_NoRefs(t *testing.T) {
body := "This is just plain text with no file references."
assert.Equal(t, 0, countFileRefs(body))
}
func TestCountFileRefs_Good_UnrelatedBacktick(t *testing.T) {
// Backtick-quoted string that is not a file:line reference
body := "Run `go test ./...` to execute tests."
assert.Equal(t, 0, countFileRefs(body))
}
func TestCountFileRefs_Bad_EmptyBody(t *testing.T) {
assert.Equal(t, 0, countFileRefs(""))
}
func TestCountFileRefs_Bad_ShortBody(t *testing.T) {
// Body too short to contain a valid reference
assert.Equal(t, 0, countFileRefs("`a`"))
}
func TestCountFileRefs_Ugly_MalformedBackticks(t *testing.T) {
// Unclosed backtick — should not panic or hang
body := "Something `unclosed"
assert.NotPanics(t, func() {
countFileRefs(body)
})
}
func TestCountFileRefs_Ugly_LongRef(t *testing.T) {
// Reference longer than 100 chars should not be counted (loop limit)
longRef := "`" + strings.Repeat("a", 101) + ".go:1`"
assert.Equal(t, 0, countFileRefs(longRef))
}
// --- modelVariant ---
func TestModelVariant_Good_WithModel(t *testing.T) {
assert.Equal(t, "gpt-5.4", modelVariant("codex:gpt-5.4"))
assert.Equal(t, "flash", modelVariant("gemini:flash"))
assert.Equal(t, "opus", modelVariant("claude:opus"))
assert.Equal(t, "haiku", modelVariant("claude:haiku"))
}
func TestModelVariant_Good_NoVariant(t *testing.T) {
assert.Equal(t, "", modelVariant("codex"))
assert.Equal(t, "", modelVariant("claude"))
assert.Equal(t, "", modelVariant("gemini"))
}
func TestModelVariant_Good_MultipleColons(t *testing.T) {
// SplitN(2) only splits on first colon; rest is preserved as the model
assert.Equal(t, "gpt-5.3-codex-spark", modelVariant("codex:gpt-5.3-codex-spark"))
}
func TestModelVariant_Bad_EmptyString(t *testing.T) {
assert.Equal(t, "", modelVariant(""))
}
func TestModelVariant_Ugly_ColonOnly(t *testing.T) {
// Just a colon with no model name
assert.Equal(t, "", modelVariant(":"))
}
// --- baseAgent ---
func TestBaseAgent_Good_Variants(t *testing.T) {
assert.Equal(t, "gemini", baseAgent("gemini:flash"))
assert.Equal(t, "gemini", baseAgent("gemini:pro"))
assert.Equal(t, "claude", baseAgent("claude:haiku"))
assert.Equal(t, "codex", baseAgent("codex:gpt-5.4"))
}
func TestBaseAgent_Good_NoVariant(t *testing.T) {
assert.Equal(t, "codex", baseAgent("codex"))
assert.Equal(t, "claude", baseAgent("claude"))
assert.Equal(t, "gemini", baseAgent("gemini"))
}
func TestBaseAgent_Good_CodexSparkSpecialCase(t *testing.T) {
// codex-spark variants map to their own pool name
assert.Equal(t, "codex-spark", baseAgent("codex:gpt-5.3-codex-spark"))
assert.Equal(t, "codex-spark", baseAgent("codex-spark"))
}
func TestBaseAgent_Bad_EmptyString(t *testing.T) {
// Empty string — SplitN returns [""], so first element is ""
assert.Equal(t, "", baseAgent(""))
}
func TestBaseAgent_Ugly_JustColon(t *testing.T) {
// Just a colon — base is empty string before colon
assert.Equal(t, "", baseAgent(":model"))
}
// --- resolveWorkspace ---
func TestResolveWorkspace_Good_ExistingDir(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// Create the workspace directory structure
wsName := "core/go-io/task-5"
wsDir := filepath.Join(root, "workspace", wsName)
require.True(t, fs.EnsureDir(wsDir).OK)
result := resolveWorkspace(wsName)
assert.Equal(t, wsDir, result)
}
func TestResolveWorkspace_Good_NestedPath(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsName := "core/agent/pr-42"
wsDir := filepath.Join(root, "workspace", wsName)
require.True(t, fs.EnsureDir(wsDir).OK)
result := resolveWorkspace(wsName)
assert.Equal(t, wsDir, result)
}
func TestResolveWorkspace_Bad_NonExistentDir(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
result := resolveWorkspace("core/go-io/task-999")
assert.Equal(t, "", result)
}
func TestResolveWorkspace_Bad_EmptyName(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// Empty name resolves to the workspace root itself — which is a dir but not a workspace
// The function returns "" if the path is not a directory, and the workspace root *is*
// a directory if created. This test verifies the path arithmetic is sane.
result := resolveWorkspace("")
// Either the workspace root itself or "" — both are acceptable; must not panic.
_ = result
}
func TestResolveWorkspace_Ugly_PathTraversal(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// Path traversal attempt should return "" (parent of workspace root won't be a workspace)
result := resolveWorkspace("../../etc")
assert.Equal(t, "", result)
}
// --- findWorkspaceByPR ---
func TestFindWorkspaceByPR_Good_MatchesFlatLayout(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "workspace", "task-10")
require.True(t, fs.EnsureDir(wsDir).OK)
require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Branch: "agent/fix-timeout",
}))
result := findWorkspaceByPR("go-io", "agent/fix-timeout")
assert.Equal(t, wsDir, result)
}
func TestFindWorkspaceByPR_Good_MatchesDeepLayout(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "workspace", "core", "go-io", "task-15")
require.True(t, fs.EnsureDir(wsDir).OK)
require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{
Status: "running",
Repo: "go-io",
Branch: "agent/add-metrics",
}))
result := findWorkspaceByPR("go-io", "agent/add-metrics")
assert.Equal(t, wsDir, result)
}
func TestFindWorkspaceByPR_Bad_NoMatch(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "workspace", "task-99")
require.True(t, fs.EnsureDir(wsDir).OK)
require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Branch: "agent/some-other-branch",
}))
result := findWorkspaceByPR("go-io", "agent/nonexistent-branch")
assert.Equal(t, "", result)
}
func TestFindWorkspaceByPR_Bad_EmptyWorkspace(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// No workspaces at all
result := findWorkspaceByPR("go-io", "agent/any-branch")
assert.Equal(t, "", result)
}
func TestFindWorkspaceByPR_Bad_RepoDiffers(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "workspace", "task-5")
require.True(t, fs.EnsureDir(wsDir).OK)
require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{
Status: "completed",
Repo: "go-log",
Branch: "agent/fix-formatter",
}))
// Same branch, different repo
result := findWorkspaceByPR("go-io", "agent/fix-formatter")
assert.Equal(t, "", result)
}
func TestFindWorkspaceByPR_Ugly_CorruptStatusFile(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "workspace", "corrupt-ws")
require.True(t, fs.EnsureDir(wsDir).OK)
require.True(t, fs.Write(filepath.Join(wsDir, "status.json"), "not-valid-json{").OK)
// Should skip corrupt entries, not panic
result := findWorkspaceByPR("go-io", "agent/any")
assert.Equal(t, "", result)
}
// --- extractPRNumber ---
func TestExtractPRNumber_Good_FullURL(t *testing.T) {
assert.Equal(t, 42, extractPRNumber("https://forge.lthn.ai/core/agent/pulls/42"))
assert.Equal(t, 1, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/1"))
assert.Equal(t, 999, extractPRNumber("https://forge.lthn.ai/core/go-log/pulls/999"))
}
func TestExtractPRNumber_Good_NumberOnly(t *testing.T) {
// If someone passes a bare number as a URL it should still work
assert.Equal(t, 7, extractPRNumber("7"))
}
func TestExtractPRNumber_Bad_EmptyURL(t *testing.T) {
assert.Equal(t, 0, extractPRNumber(""))
}
func TestExtractPRNumber_Bad_TrailingSlash(t *testing.T) {
// URL ending with slash has empty last segment
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/"))
}
func TestExtractPRNumber_Bad_NonNumericEnd(t *testing.T) {
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/abc"))
}
func TestExtractPRNumber_Ugly_JustSlashes(t *testing.T) {
// All slashes — last segment is empty
assert.Equal(t, 0, extractPRNumber("///"))
}