package httpapi import "context" import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "supply-intelligence/internal/admission" "supply-intelligence/internal/discovery" "supply-intelligence/internal/domain" "supply-intelligence/internal/gatewayconsumer" "supply-intelligence/internal/poller" "supply-intelligence/internal/probe" "supply-intelligence/internal/publish" "supply-intelligence/internal/repository" ) func TestServerRoutingStateEndpoint(t *testing.T) { repo := repository.NewMemoryRepository() repo.UpsertRoutingState(context.Background(), domain.AccountRoutingState{ AccountID: 101, Platform: "openai", AccountStatus: domain.AccountStatusActive, RoutingEnabled: true, RiskScore: 10, ReasonCode: "ok", LastProbeAt: time.Unix(100, 0).UTC(), Version: 3, }) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) req := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/accounts/101/routing-state", nil) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("unexpected status: %d body=%s", rr.Code, rr.Body.String()) } var got domain.AccountRoutingState if err := json.NewDecoder(rr.Body).Decode(&got); err != nil { t.Fatalf("decode error: %v", err) } if got.AccountID != 101 || got.AccountStatus != domain.AccountStatusActive { t.Fatalf("unexpected payload: %+v", got) } } func TestServerProbeEvaluateEndpointPaths(t *testing.T) { tests := []struct { name string body string wantStatus int wantClassification domain.ProbeClassification wantAccountStatus domain.AccountStatus wantReasonCode string wantRoutingEnabled bool }{ { name: "success", body: `{"account_id":201,"platform":"openai","current_status":"suspended","status_code":200}`, wantStatus: http.StatusOK, wantClassification: domain.ProbeClassificationSuccess, wantAccountStatus: domain.AccountStatusActive, wantReasonCode: "ok", wantRoutingEnabled: true, }, { name: "explicit_failure", body: `{"account_id":202,"platform":"openai","current_status":"active","status_code":401}`, wantStatus: http.StatusOK, wantClassification: domain.ProbeClassificationExplicitFailure, wantAccountStatus: domain.AccountStatusSuspended, wantReasonCode: "auth_rejected", wantRoutingEnabled: false, }, { name: "inconclusive", body: `{"account_id":203,"platform":"openai","current_status":"suspended","transport_error":"dial tcp timeout"}`, wantStatus: http.StatusOK, wantClassification: domain.ProbeClassificationInconclusive, wantAccountStatus: domain.AccountStatusSuspended, wantReasonCode: "transport_error", wantRoutingEnabled: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { repo := repository.NewMemoryRepository() server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/probe/evaluate", bytes.NewBufferString(tt.body)) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != tt.wantStatus { t.Fatalf("unexpected status: %d body=%s", rr.Code, rr.Body.String()) } var got probe.EvaluateOutput if err := json.NewDecoder(rr.Body).Decode(&got); err != nil { t.Fatalf("decode error: %v", err) } if got.Classification != tt.wantClassification { t.Fatalf("unexpected classification: %q", got.Classification) } if got.RoutingState.AccountStatus != tt.wantAccountStatus { t.Fatalf("unexpected account status: %q", got.RoutingState.AccountStatus) } if got.RoutingState.ReasonCode != tt.wantReasonCode { t.Fatalf("unexpected reason code: %q", got.RoutingState.ReasonCode) } if got.RoutingState.RoutingEnabled != tt.wantRoutingEnabled { t.Fatalf("unexpected routing enabled: %v", got.RoutingState.RoutingEnabled) } }) } } func TestServerPublishPackageEventEndpoint(t *testing.T) { repo := repository.NewMemoryRepository() repo.UpsertDiscoveryCandidateContext(context.Background(), domain.DiscoveryCandidate{ CandidateID: "cand-http-publish", AccountID: 501, Platform: "openai", Model: "gpt-4.1-mini", Source: "admission", Status: domain.DiscoveryCandidateStatusTestPassed, DiscoveredAt: time.Unix(100, 0).UTC(), UpdatedAt: time.Unix(110, 0).UTC(), Version: 2, }) repo.UpsertSupplyPackage(context.Background(), domain.SupplyPackage{PackageID: 1001, Platform: "openai", Model: "gpt-4.1-mini", Status: "draft", Source: "admission", UpdatedAt: time.Unix(110, 0).UTC(), Version: 1}) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) body := bytes.NewBufferString(`{"event_id":"evt-1","platform":"openai","model":"gpt-4.1-mini","occurred_at":"2026-05-06T20:30:00Z"}`) req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/publish/package-event", body) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("unexpected publish status: %d body=%s", rr.Code, rr.Body.String()) } var out struct { Candidate domain.DiscoveryCandidate `json:"candidate"` Package domain.SupplyPackage `json:"package"` Event domain.PackageChangeEvent `json:"event"` GatewaySyncStatus domain.GatewaySyncStatus `json:"gateway_sync_status"` } if err := json.NewDecoder(rr.Body).Decode(&out); err != nil { t.Fatalf("decode error: %v", err) } if out.Candidate.Status != domain.DiscoveryCandidateStatusPublished { t.Fatalf("unexpected candidate: %+v", out.Candidate) } if out.Package.Status != "active" { t.Fatalf("unexpected package: %+v", out.Package) } if out.Event.EventID != "evt-1" || out.Event.EventType != publish.PackagePublishedEventType { t.Fatalf("unexpected event: %+v", out.Event) } if out.GatewaySyncStatus != domain.GatewaySyncStatusPending { t.Fatalf("unexpected sync status: %q", out.GatewaySyncStatus) } } func TestServerPackageChangeListAndAck(t *testing.T) { repo := repository.NewMemoryRepository() repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-1", EventType: publish.PackagePublishedEventType, PackageID: 1001, Platform: "openai", Model: "gpt-4.1-mini", OccurredAt: time.Unix(5, 0).UTC(), Version: 7, GatewaySyncStatus: domain.GatewaySyncStatusPending}) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) listReq := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/gateway/package-changes", nil) listRR := httptest.NewRecorder() server.Routes().ServeHTTP(listRR, listReq) if listRR.Code != http.StatusOK { t.Fatalf("unexpected list status: %d body=%s", listRR.Code, listRR.Body.String()) } var listResp struct { Items []domain.PackageChangeEvent `json:"items"` NextCursor string `json:"next_cursor"` } if err := json.NewDecoder(listRR.Body).Decode(&listResp); err != nil { t.Fatalf("decode list error: %v", err) } if len(listResp.Items) != 1 || listResp.NextCursor != "" { t.Fatalf("unexpected list response: %+v", listResp) } ackReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/package-changes/evt-1/ack", bytes.NewBufferString(`{"consumer":"gateway","result":"applied","detail":"ok"}`)) ackRR := httptest.NewRecorder() server.Routes().ServeHTTP(ackRR, ackReq) if ackRR.Code != http.StatusNoContent { t.Fatalf("unexpected ack status: %d body=%s", ackRR.Code, ackRR.Body.String()) } updated, _ := repo.ListPackageEventsAfter(context.Background(), "") if len(updated) != 1 || updated[0].GatewaySyncStatus != domain.GatewaySyncStatusApplied { t.Fatalf("unexpected ack state: %+v", updated) } } func TestServerPackageChangeAckMissingEventReturnsNotFound(t *testing.T) { repo := repository.NewMemoryRepository() server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) ackReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/package-changes/evt-missing/ack", bytes.NewBufferString(`{"consumer":"gateway","result":"applied","detail":"ok"}`)) ackRR := httptest.NewRecorder() server.Routes().ServeHTTP(ackRR, ackReq) if ackRR.Code != http.StatusNotFound { t.Fatalf("unexpected ack status: %d body=%s", ackRR.Code, ackRR.Body.String()) } var payload map[string]string if err := json.NewDecoder(ackRR.Body).Decode(&payload); err != nil { t.Fatalf("decode ack missing error: %v", err) } if payload["error"] != "not_found" { t.Fatalf("unexpected ack missing payload: %+v", payload) } } func TestServerPackageChangeAckRejectsInvalidResult(t *testing.T) { repo := repository.NewMemoryRepository() repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-ack-invalid", EventType: publish.PackagePublishedEventType, PackageID: 1003, Platform: "openai", Model: "gpt-4.1-mini", OccurredAt: time.Unix(7, 0).UTC(), Version: 9, GatewaySyncStatus: domain.GatewaySyncStatusPending}) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) ackReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/package-changes/evt-ack-invalid/ack", bytes.NewBufferString(`{"consumer":"gateway","result":"unknown","detail":"bad"}`)) ackRR := httptest.NewRecorder() server.Routes().ServeHTTP(ackRR, ackReq) if ackRR.Code != http.StatusBadRequest { t.Fatalf("unexpected invalid-result ack status: %d body=%s", ackRR.Code, ackRR.Body.String()) } var payload map[string]string if err := json.NewDecoder(ackRR.Body).Decode(&payload); err != nil { t.Fatalf("decode invalid-result ack error: %v", err) } if payload["error"] != "invalid_result" { t.Fatalf("unexpected invalid-result ack payload: %+v", payload) } } func TestServerPackageChangeListWithCursor(t *testing.T) { repo := repository.NewMemoryRepository() repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-1", EventType: publish.PackagePublishedEventType, PackageID: 1001, Platform: "openai", Model: "gpt-4.1-mini", OccurredAt: time.Unix(5, 0).UTC(), Version: 7, GatewaySyncStatus: domain.GatewaySyncStatusPending}) repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-2", EventType: publish.PackagePublishedEventType, PackageID: 1002, Platform: "openai", Model: "gpt-4.1", OccurredAt: time.Unix(6, 0).UTC(), Version: 8, GatewaySyncStatus: domain.GatewaySyncStatusPending}) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) req := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/gateway/package-changes?cursor=evt-1", nil) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("unexpected status: %d body=%s", rr.Code, rr.Body.String()) } var resp struct { Items []domain.PackageChangeEvent `json:"items"` NextCursor string `json:"next_cursor"` } if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil { t.Fatalf("decode error: %v", err) } if len(resp.Items) != 1 || resp.Items[0].EventID != "evt-2" || resp.NextCursor != "" { t.Fatalf("unexpected cursor response: %+v", resp) } } func TestServerConsumeOnceEndpoint(t *testing.T) { repo := repository.NewMemoryRepository() repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-apply", EventType: publish.PackagePublishedEventType, PackageID: 1001, Platform: "openai", Model: "gpt-4.1-mini", OccurredAt: time.Unix(5, 0).UTC(), Version: 7, GatewaySyncStatus: domain.GatewaySyncStatusPending}) repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-fail", EventType: publish.PackagePublishedEventType, PackageID: 1002, Platform: "openai", Model: "gpt-fail-model", OccurredAt: time.Unix(6, 0).UTC(), Version: 8, GatewaySyncStatus: domain.GatewaySyncStatusPending}) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/consume-once", bytes.NewBufferString(`{"consumer":"gateway"}`)) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("unexpected consume status: %d body=%s", rr.Code, rr.Body.String()) } var out gatewayconsumer.ConsumeOnceOutput if err := json.NewDecoder(rr.Body).Decode(&out); err != nil { t.Fatalf("decode error: %v", err) } if len(out.Items) != 2 { t.Fatalf("unexpected consume output length: %+v", out) } if out.Items[0].Result != domain.GatewayAckResultApplied || out.Items[0].GatewaySyncStatus != domain.GatewaySyncStatusApplied || out.Items[0].Detail == "" { t.Fatalf("unexpected first consume item: %+v", out.Items[0]) } if out.Items[1].Result != domain.GatewayAckResultFailed || out.Items[1].GatewaySyncStatus != domain.GatewaySyncStatusFailed || out.Items[1].Detail == "" { t.Fatalf("unexpected second consume item: %+v", out.Items[1]) } } func TestServerConsumeOnceSkipsUnauthorizedAndLeavesPending(t *testing.T) { repo := repository.NewMemoryRepository() repo.UpsertSupplyAccount(context.Background(), domain.SupplyAccount{AccountID: 2001, Platform: "openai", APIKey: "key-other", ConsumerTag: "other-consumer", Status: "active", CreatedAt: time.Unix(1, 0).UTC(), UpdatedAt: time.Unix(1, 0).UTC()}) repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-unauthorized", EventType: publish.PackagePublishedEventType, PackageID: 2001, AccountID: 2001, Platform: "openai", Model: "gpt-4.1-unauthorized", OccurredAt: time.Unix(8, 0).UTC(), Version: 10, GatewaySyncStatus: domain.GatewaySyncStatusPending}) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/consume-once", bytes.NewBufferString(`{"consumer":"gateway"}`)) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("unexpected consume status: %d body=%s", rr.Code, rr.Body.String()) } var out gatewayconsumer.ConsumeOnceOutput if err := json.NewDecoder(rr.Body).Decode(&out); err != nil { t.Fatalf("decode error: %v", err) } if len(out.Items) != 0 { t.Fatalf("expected unauthorized event to be skipped, got %+v", out.Items) } items, _ := repo.ListPackageEventsAfter(context.Background(), "") if len(items) != 1 || items[0].GatewaySyncStatus != domain.GatewaySyncStatusPending { t.Fatalf("expected unauthorized event to remain pending, got %+v", items) } } func TestServerConsumeOnceSkipsNonPendingEvents(t *testing.T) { repo := repository.NewMemoryRepository() repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-applied-existing", EventType: publish.PackagePublishedEventType, PackageID: 2002, Platform: "openai", Model: "gpt-4.1-applied", OccurredAt: time.Unix(9, 0).UTC(), Version: 11, GatewaySyncStatus: domain.GatewaySyncStatusApplied}) repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-failed-existing", EventType: publish.PackagePublishedEventType, PackageID: 2003, Platform: "openai", Model: "gpt-4.1-failed-existing", OccurredAt: time.Unix(10, 0).UTC(), Version: 12, GatewaySyncStatus: domain.GatewaySyncStatusFailed}) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/consume-once", bytes.NewBufferString(`{"consumer":"gateway"}`)) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("unexpected consume status: %d body=%s", rr.Code, rr.Body.String()) } var out gatewayconsumer.ConsumeOnceOutput if err := json.NewDecoder(rr.Body).Decode(&out); err != nil { t.Fatalf("decode error: %v", err) } if len(out.Items) != 0 { t.Fatalf("expected no items for non-pending events, got %+v", out.Items) } } func TestServerConsumeOnceFailedDoesNotDriftSnapshot(t *testing.T) { repo := repository.NewMemoryRepository() repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-apply-first", EventType: publish.PackagePublishedEventType, PackageID: 2004, Platform: "openai", Model: "gpt-4.1-first", OccurredAt: time.Unix(11, 0).UTC(), Version: 13, GatewaySyncStatus: domain.GatewaySyncStatusPending}) repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-fail-second", EventType: publish.PackagePublishedEventType, PackageID: 2005, Platform: "openai", Model: "gpt-fail-second", OccurredAt: time.Unix(12, 0).UTC(), Version: 14, GatewaySyncStatus: domain.GatewaySyncStatusPending}) server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/consume-once", bytes.NewBufferString(`{"consumer":"gateway"}`)) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("unexpected consume status: %d body=%s", rr.Code, rr.Body.String()) } snapshot, ok := repo.GetGatewayAppliedSnapshot(context.Background(), "gateway") if !ok { t.Fatal("expected gateway snapshot") } if snapshot.LastEventID != "evt-apply-first" || snapshot.LastPackageID != 2004 || snapshot.LastResult != string(domain.GatewayAckResultApplied) { t.Fatalf("expected snapshot to stay on last applied event, got %+v", snapshot) } items, _ := repo.ListPackageEventsAfter(context.Background(), "") statusByID := map[string]domain.GatewaySyncStatus{} for _, item := range items { statusByID[item.EventID] = item.GatewaySyncStatus } if statusByID["evt-apply-first"] != domain.GatewaySyncStatusApplied || statusByID["evt-fail-second"] != domain.GatewaySyncStatusFailed { t.Fatalf("unexpected event statuses after consume: %+v", statusByID) } } func TestServerGatewayRuntimeStatusReportsCountsAndPauseResumeEndpoints(t *testing.T) { repo := repository.NewMemoryRepository() nextRetryAt := time.Unix(1, 0).UTC() repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-runtime-retry", EventType: publish.PackagePublishedEventType, PackageID: 3001, Platform: "openai", Model: "gpt-4.1-retry", OccurredAt: time.Unix(20, 0).UTC(), Version: 15, GatewaySyncStatus: domain.GatewaySyncStatusPending, RetryCount: 1, NextRetryAt: &nextRetryAt, LastFailureCategory: domain.GatewayFailureCategoryTemporaryTimeout}) repo.AppendPackageEvent(context.Background(), domain.PackageChangeEvent{EventID: "evt-runtime-failed", EventType: publish.PackagePublishedEventType, PackageID: 3002, Platform: "openai", Model: "gpt-4.1-failed", OccurredAt: time.Unix(21, 0).UTC(), Version: 16, GatewaySyncStatus: domain.GatewaySyncStatusFailed, LastFailureCategory: domain.GatewayFailureCategoryContractInvalid}) service := gatewayconsumer.NewService(repo) runtime := poller.NewRuntime(poller.NewGatewayPackagePoller(service), time.Second) if !runtime.Pause() { t.Fatal("expected pause before start to succeed") } ctx, cancel := context.WithCancel(context.Background()) defer cancel() if !runtime.Start(ctx) { t.Fatal("expected runtime to start") } defer runtime.Stop() server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), service, runtime, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) statusReq := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/gateway/runtime-status", nil) statusRR := httptest.NewRecorder() server.Routes().ServeHTTP(statusRR, statusReq) if statusRR.Code != http.StatusOK { t.Fatalf("unexpected runtime-status status: %d body=%s", statusRR.Code, statusRR.Body.String()) } var statusBody struct { Started bool `json:"started"` Paused bool `json:"paused"` PendingRetryEvents int `json:"pending_retry_events"` FailedEvents int `json:"failed_events"` LastError string `json:"last_error"` } if err := json.NewDecoder(statusRR.Body).Decode(&statusBody); err != nil { t.Fatalf("decode runtime-status response: %v", err) } if !statusBody.Started || !statusBody.Paused { t.Fatalf("expected started and paused runtime, got %+v", statusBody) } if statusBody.PendingRetryEvents != 1 || statusBody.FailedEvents != 1 { t.Fatalf("unexpected runtime counters: %+v", statusBody) } if statusBody.LastError != "" { t.Fatalf("expected empty last_error, got %+v", statusBody) } pauseReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/runtime/pause", nil) pauseRR := httptest.NewRecorder() server.Routes().ServeHTTP(pauseRR, pauseReq) if pauseRR.Code != http.StatusOK { t.Fatalf("unexpected pause status: %d body=%s", pauseRR.Code, pauseRR.Body.String()) } resumeReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/runtime/resume", nil) resumeRR := httptest.NewRecorder() server.Routes().ServeHTTP(resumeRR, resumeReq) if resumeRR.Code != http.StatusOK { t.Fatalf("unexpected resume status: %d body=%s", resumeRR.Code, resumeRR.Body.String()) } if runtime.Status().Paused { t.Fatalf("expected runtime resumed, got %+v", runtime.Status()) } } func TestServerDiscoveryCandidateCreateAndList(t *testing.T) { repo := repository.NewMemoryRepository() server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) createReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/discovery/candidates", bytes.NewBufferString(`{"candidate_id":"cand-1","account_id":301,"platform":"openai","model":"gpt-4.1-mini","source":"manual_seed","reason_code":"new_model","discovered_at":"2026-05-06T20:30:00Z"}`)) createRR := httptest.NewRecorder() server.Routes().ServeHTTP(createRR, createReq) if createRR.Code != http.StatusOK { t.Fatalf("unexpected create status: %d body=%s", createRR.Code, createRR.Body.String()) } listReq := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/discovery/candidates", nil) listRR := httptest.NewRecorder() server.Routes().ServeHTTP(listRR, listReq) if listRR.Code != http.StatusOK { t.Fatalf("unexpected list status: %d body=%s", listRR.Code, listRR.Body.String()) } var listResp struct { Items []domain.DiscoveryCandidate `json:"items"` } if err := json.NewDecoder(listRR.Body).Decode(&listResp); err != nil { t.Fatalf("decode list error: %v", err) } if len(listResp.Items) != 1 || listResp.Items[0].CandidateID != "cand-1" || listResp.Items[0].Status != domain.DiscoveryCandidateStatusDiscovered { t.Fatalf("unexpected discovery list response: %+v", listResp.Items) } } func TestServerDiscoveryCandidateRejectsInvalidInput(t *testing.T) { repo := repository.NewMemoryRepository() server := NewServer(repo, probe.NewService(repo), publish.NewService(repo), gatewayconsumer.NewService(repo), nil, discovery.NewService(repo), admission.NewService(nil, nil, []admission.TestSuite{}, nil, nil), nil, nil) req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/discovery/candidates", bytes.NewBufferString(`{"candidate_id":"","account_id":0}`)) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusBadRequest { t.Fatalf("unexpected status: %d body=%s", rr.Code, rr.Body.String()) } }