test: add Ugly tests for queue, shutdown, verify, status, review_queue
- TestQueue_CanDispatchAgent_Ugly — Core.Config concurrency path - TestQueue_DrainQueue_Ugly — Core lock path - TestShutdown_ShutdownNow_Ugly — deep layout - TestVerify_AutoVerifyAndMerge_Ugly — invalid PR URL - TestVerify_AttemptVerifyAndMerge_Ugly — build failure - TestVerify_ExtractPRNumber_Ugly — edge cases - TestStatus_WriteStatus_Ugly — full roundtrip - TestReviewQueue_LoadRateLimitState_Ugly — corrupt JSON agentic 74.3%, 566 tests Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
acae0d804f
commit
8b46e15d24
5 changed files with 240 additions and 0 deletions
|
|
@ -9,6 +9,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
|
@ -185,3 +186,49 @@ func TestDrainOne_Good_SkipsBackedOffPool(t *testing.T) {
|
|||
}
|
||||
assert.False(t, s.drainOne())
|
||||
}
|
||||
|
||||
// --- canDispatchAgent (Ugly — with Core.Config concurrency) ---
|
||||
|
||||
func TestQueue_CanDispatchAgent_Ugly(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", root)
|
||||
os.MkdirAll(filepath.Join(root, "workspace"), 0o755)
|
||||
|
||||
c := core.New()
|
||||
// Set concurrency on Core.Config() — same path that Register() uses
|
||||
c.Config().Set("agents.concurrency", map[string]ConcurrencyLimit{
|
||||
"claude": {Total: 1},
|
||||
"gemini": {Total: 3},
|
||||
})
|
||||
|
||||
s := &PrepSubsystem{
|
||||
core: c,
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
// No running workspaces → should be able to dispatch
|
||||
assert.True(t, s.canDispatchAgent("claude"))
|
||||
assert.True(t, s.canDispatchAgent("gemini:flash"))
|
||||
// Agent with no limit configured → always allowed
|
||||
assert.True(t, s.canDispatchAgent("codex:gpt-5.4"))
|
||||
}
|
||||
|
||||
// --- drainQueue (Ugly — with Core lock path) ---
|
||||
|
||||
func TestQueue_DrainQueue_Ugly(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", root)
|
||||
os.MkdirAll(filepath.Join(root, "workspace"), 0o755)
|
||||
|
||||
c := core.New()
|
||||
s := &PrepSubsystem{
|
||||
core: c,
|
||||
frozen: false,
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
// Not frozen, Core is present, empty workspace → drainQueue runs the Core lock path without panic
|
||||
assert.NotPanics(t, func() { s.drainQueue() })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -209,3 +210,36 @@ func TestFindWorkspaceByPR_Good_DeepLayout(t *testing.T) {
|
|||
result := findWorkspaceByPR("agent", "agent/tests")
|
||||
assert.Equal(t, ws, result)
|
||||
}
|
||||
|
||||
// --- loadRateLimitState (Ugly — corrupt JSON) ---
|
||||
|
||||
func TestReviewQueue_LoadRateLimitState_Ugly(t *testing.T) {
|
||||
// core.Env("DIR_HOME") is cached at init, so we must write to the real path.
|
||||
// Save original content, write corrupt JSON, test, then restore.
|
||||
ratePath := filepath.Join(core.Env("DIR_HOME"), ".core", "coderabbit-ratelimit.json")
|
||||
|
||||
// Save original content (may or may not exist)
|
||||
original, readErr := os.ReadFile(ratePath)
|
||||
hadFile := readErr == nil
|
||||
|
||||
// Ensure parent dir exists
|
||||
os.MkdirAll(filepath.Dir(ratePath), 0o755)
|
||||
|
||||
// Write corrupt JSON
|
||||
require.NoError(t, os.WriteFile(ratePath, []byte("not-valid-json{{{"), 0o644))
|
||||
t.Cleanup(func() {
|
||||
if hadFile {
|
||||
os.WriteFile(ratePath, original, 0o644)
|
||||
} else {
|
||||
os.Remove(ratePath)
|
||||
}
|
||||
})
|
||||
|
||||
s := &PrepSubsystem{
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
result := s.loadRateLimitState()
|
||||
assert.Nil(t, result, "corrupt JSON should return nil")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -533,3 +533,39 @@ func TestDrainQueue_Good_FrozenDoesNothing(t *testing.T) {
|
|||
s.drainQueue()
|
||||
})
|
||||
}
|
||||
|
||||
// --- shutdownNow (Ugly — deep layout with queued status) ---
|
||||
|
||||
func TestShutdown_ShutdownNow_Ugly(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", root)
|
||||
wsRoot := filepath.Join(root, "workspace")
|
||||
|
||||
// Create workspace in deep layout (org/repo/task)
|
||||
ws := filepath.Join(wsRoot, "core", "go-io", "task-5")
|
||||
require.True(t, fs.EnsureDir(ws).OK)
|
||||
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
|
||||
Status: "queued",
|
||||
Repo: "go-io",
|
||||
Agent: "codex",
|
||||
Task: "Add tests",
|
||||
}))
|
||||
|
||||
s := &PrepSubsystem{
|
||||
frozen: false,
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
_, out, err := s.shutdownNow(context.Background(), nil, ShutdownInput{})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, out.Success)
|
||||
assert.True(t, s.frozen)
|
||||
assert.Contains(t, out.Message, "cleared 1")
|
||||
|
||||
// Verify the queued workspace is now failed
|
||||
st, err := ReadStatus(ws)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "failed", st.Status)
|
||||
assert.Contains(t, st.Question, "cleared by shutdown_now")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,3 +254,48 @@ func TestStatus_Status_Ugly(t *testing.T) {
|
|||
assert.Equal(t, "failed", st3.Status)
|
||||
assert.Equal(t, "Agent process died (no output log)", st3.Question)
|
||||
}
|
||||
|
||||
// --- writeStatus (extended Ugly) ---
|
||||
|
||||
func TestStatus_WriteStatus_Ugly(t *testing.T) {
|
||||
// Write a status with all fields, read back, verify UpdatedAt is set and all fields preserved
|
||||
dir := t.TempDir()
|
||||
|
||||
original := &WorkspaceStatus{
|
||||
Status: "blocked",
|
||||
Agent: "gemini:flash",
|
||||
Repo: "go-mcp",
|
||||
Org: "core",
|
||||
Task: "Refactor IPC handler",
|
||||
Branch: "agent/refactor-ipc",
|
||||
Issue: 77,
|
||||
PID: 999999, // dead PID — non-existent
|
||||
StartedAt: time.Now().Add(-10 * time.Minute).Truncate(time.Second),
|
||||
Question: "Should I break backward compat?",
|
||||
Runs: 5,
|
||||
PRURL: "https://forge.lthn.ai/core/go-mcp/pulls/12",
|
||||
}
|
||||
|
||||
err := writeStatus(dir, original)
|
||||
require.NoError(t, err)
|
||||
|
||||
// UpdatedAt should have been set by writeStatus
|
||||
assert.False(t, original.UpdatedAt.IsZero(), "writeStatus must set UpdatedAt")
|
||||
|
||||
// Read back and verify every field
|
||||
read, err := ReadStatus(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "blocked", read.Status)
|
||||
assert.Equal(t, "gemini:flash", read.Agent)
|
||||
assert.Equal(t, "go-mcp", read.Repo)
|
||||
assert.Equal(t, "core", read.Org)
|
||||
assert.Equal(t, "Refactor IPC handler", read.Task)
|
||||
assert.Equal(t, "agent/refactor-ipc", read.Branch)
|
||||
assert.Equal(t, 77, read.Issue)
|
||||
assert.Equal(t, 999999, read.PID)
|
||||
assert.Equal(t, "Should I break backward compat?", read.Question)
|
||||
assert.Equal(t, 5, read.Runs)
|
||||
assert.Equal(t, "https://forge.lthn.ai/core/go-mcp/pulls/12", read.PRURL)
|
||||
assert.False(t, read.UpdatedAt.IsZero(), "UpdatedAt must survive roundtrip")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -507,3 +507,81 @@ func TestTruncate_Bad_ZeroMax(t *testing.T) {
|
|||
func TestTruncate_Ugly_EmptyString(t *testing.T) {
|
||||
assert.Equal(t, "", truncate("", 10))
|
||||
}
|
||||
|
||||
// --- autoVerifyAndMerge (extended Ugly) ---
|
||||
|
||||
func TestVerify_AutoVerifyAndMerge_Ugly(t *testing.T) {
|
||||
// Workspace with status=completed, repo=test, PRURL="not-a-url"
|
||||
// extractPRNumber returns 0 for "not-a-url" → early return, no panic
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, writeStatus(dir, &WorkspaceStatus{
|
||||
Status: "completed",
|
||||
Repo: "test",
|
||||
Branch: "agent/fix",
|
||||
PRURL: "not-a-url",
|
||||
}))
|
||||
|
||||
s := &PrepSubsystem{
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
// PR number is 0 → should return early without panicking
|
||||
assert.NotPanics(t, func() {
|
||||
s.autoVerifyAndMerge(dir)
|
||||
})
|
||||
|
||||
// Status should remain unchanged (not "merged")
|
||||
st, err := ReadStatus(dir)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "completed", st.Status)
|
||||
}
|
||||
|
||||
// --- attemptVerifyAndMerge (Ugly — Go project that fails build) ---
|
||||
|
||||
func TestVerify_AttemptVerifyAndMerge_Ugly(t *testing.T) {
|
||||
// Go project that fails build (go.mod but no valid Go code)
|
||||
// with httptest Forge mock for comment API → returns testFailed
|
||||
commentCalled := false
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" && containsStr(r.URL.Path, "/comments") {
|
||||
commentCalled = true
|
||||
json.NewEncoder(w).Encode(map[string]any{"id": 1})
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
dir := t.TempDir()
|
||||
// Write a go.mod so runVerification detects Go and runs "go test ./..."
|
||||
require.True(t, fs.Write(filepath.Join(dir, "go.mod"), "module broken-test\n\ngo 1.22").OK)
|
||||
// Write invalid Go code to force test failure
|
||||
require.True(t, fs.Write(filepath.Join(dir, "broken.go"), "package broken\n\nfunc Bad() { undeclared() }").OK)
|
||||
|
||||
s := &PrepSubsystem{
|
||||
forge: forge.NewForge(srv.URL, "test-token"),
|
||||
forgeURL: srv.URL,
|
||||
forgeToken: "test-token",
|
||||
client: srv.Client(),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
result := s.attemptVerifyAndMerge(dir, "core", "test-repo", "agent/fix", 42)
|
||||
assert.Equal(t, testFailed, result)
|
||||
assert.True(t, commentCalled, "should have posted a comment about test failure")
|
||||
}
|
||||
|
||||
// --- extractPRNumber (extended Ugly) ---
|
||||
|
||||
func TestVerify_ExtractPRNumber_Ugly(t *testing.T) {
|
||||
// Just a bare number "5" → last segment is "5" → returns 5
|
||||
assert.Equal(t, 5, extractPRNumber("5"))
|
||||
|
||||
// Trailing slash → last segment is empty string → parseInt("") → 0
|
||||
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/42/"))
|
||||
|
||||
// Non-numeric string → parseInt("abc") → 0
|
||||
assert.Equal(t, 0, extractPRNumber("abc"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue