Coverage: agentic 40.1% → 54.3%, setup 71.5% → 75.8% Total: 695 passing tests across all packages (was ~357) New test files (15): - commands_forge_test.go — parseForgeArgs, fmtIndex - commands_workspace_test.go — extractField (9 cases) - commands_test.go — command registration + Core integration - handlers_test.go — RegisterHandlers, IPC pipeline, lifecycle - plan_crud_test.go — full CRUD via MCP handlers (23 tests) - prep_extra_test.go — buildPrompt, findConsumersList, pullWikiContent, getIssueBody - queue_extra_test.go — ConcurrencyLimit YAML, delayForAgent, drainOne - remote_client_test.go — mcpInitialize, mcpCall, readSSEData, setHeaders - remote_test.go — resolveHost, remoteToken - resume_test.go — resume dry run, agent override, validation - review_queue_test.go — countFindings, parseRetryAfter, buildAutoPRBody - review_queue_extra_test.go — buildReviewCommand, rateLimitState, reviewQueue - verify_extra_test.go — attemptVerifyAndMerge, autoVerifyAndMerge pipeline - watch_test.go — findActiveWorkspaces, resolveWorkspaceDir - setup/setup_extra_test.go — defaultBuildCommand, defaultTestCommand all branches Co-Authored-By: Virgil <virgil@lethean.io>
187 lines
4.5 KiB
Go
187 lines
4.5 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package agentic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// --- UnmarshalYAML for ConcurrencyLimit ---
|
|
|
|
func TestConcurrencyLimit_Good_IntForm(t *testing.T) {
|
|
var cfg struct {
|
|
Limit ConcurrencyLimit `yaml:"limit"`
|
|
}
|
|
err := yaml.Unmarshal([]byte("limit: 3"), &cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, cfg.Limit.Total)
|
|
assert.Nil(t, cfg.Limit.Models)
|
|
}
|
|
|
|
func TestConcurrencyLimit_Good_MapForm(t *testing.T) {
|
|
data := `limit:
|
|
total: 2
|
|
gpt-5.4: 1
|
|
gpt-5.3-codex-spark: 1`
|
|
|
|
var cfg struct {
|
|
Limit ConcurrencyLimit `yaml:"limit"`
|
|
}
|
|
err := yaml.Unmarshal([]byte(data), &cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, cfg.Limit.Total)
|
|
assert.Equal(t, 1, cfg.Limit.Models["gpt-5.4"])
|
|
assert.Equal(t, 1, cfg.Limit.Models["gpt-5.3-codex-spark"])
|
|
}
|
|
|
|
func TestConcurrencyLimit_Good_MapNoTotal(t *testing.T) {
|
|
data := `limit:
|
|
flash: 2
|
|
pro: 1`
|
|
|
|
var cfg struct {
|
|
Limit ConcurrencyLimit `yaml:"limit"`
|
|
}
|
|
err := yaml.Unmarshal([]byte(data), &cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, cfg.Limit.Total)
|
|
assert.Equal(t, 2, cfg.Limit.Models["flash"])
|
|
}
|
|
|
|
func TestConcurrencyLimit_Good_FullConfig(t *testing.T) {
|
|
data := `version: 1
|
|
concurrency:
|
|
claude: 1
|
|
codex:
|
|
total: 2
|
|
gpt-5.4: 1
|
|
gpt-5.3-codex-spark: 1
|
|
gemini: 3`
|
|
|
|
var cfg AgentsConfig
|
|
err := yaml.Unmarshal([]byte(data), &cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, cfg.Concurrency["claude"].Total)
|
|
assert.Equal(t, 2, cfg.Concurrency["codex"].Total)
|
|
assert.Equal(t, 1, cfg.Concurrency["codex"].Models["gpt-5.4"])
|
|
assert.Equal(t, 3, cfg.Concurrency["gemini"].Total)
|
|
}
|
|
|
|
// --- delayForAgent (extended — sustained mode) ---
|
|
|
|
func TestDelayForAgent_Good_SustainedMode(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", root)
|
|
|
|
cfg := `version: 1
|
|
concurrency:
|
|
codex: 2
|
|
rates:
|
|
codex:
|
|
reset_utc: "06:00"
|
|
sustained_delay: 120
|
|
burst_window: 2
|
|
burst_delay: 15`
|
|
os.WriteFile(filepath.Join(root, "agents.yaml"), []byte(cfg), 0o644)
|
|
|
|
s := &PrepSubsystem{
|
|
codePath: t.TempDir(),
|
|
backoff: make(map[string]time.Time),
|
|
failCount: make(map[string]int),
|
|
}
|
|
|
|
d := s.delayForAgent("codex:gpt-5.4")
|
|
assert.True(t, d == 120*time.Second || d == 15*time.Second,
|
|
"expected 120s or 15s, got %v", d)
|
|
}
|
|
|
|
// --- countRunningByModel ---
|
|
|
|
func TestCountRunningByModel_Good_NoWorkspaces(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", root)
|
|
os.MkdirAll(filepath.Join(root, "workspace"), 0o755)
|
|
|
|
s := &PrepSubsystem{
|
|
backoff: make(map[string]time.Time),
|
|
failCount: make(map[string]int),
|
|
}
|
|
assert.Equal(t, 0, s.countRunningByModel("codex:gpt-5.4"))
|
|
}
|
|
|
|
// --- drainQueue / drainOne ---
|
|
|
|
func TestDrainQueue_Good_NoCoreFallsBackToMutex(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", root)
|
|
os.MkdirAll(filepath.Join(root, "workspace"), 0o755)
|
|
|
|
s := &PrepSubsystem{
|
|
frozen: false,
|
|
core: nil,
|
|
backoff: make(map[string]time.Time),
|
|
failCount: make(map[string]int),
|
|
}
|
|
assert.NotPanics(t, func() { s.drainQueue() })
|
|
}
|
|
|
|
func TestDrainOne_Good_NoWorkspaces(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", root)
|
|
os.MkdirAll(filepath.Join(root, "workspace"), 0o755)
|
|
|
|
s := &PrepSubsystem{
|
|
codePath: t.TempDir(),
|
|
backoff: make(map[string]time.Time),
|
|
failCount: make(map[string]int),
|
|
}
|
|
assert.False(t, s.drainOne())
|
|
}
|
|
|
|
func TestDrainOne_Good_SkipsNonQueued(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", root)
|
|
wsRoot := filepath.Join(root, "workspace")
|
|
|
|
ws := filepath.Join(wsRoot, "ws-done")
|
|
os.MkdirAll(ws, 0o755)
|
|
st := &WorkspaceStatus{Status: "completed", Agent: "codex", Repo: "test"}
|
|
data, _ := json.Marshal(st)
|
|
os.WriteFile(filepath.Join(ws, "status.json"), data, 0o644)
|
|
|
|
s := &PrepSubsystem{
|
|
codePath: t.TempDir(),
|
|
backoff: make(map[string]time.Time),
|
|
failCount: make(map[string]int),
|
|
}
|
|
assert.False(t, s.drainOne())
|
|
}
|
|
|
|
func TestDrainOne_Good_SkipsBackedOffPool(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", root)
|
|
wsRoot := filepath.Join(root, "workspace")
|
|
|
|
ws := filepath.Join(wsRoot, "ws-queued")
|
|
os.MkdirAll(ws, 0o755)
|
|
st := &WorkspaceStatus{Status: "queued", Agent: "codex", Repo: "test", Task: "do it"}
|
|
data, _ := json.Marshal(st)
|
|
os.WriteFile(filepath.Join(ws, "status.json"), data, 0o644)
|
|
|
|
s := &PrepSubsystem{
|
|
codePath: t.TempDir(),
|
|
backoff: map[string]time.Time{
|
|
"codex": time.Now().Add(1 * time.Hour),
|
|
},
|
|
failCount: make(map[string]int),
|
|
}
|
|
assert.False(t, s.drainOne())
|
|
}
|