package ai import ( "context" "errors" "testing" "git.georgsen.dk/hwlab/internal/inventory" ) func testReq() IntakeRequest { return IntakeRequest{PhotosBase64: []string{"data:image/jpeg;base64,/9j/"}, JobID: "test-job"} } // TestOrchestratorHighConfidence: tier1 returns confidence 0.95; tier2 never called; status == StatusIndexed func TestOrchestratorHighConfidence(t *testing.T) { tier1 := &MockAIClient{FixedResult: HighConfidenceResult()} tier2 := &MockAIClient{} o := NewOrchestrator(tier1, tier2, 0.75) result, status, err := o.Analyze(context.Background(), testReq()) if err != nil { t.Fatalf("expected no error, got: %v", err) } if result == nil { t.Fatal("expected non-nil result") } if status != inventory.StatusIndexed { t.Errorf("expected StatusIndexed, got %q", status) } if len(tier2.Calls) != 0 { t.Errorf("expected tier2 never called, got %d calls", len(tier2.Calls)) } if len(tier1.Calls) != 1 { t.Errorf("expected tier1 called once, got %d calls", len(tier1.Calls)) } } // TestOrchestratorLowConfidenceEscalates: tier1 returns confidence 0.40; tier2 called once; tier2 returns 0.85; status == StatusIndexed func TestOrchestratorLowConfidenceEscalates(t *testing.T) { tier1 := &MockAIClient{FixedResult: LowConfidenceResult()} tier2 := &MockAIClient{FixedResult: HighConfidenceResult()} o := NewOrchestrator(tier1, tier2, 0.75) result, status, err := o.Analyze(context.Background(), testReq()) if err != nil { t.Fatalf("expected no error, got: %v", err) } if result == nil { t.Fatal("expected non-nil result") } if status != inventory.StatusIndexed { t.Errorf("expected StatusIndexed after tier2 escalation, got %q", status) } if len(tier2.Calls) != 1 { t.Errorf("expected tier2 called once, got %d calls", len(tier2.Calls)) } if result.Confidence < 0.75 { t.Errorf("expected tier2 result confidence >= 0.75, got %.2f", result.Confidence) } } // TestOrchestratorBothTiersFail: both tiers error; result is non-nil, status == StatusNeedsResearch, err == nil func TestOrchestratorBothTiersFail(t *testing.T) { tier1 := &MockAIClient{FixedError: errors.New("tier1 connection refused")} tier2 := &MockAIClient{FixedError: errors.New("tier2 connection refused")} o := NewOrchestrator(tier1, tier2, 0.75) result, status, err := o.Analyze(context.Background(), testReq()) if err != nil { t.Fatalf("orchestrator must not propagate tier errors, got: %v", err) } if result == nil { t.Fatal("orchestrator must return non-nil result even on total failure") } if status != inventory.StatusNeedsResearch { t.Errorf("expected StatusNeedsResearch on total failure, got %q", status) } } // TestOrchestratorTier1NilResult: tier1 returns nil result with nil error; orchestrator escalates to tier2 func TestOrchestratorTier1NilResult(t *testing.T) { tier1 := &MockAIClient{FixedResult: nil, FixedError: nil} tier2 := &MockAIClient{FixedResult: HighConfidenceResult()} o := NewOrchestrator(tier1, tier2, 0.75) result, status, err := o.Analyze(context.Background(), testReq()) if err != nil { t.Fatalf("expected no error, got: %v", err) } if result == nil { t.Fatal("expected non-nil result from tier2 escalation") } if len(tier2.Calls) != 1 { t.Errorf("expected tier2 called once after tier1 nil result, got %d calls", len(tier2.Calls)) } if status != inventory.StatusIndexed { t.Errorf("expected StatusIndexed from tier2, got %q", status) } } // TestOrchestratorNeedsResearch: tier1 returns 0.40; tier2 also returns 0.40; final status == StatusNeedsResearch func TestOrchestratorNeedsResearch(t *testing.T) { tier1 := &MockAIClient{FixedResult: LowConfidenceResult()} tier2 := &MockAIClient{FixedResult: LowConfidenceResult()} o := NewOrchestrator(tier1, tier2, 0.75) result, status, err := o.Analyze(context.Background(), testReq()) if err != nil { t.Fatalf("expected no error, got: %v", err) } if result == nil { t.Fatal("expected non-nil result") } if status != inventory.StatusNeedsResearch { t.Errorf("expected StatusNeedsResearch when both tiers return low confidence, got %q", status) } if len(tier2.Calls) != 1 { t.Errorf("expected tier2 called once, got %d calls", len(tier2.Calls)) } }