go-agent/clotho_test.go
Snider 61e01bfdf1 feat: initial go-agent — agentci + jobrunner + plugins marketplace
Consolidates three codebases into a single agent orchestration repo:

- agentci (from go-scm): Clotho dual-run verification, agent config,
  SSH security (sanitisation, secure commands, token masking)
- jobrunner (from go-scm): Poll-dispatch-report pipeline with 7 handlers
  (dispatch, completion, auto-merge, publish draft, dismiss reviews,
  send fix command, tick parent epic)
- plugins marketplace (from agentic/plugins): 27 Claude/Codex/Gemini
  plugins with shared MCP server

All 150+ tests passing across 6 packages.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 15:47:19 +00:00

194 lines
4.9 KiB
Go

package agent
import (
"context"
"testing"
"forge.lthn.ai/core/go-agent/jobrunner"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newTestSpinner() *Spinner {
return NewSpinner(
ClothoConfig{
Strategy: "clotho-verified",
ValidationThreshold: 0.85,
},
map[string]AgentConfig{
"claude-agent": {
Host: "claude@10.0.0.1",
Model: "opus",
Runner: "claude",
Active: true,
DualRun: false,
ForgejoUser: "claude-forge",
},
"gemini-agent": {
Host: "localhost",
Model: "gemini-2.0-flash",
VerifyModel: "gemini-1.5-pro",
Runner: "gemini",
Active: true,
DualRun: true,
ForgejoUser: "gemini-forge",
},
},
)
}
func TestNewSpinner_Good(t *testing.T) {
spinner := newTestSpinner()
assert.NotNil(t, spinner)
assert.Equal(t, "clotho-verified", spinner.Config.Strategy)
assert.Len(t, spinner.Agents, 2)
}
func TestDeterminePlan_Good_Standard(t *testing.T) {
spinner := newTestSpinner()
signal := &jobrunner.PipelineSignal{
RepoOwner: "host-uk",
RepoName: "core-php",
}
mode := spinner.DeterminePlan(signal, "claude-agent")
assert.Equal(t, ModeStandard, mode)
}
func TestDeterminePlan_Good_DualRunByAgent(t *testing.T) {
spinner := newTestSpinner()
signal := &jobrunner.PipelineSignal{
RepoOwner: "host-uk",
RepoName: "some-repo",
}
mode := spinner.DeterminePlan(signal, "gemini-agent")
assert.Equal(t, ModeDual, mode)
}
func TestDeterminePlan_Good_DualRunByCriticalRepo(t *testing.T) {
spinner := newTestSpinner()
tests := []struct {
name string
repoName string
expected RunMode
}{
{name: "core repo", repoName: "core", expected: ModeDual},
{name: "security repo", repoName: "auth-security", expected: ModeDual},
{name: "security-audit", repoName: "security-audit", expected: ModeDual},
{name: "regular repo", repoName: "docs", expected: ModeStandard},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
signal := &jobrunner.PipelineSignal{
RepoOwner: "host-uk",
RepoName: tt.repoName,
}
mode := spinner.DeterminePlan(signal, "claude-agent")
assert.Equal(t, tt.expected, mode)
})
}
}
func TestDeterminePlan_Good_NonVerifiedStrategy(t *testing.T) {
spinner := NewSpinner(
ClothoConfig{Strategy: "direct"},
map[string]AgentConfig{
"agent": {Host: "localhost", DualRun: true, Active: true},
},
)
signal := &jobrunner.PipelineSignal{RepoName: "core"}
mode := spinner.DeterminePlan(signal, "agent")
assert.Equal(t, ModeStandard, mode, "non-verified strategy should always return standard")
}
func TestDeterminePlan_Good_UnknownAgent(t *testing.T) {
spinner := newTestSpinner()
signal := &jobrunner.PipelineSignal{RepoName: "some-repo"}
mode := spinner.DeterminePlan(signal, "nonexistent-agent")
assert.Equal(t, ModeStandard, mode, "unknown agent should return standard")
}
func TestGetVerifierModel_Good(t *testing.T) {
spinner := newTestSpinner()
model := spinner.GetVerifierModel("gemini-agent")
assert.Equal(t, "gemini-1.5-pro", model)
}
func TestGetVerifierModel_Good_Default(t *testing.T) {
spinner := newTestSpinner()
// claude-agent has no VerifyModel set.
model := spinner.GetVerifierModel("claude-agent")
assert.Equal(t, "gemini-1.5-pro", model, "should fall back to default")
}
func TestGetVerifierModel_Good_UnknownAgent(t *testing.T) {
spinner := newTestSpinner()
model := spinner.GetVerifierModel("unknown")
assert.Equal(t, "gemini-1.5-pro", model, "should fall back to default")
}
func TestFindByForgejoUser_Good_DirectMatch(t *testing.T) {
spinner := newTestSpinner()
// Direct match on config key.
name, agent, found := spinner.FindByForgejoUser("claude-agent")
assert.True(t, found)
assert.Equal(t, "claude-agent", name)
assert.Equal(t, "opus", agent.Model)
}
func TestFindByForgejoUser_Good_ByField(t *testing.T) {
spinner := newTestSpinner()
// Match by ForgejoUser field.
name, agent, found := spinner.FindByForgejoUser("claude-forge")
assert.True(t, found)
assert.Equal(t, "claude-agent", name)
assert.Equal(t, "opus", agent.Model)
}
func TestFindByForgejoUser_Bad_NotFound(t *testing.T) {
spinner := newTestSpinner()
_, _, found := spinner.FindByForgejoUser("nonexistent")
assert.False(t, found)
}
func TestFindByForgejoUser_Bad_Empty(t *testing.T) {
spinner := newTestSpinner()
_, _, found := spinner.FindByForgejoUser("")
assert.False(t, found)
}
func TestWeave_Good_Matching(t *testing.T) {
spinner := newTestSpinner()
converge, err := spinner.Weave(context.Background(), []byte("output"), []byte("output"))
require.NoError(t, err)
assert.True(t, converge)
}
func TestWeave_Good_Diverging(t *testing.T) {
spinner := newTestSpinner()
converge, err := spinner.Weave(context.Background(), []byte("primary"), []byte("different"))
require.NoError(t, err)
assert.False(t, converge)
}
func TestRunModeConstants(t *testing.T) {
assert.Equal(t, RunMode("standard"), ModeStandard)
assert.Equal(t, RunMode("dual"), ModeDual)
}