package main import ( "testing" "git.d-ma.be/mathias/tapir/internal/config" ) // TestBuildProcessorNilOnIncompleteConfig asserts the queue-only fallback: when a // required input is missing, buildProcessor returns (nil, nil) — never an error — // so the web UI degrades to queue-only instead of failing to start. func TestBuildProcessorNilOnIncompleteConfig(t *testing.T) { // A complete config (the fields buildProcessor gates on). The store is nil: // buildProcessor must not touch it on the incomplete paths, and the complete // path only stores the pointer (no connection), so nil is fine for this test. complete := config.Config{ GatewayURL: "http://gw/v1", YTClientID: "id", YTClientSecret: "secret", SecretsFile: "/tmp/secrets.json", } tests := []struct { name string mutate func(config.Config) config.Config wantNil bool }{ {"complete", func(c config.Config) config.Config { return c }, false}, {"no gateway url", func(c config.Config) config.Config { c.GatewayURL = ""; return c }, true}, {"no yt client id", func(c config.Config) config.Config { c.YTClientID = ""; return c }, true}, {"no yt client secret", func(c config.Config) config.Config { c.YTClientSecret = ""; return c }, true}, {"no secrets file", func(c config.Config) config.Config { c.SecretsFile = ""; return c }, true}, {"empty config", func(config.Config) config.Config { return config.Config{} }, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { engine, err := buildProcessor(tt.mutate(complete), nil) if err != nil { t.Fatalf("buildProcessor returned an error, want nil: %v", err) } if (engine == nil) != tt.wantNil { t.Fatalf("engine == nil is %v, want %v", engine == nil, tt.wantNil) } }) } } // TestBurstChainModelsLeadsWithOnboardModel: the onboarding burst chain (ADR-028) // leads with the stronger onboard model, then falls back through the standard // ADR-022 chain (primary -> local fallback -> cloud), deduped. func TestBurstChainModelsLeadsWithOnboardModel(t *testing.T) { got := burstChainModels(config.Config{ OnboardSummarizerModel: "iguana/gemma4-26b", SummarizerModel: "koala/phi4-mini", FallbackModel: "iguana/gemma4-26b", // also the onboard model -> dedup CloudFallbackModel: "berget/mistral-small", }) want := []string{"iguana/gemma4-26b", "koala/phi4-mini", "berget/mistral-small"} if len(got) != len(want) { t.Fatalf("burstChainModels = %v, want %v", got, want) } for i := range want { if got[i] != want[i] { t.Fatalf("burstChainModels = %v, want %v", got, want) } } } // TestBurstChainModelsCloudAbsentWhenDisabled: the NDA lever holds for the burst // too — empty cloud fallback keeps the burst chain fully local. func TestBurstChainModelsCloudAbsentWhenDisabled(t *testing.T) { got := burstChainModels(config.Config{ OnboardSummarizerModel: "iguana/gemma4-26b", SummarizerModel: "koala/phi4-mini", CloudFallbackModel: "", }) for _, m := range got { if m == "" || m == "berget/mistral-small" { t.Fatalf("cloud model leaked into burst chain: %v", got) } } } // TestBuildBurstProcessorNilWhenCollapsed: an empty or primary-equal onboard model // collapses the burst onto the shared processor (buildBurstProcessor returns nil). func TestBuildBurstProcessorNilWhenCollapsed(t *testing.T) { base := config.Config{ GatewayURL: "http://gw/v1", YTClientID: "id", YTClientSecret: "secret", SecretsFile: "/tmp/secrets.json", SummarizerModel: "koala/phi4-mini", } t.Run("empty onboard model", func(t *testing.T) { base.OnboardSummarizerModel = "" eng, err := buildBurstProcessor(base, nil) if err != nil || eng != nil { t.Fatalf("buildBurstProcessor = (%v, %v), want (nil, nil)", eng, err) } }) t.Run("onboard model equals primary", func(t *testing.T) { base.OnboardSummarizerModel = "koala/phi4-mini" eng, err := buildBurstProcessor(base, nil) if err != nil || eng != nil { t.Fatalf("buildBurstProcessor = (%v, %v), want (nil, nil)", eng, err) } }) }