package configread import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) func currentExample(t *testing.T, name string) []byte { t.Helper() raw, err := os.ReadFile(filepath.Join("..", "..", "contracts", "local", "examples", name+".json")) if err != nil { t.Fatal(err) } return raw } func TestReadCurrentTaskSnapshot(t *testing.T) { for _, mode := range []string{"asr", "full"} { t.Run(mode, func(t *testing.T) { responses := map[string][]byte{ "/internal/v1/dispatcher/sip": currentExample(t, "config-read-sip"), "/internal/v1/dispatcher/ai-providers": currentExample(t, "config-read-providers"), "/internal/v1/dispatcher/task/task-" + mode: currentExample(t, "config-read-task-"+mode), "/internal/v1/dispatcher/tenant/1001/quota": currentExample(t, "config-read-quota"), } calls := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++ if r.Method != http.MethodGet || r.Header.Get("X-DISPATCHER-ID") != "c046b893-8628-4589-ae50-619d049248a6" || r.Header.Get("X-DISPATCHER-SECRET-KEY") != "test-secret" { t.Errorf("unexpected method/headers: %s", r.URL.Path) w.WriteHeader(http.StatusBadRequest) return } body, ok := responses[r.URL.Path] if !ok { t.Errorf("unexpected path %s", r.URL.Path) w.WriteHeader(http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _, _ = w.Write(body) })) defer server.Close() client, err := NewClient(server.URL, "c046b893-8628-4589-ae50-619d049248a6", "test-secret", server.Client()) if err != nil { t.Fatal(err) } snapshot, err := client.ReadCurrentTask(context.Background(), "task-"+mode, 1001) if err != nil { t.Fatal(err) } if calls != 4 || snapshot.SIP.Revision != 8 || snapshot.Task.TenantID != 1001 || snapshot.Quota.TenantID != 1001 { t.Fatalf("unexpected request count or identity: calls=%d snapshot=%+v", calls, snapshot) } if got := snapshot.Providers["asr-example"].Credential; got != "example-only-not-a-real-secret" { t.Fatalf("credential not passed unchanged: %q", got) } if mode == "asr" && (snapshot.Task.Agent.Mode != "asr_only" || strings.Contains(string(snapshot.Task.Agent.Raw), `"llm"`)) { t.Fatalf("ASR-only Agent picked up an LLM: %s", snapshot.Task.Agent.Raw) } if mode == "full" { var agent struct { LLM struct { Temperature *float64 `json:"temperature"` } `json:"llm"` Conversation struct { AllowInterrupt *bool `json:"allow_interrupt"` } `json:"conversation"` } if err := json.Unmarshal(snapshot.Task.Agent.Raw, &agent); err != nil || agent.LLM.Temperature == nil || *agent.LLM.Temperature != 0 || agent.Conversation.AllowInterrupt == nil || *agent.Conversation.AllowInterrupt { t.Fatalf("explicit zero/false lost: %v, %s", err, snapshot.Task.Agent.Raw) } } }) } } func TestReadCurrentSIPWithoutTasks(t *testing.T) { id := "c046b893-8628-4589-ae50-619d049248a6" requests := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requests++ if r.URL.Path != "/internal/v1/dispatcher/sip" { t.Errorf("startup SIP read requested unexpected path %q", r.URL.Path) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write(currentExample(t, "config-read-sip")) })) defer server.Close() client, err := NewClient(server.URL, id, "test-secret", server.Client()) if err != nil { t.Fatal(err) } sip, err := client.ReadCurrentSIP(context.Background()) if err != nil || sip.Revision != 8 || requests != 1 { t.Fatalf("startup SIP config: %+v, %v, requests=%d", sip, err, requests) } } func TestReadCurrentTaskRejectsMismatchedOwnerAndMalformedSIP(t *testing.T) { for _, mode := range []string{"owner", "sip-revision", "provider-ref", "provider-disabled", "provider-wrong-role"} { t.Run(mode, func(t *testing.T) { responses := map[string][]byte{ "/internal/v1/dispatcher/sip": currentExample(t, "config-read-sip"), "/internal/v1/dispatcher/ai-providers": currentExample(t, "config-read-providers"), "/internal/v1/dispatcher/task/task-asr": currentExample(t, "config-read-task-asr"), "/internal/v1/dispatcher/tenant/1001/quota": currentExample(t, "config-read-quota"), } switch mode { case "owner": responses["/internal/v1/dispatcher/task/task-asr"] = []byte(strings.Replace(string(responses["/internal/v1/dispatcher/task/task-asr"]), `"tenant_id":1001`, `"tenant_id":1002`, 1)) case "sip-revision": responses["/internal/v1/dispatcher/sip"] = currentExample(t, "invalid/config-read-sip-missing-revision") case "provider-ref": responses["/internal/v1/dispatcher/ai-providers"] = currentExample(t, "invalid/config-read-provider-ref") case "provider-disabled": responses["/internal/v1/dispatcher/ai-providers"] = []byte(strings.Replace(string(responses["/internal/v1/dispatcher/ai-providers"]), `"enabled":true`, `"enabled":false`, 1)) case "provider-wrong-role": responses["/internal/v1/dispatcher/ai-providers"] = []byte(strings.Replace(string(responses["/internal/v1/dispatcher/ai-providers"]), `"role":"asr"`, `"role":"tts"`, 1)) } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write(responses[r.URL.Path]) })) defer server.Close() client, err := NewClient(server.URL, "c046b893-8628-4589-ae50-619d049248a6", "test-secret", server.Client()) if err != nil { t.Fatal(err) } if _, err := client.ReadCurrentTask(context.Background(), "task-asr", 1001); err == nil || strings.Contains(err.Error(), "example-only-not-a-real-secret") { t.Fatalf("expected redacted fail-closed error, got %v", err) } }) } }