Files
troubleshoot/pkg/analyze/agents_integration_test.go
a8fb5210f4 Advanced analysis clean (#1868)
* created roadmap and yaml claude agent

* Update roadmap.md

* feat: Clean advanced analysis implementation - core agents, engine, artifacts

* Remove unrelated files - keep only advanced analysis implementation

* fix: Fix goroutine leak in hosted agent rate limiter

- Added stop channel and stopped flag to RateLimiter struct
- Modified replenishTokens to listen for stop signal and exit cleanly
- Added Stop() method to gracefully shutdown rate limiter
- Added Stop() method to HostedAgent to cleanup rate limiter on shutdown

Fixes cursor bot issue: Rate Limiter Goroutine Leak

* fix: Fix analyzer config and model validation bugs

Bug 1: Analyzer Config Missing File Path
- Added filePath to DeploymentStatus analyzer config in convertAnalyzerToSpec
- Sets namespace-specific path (cluster-resources/deployments/{namespace}.json)
- Falls back to generic path (cluster-resources/deployments.json) if no namespace
- Fixes LocalAgent.analyzeDeploymentStatus backward compatibility

Bug 2: HealthCheck Fails Model Validation
- Changed Ollama model validation from prefix match to exact match
- Prevents false positives where llama2:13b would match request for llama2:7b
- Ensures agent only reports healthy when exact model is available

Both fixes address cursor bot reported issues and maintain backward compatibility.

* fixing lint errors

* fixing lint errors

* adding CLI flags

* fix: resolve linting errors for CI

- Remove unnecessary nil check in host_kernel_configs.go (len() for nil slices is zero)
- Remove unnecessary fmt.Sprintf() calls in ceph.go for static strings
- Apply go fmt formatting fixes

Fixes failing lint CI check

* fix: resolve CI failures in build-test workflow and Ollama tests

1. Fix GitHub Actions workflow logic error:
   - Replace problematic contains() expression with explicit job result checks
   - Properly handle failure and cancelled states for each job
   - Prevents false positive failures in success summary job

2. Fix Ollama agent parseLLMResponse panics:
   - Add proper error handling for malformed JSON in LLM responses
   - Return error when JSON is found but invalid (instead of silent fallback)
   - Add error when no meaningful content can be parsed from response
   - Prevents nil pointer dereference in test assertions

Fixes failing build-test/success and build-test/test CI checks

* fix: resolve all CI failures and cursor bot issues

1. Fix disable-ollama flag logic bug:
   - Remove disable-ollama from advanced analysis trigger condition
   - Prevents unintended advanced analysis mode when no agents registered
   - Allows proper fallback to legacy analysis

2. Fix diff test consistency:
   - Update test expectations to match function behavior (lines with newlines)
   - Ensures consistency between streaming and non-streaming diff paths

3. Fix Ollama agent error handling:
   - Add proper error return for malformed JSON in LLM responses
   - Add meaningful content validation for markdown parsing
   - Prevents nil pointer panics in test assertions

4. Fix analysis engine mock agent:
   - Mock agent now processes and returns results for all provided analyzers
   - Fixes test expectation mismatch (expected 8 results, got 1)

Resolves all failing CI checks: lint, test, and success workflow logic

---------

Co-authored-by: Noah Campbell <noah.edward.campbell@gmail.com>
2025-09-30 11:44:46 -05:00

253 lines
6.5 KiB
Go

package analyzer
import (
"context"
"testing"
"time"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestPhase2_MultiAgentIntegration tests Phase 2 multi-agent coordination
func TestPhase2_MultiAgentIntegration(t *testing.T) {
ctx := context.Background()
engine := NewAnalysisEngine()
// Register multiple agents to test Phase 2 coordination
localAgent := &integrationTestAgent{
name: "local",
available: true,
results: []*AnalyzerResult{
{
IsPass: true,
Title: "Local Pod Check",
Message: "Local analysis passed",
AgentName: "local",
Confidence: 0.9,
},
},
}
hostedAgent := &integrationTestAgent{
name: "hosted",
available: true,
results: []*AnalyzerResult{
{
IsWarn: true,
Title: "Hosted AI Analysis",
Message: "AI detected potential issue",
AgentName: "hosted",
Confidence: 0.8,
},
},
}
ollamaAgent := &integrationTestAgent{
name: "ollama",
available: true,
results: []*AnalyzerResult{
{
IsFail: true,
Title: "Ollama Deep Analysis",
Message: "LLM found critical issue",
AgentName: "ollama",
Confidence: 0.85,
Remediation: &RemediationStep{
Description: "LLM-suggested remediation",
Priority: 9,
Category: "ai-suggested",
IsAutomatable: false,
},
},
},
}
// Register all agents
require.NoError(t, engine.RegisterAgent("local", localAgent))
require.NoError(t, engine.RegisterAgent("hosted", hostedAgent))
require.NoError(t, engine.RegisterAgent("ollama", ollamaAgent))
// Test multi-agent analysis
bundle := &SupportBundle{
Files: map[string][]byte{
"test.json": []byte(`{"test": "data"}`),
},
Metadata: &SupportBundleMetadata{
CreatedAt: time.Now(),
Version: "1.0.0",
},
}
// Test with multiple agents
opts := AnalysisOptions{
Agents: []string{"local", "hosted", "ollama"},
IncludeRemediation: true,
}
result, err := engine.Analyze(ctx, bundle, opts)
require.NoError(t, err)
require.NotNil(t, result)
// Verify multi-agent coordination
assert.Len(t, result.Summary.AgentsUsed, 3)
assert.Contains(t, result.Summary.AgentsUsed, "local")
assert.Contains(t, result.Summary.AgentsUsed, "hosted")
assert.Contains(t, result.Summary.AgentsUsed, "ollama")
// Verify results from all agents
assert.Len(t, result.Results, 3)
agentResults := make(map[string]*AnalyzerResult)
for _, r := range result.Results {
agentResults[r.AgentName] = r
}
assert.Contains(t, agentResults, "local")
assert.Contains(t, agentResults, "hosted")
assert.Contains(t, agentResults, "ollama")
// Verify summary counts
assert.Equal(t, 1, result.Summary.PassCount)
assert.Equal(t, 1, result.Summary.WarnCount)
assert.Equal(t, 1, result.Summary.FailCount)
// Verify remediation from LLM agent
assert.NotEmpty(t, result.Remediation)
assert.Equal(t, "ai-suggested", result.Remediation[0].Category)
}
// TestPhase2_AgentFallback tests fallback mechanisms
func TestPhase2_AgentFallback(t *testing.T) {
ctx := context.Background()
engine := NewAnalysisEngine()
// Register agents with different availability
availableAgent := &integrationTestAgent{
name: "available",
available: true,
results: []*AnalyzerResult{
{IsPass: true, Title: "Available Agent Result", AgentName: "available"},
},
}
unavailableAgent := &integrationTestAgent{
name: "unavailable",
available: false,
}
require.NoError(t, engine.RegisterAgent("available", availableAgent))
require.NoError(t, engine.RegisterAgent("unavailable", unavailableAgent))
bundle := &SupportBundle{
Files: map[string][]byte{"test.json": []byte(`{}`)},
Metadata: &SupportBundleMetadata{CreatedAt: time.Now()},
}
// Test with mixed availability
opts := AnalysisOptions{
Agents: []string{"available", "unavailable"},
}
result, err := engine.Analyze(ctx, bundle, opts)
require.NoError(t, err)
require.NotNil(t, result)
// Should only use available agent
assert.Len(t, result.Summary.AgentsUsed, 1)
assert.Contains(t, result.Summary.AgentsUsed, "available")
assert.Len(t, result.Results, 1)
assert.Equal(t, "available", result.Results[0].AgentName)
}
// TestPhase2_AgentHealthCheck tests health checking for all agent types
func TestPhase2_AgentHealthCheck(t *testing.T) {
ctx := context.Background()
engine := NewAnalysisEngine()
// Register agents with different health states
healthyAgent := &integrationTestAgent{
name: "healthy",
available: true,
healthy: true,
}
unhealthyAgent := &integrationTestAgent{
name: "unhealthy",
available: true,
healthy: false,
error: "simulated agent error",
}
require.NoError(t, engine.RegisterAgent("healthy", healthyAgent))
require.NoError(t, engine.RegisterAgent("unhealthy", unhealthyAgent))
health, err := engine.HealthCheck(ctx)
require.NoError(t, err)
require.NotNil(t, health)
// Should be degraded due to unhealthy agent
assert.Equal(t, "degraded", health.Status)
assert.Len(t, health.Agents, 2)
// Find and verify agent health states
healthMap := make(map[string]AgentHealth)
for _, agentHealth := range health.Agents {
healthMap[agentHealth.Name] = agentHealth
}
assert.Equal(t, "healthy", healthMap["healthy"].Status)
assert.True(t, healthMap["healthy"].Available)
assert.Equal(t, "unhealthy", healthMap["unhealthy"].Status)
assert.Equal(t, "simulated agent error", healthMap["unhealthy"].Error)
assert.True(t, healthMap["unhealthy"].Available)
}
// integrationTestAgent is used for testing (avoiding import cycles)
type integrationTestAgent struct {
name string
available bool
healthy bool
error string
results []*AnalyzerResult
}
func (a *integrationTestAgent) Name() string {
return a.name
}
func (a *integrationTestAgent) IsAvailable() bool {
return a.available
}
func (a *integrationTestAgent) Capabilities() []string {
return []string{"test-capability"}
}
func (a *integrationTestAgent) HealthCheck(ctx context.Context) error {
if !a.healthy {
if a.error != "" {
return errors.New(a.error)
}
return errors.New("agent unhealthy")
}
return nil
}
func (a *integrationTestAgent) Analyze(ctx context.Context, data []byte, analyzers []AnalyzerSpec) (*AgentResult, error) {
if !a.available {
return nil, errors.New("agent not available")
}
return &AgentResult{
Results: a.results,
Metadata: AgentResultMetadata{
Duration: time.Millisecond * 50,
AnalyzerCount: len(analyzers),
Version: "1.0.0",
},
}, nil
}