package httpapi_test import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "supply-intelligence/internal/app" "supply-intelligence/internal/domain" "supply-intelligence/internal/probe" ) func domainTime(ts int64) time.Time { return time.Unix(ts, 0).UTC() } func TestApplicationServerRoutes(t *testing.T) { application := app.New() req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/probe/evaluate", bytes.NewBufferString(`{"account_id":7,"platform":"openai","current_status":"active","status_code":401}`)) rr := httptest.NewRecorder() application.Server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("unexpected status: %d body=%s", rr.Code, rr.Body.String()) } var result probe.EvaluateOutput if err := json.NewDecoder(rr.Body).Decode(&result); err != nil { t.Fatalf("decode error: %v", err) } if result.RoutingState.AccountID != 7 || result.RoutingState.AccountStatus != "suspended" { t.Fatalf("unexpected state: %+v", result.RoutingState) } getReq := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/accounts/7/routing-state", nil) getRR := httptest.NewRecorder() application.Server.Routes().ServeHTTP(getRR, getReq) if getRR.Code != http.StatusOK { t.Fatalf("unexpected get status: %d body=%s", getRR.Code, getRR.Body.String()) } } func TestPublishConsumeOnceListAppliedIntegration(t *testing.T) { application := app.New() application.Repo.UpsertDiscoveryCandidateContext(nil, domain.DiscoveryCandidate{CandidateID: "cand-integration-1", AccountID: 601, Platform: "openai", Model: "gpt-4.1-mini", Source: "admission", Status: domain.DiscoveryCandidateStatusTestPassed, DiscoveredAt: domainTime(100), UpdatedAt: domainTime(110), Version: 2}) application.Repo.UpsertSupplyPackage(nil, domain.SupplyPackage{PackageID: 501, Platform: "openai", Model: "gpt-4.1-mini", Status: "draft", Source: "admission", UpdatedAt: domainTime(110), Version: 1}) publishReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/publish/package-event", bytes.NewBufferString(`{"event_id":"evt-integration-1","platform":"openai","model":"gpt-4.1-mini","occurred_at":"2026-05-06T20:30:00Z"}`)) publishRR := httptest.NewRecorder() application.Server.Routes().ServeHTTP(publishRR, publishReq) if publishRR.Code != http.StatusOK { t.Fatalf("unexpected publish status: %d body=%s", publishRR.Code, publishRR.Body.String()) } consumeReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/consume-once", bytes.NewBufferString(`{"consumer":"gateway"}`)) consumeRR := httptest.NewRecorder() application.Server.Routes().ServeHTTP(consumeRR, consumeReq) if consumeRR.Code != http.StatusOK { t.Fatalf("unexpected consume status: %d body=%s", consumeRR.Code, consumeRR.Body.String()) } listReq := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/gateway/package-changes", nil) listRR := httptest.NewRecorder() application.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.Items[0].EventID != "evt-integration-1" { t.Fatalf("unexpected list items: %+v", listResp.Items) } if listResp.NextCursor != "" { t.Fatalf("unexpected next cursor: %+v", listResp) } if listResp.Items[0].GatewaySyncStatus != domain.GatewaySyncStatusApplied { t.Fatalf("unexpected sync status: %+v", listResp.Items[0]) } } func TestPublishConsumeOnceListFailedIntegration(t *testing.T) { application := app.New() application.Repo.UpsertDiscoveryCandidateContext(nil, domain.DiscoveryCandidate{CandidateID: "cand-integration-failed", AccountID: 602, Platform: "openai", Model: "gpt-fail-model", Source: "admission", Status: domain.DiscoveryCandidateStatusTestPassed, DiscoveredAt: domainTime(100), UpdatedAt: domainTime(110), Version: 2}) application.Repo.UpsertSupplyPackage(nil, domain.SupplyPackage{PackageID: 502, Platform: "openai", Model: "gpt-fail-model", Status: "draft", Source: "admission", UpdatedAt: domainTime(110), Version: 1}) publishReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/publish/package-event", bytes.NewBufferString(`{"event_id":"evt-integration-failed","platform":"openai","model":"gpt-fail-model","occurred_at":"2026-05-06T20:31:00Z"}`)) publishRR := httptest.NewRecorder() application.Server.Routes().ServeHTTP(publishRR, publishReq) if publishRR.Code != http.StatusOK { t.Fatalf("unexpected publish status: %d body=%s", publishRR.Code, publishRR.Body.String()) } consumeReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/gateway/consume-once", bytes.NewBufferString(`{"consumer":"gateway"}`)) consumeRR := httptest.NewRecorder() application.Server.Routes().ServeHTTP(consumeRR, consumeReq) if consumeRR.Code != http.StatusOK { t.Fatalf("unexpected consume status: %d body=%s", consumeRR.Code, consumeRR.Body.String()) } listReq := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/gateway/package-changes", nil) listRR := httptest.NewRecorder() application.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.Items[0].EventID != "evt-integration-failed" { t.Fatalf("unexpected list items: %+v", listResp.Items) } if listResp.NextCursor != "" { t.Fatalf("unexpected next cursor: %+v", listResp) } if listResp.Items[0].GatewaySyncStatus != domain.GatewaySyncStatusFailed { t.Fatalf("unexpected sync status: %+v", listResp.Items[0]) } } func TestPublishEndpointDuplicateReplayReturnsStableAlreadyApplied(t *testing.T) { application := app.New() application.Repo.UpsertDiscoveryCandidateContext(nil, domain.DiscoveryCandidate{CandidateID: "cand-dup-stable", AccountID: 603, Platform: "openai", Model: "gpt-4.1-stable", Source: "admission", Status: domain.DiscoveryCandidateStatusTestPassed, DiscoveredAt: domainTime(100), UpdatedAt: domainTime(110), Version: 2}) application.Repo.UpsertSupplyPackage(nil, domain.SupplyPackage{PackageID: 503, Platform: "openai", Model: "gpt-4.1-stable", Status: "draft", Source: "admission", UpdatedAt: domainTime(110), Version: 1}) body := `{"event_id":"evt-stable-1","platform":"openai","model":"gpt-4.1-stable","occurred_at":"2026-05-06T20:32:00Z"}` firstReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/publish/package-event", bytes.NewBufferString(body)) firstRR := httptest.NewRecorder() application.Server.Routes().ServeHTTP(firstRR, firstReq) if firstRR.Code != http.StatusOK { t.Fatalf("unexpected first publish status: %d body=%s", firstRR.Code, firstRR.Body.String()) } replayReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/publish/package-event", bytes.NewBufferString(body)) replayRR := httptest.NewRecorder() application.Server.Routes().ServeHTTP(replayRR, replayReq) if replayRR.Code != http.StatusConflict { t.Fatalf("unexpected replay status: %d body=%s", replayRR.Code, replayRR.Body.String()) } var payload map[string]any if err := json.NewDecoder(replayRR.Body).Decode(&payload); err != nil { t.Fatalf("decode replay error: %v", err) } if payload["error"] != "publish_already_applied" { t.Fatalf("expected stable replay error publish_already_applied, got %+v", payload) } } func TestPublishEndpointHalfAppliedStateReturnsStableAlreadyApplied(t *testing.T) { application := app.New() application.Repo.UpsertDiscoveryCandidateContext(nil, domain.DiscoveryCandidate{CandidateID: "cand-half-state", AccountID: 604, Platform: "openai", Model: "gpt-4.1-half-state", Source: "admission", Status: domain.DiscoveryCandidateStatusPublished, DiscoveredAt: domainTime(100), UpdatedAt: domainTime(110), Version: 2}) application.Repo.UpsertSupplyPackage(nil, domain.SupplyPackage{PackageID: 504, Platform: "openai", Model: "gpt-4.1-half-state", Status: "draft", Source: "admission", UpdatedAt: domainTime(110), Version: 1}) req := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/publish/package-event", bytes.NewBufferString(`{"event_id":"evt-half-state","platform":"openai","model":"gpt-4.1-half-state","occurred_at":"2026-05-06T20:33:00Z"}`)) rr := httptest.NewRecorder() application.Server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusConflict { t.Fatalf("unexpected status: %d body=%s", rr.Code, rr.Body.String()) } var payload map[string]any if err := json.NewDecoder(rr.Body).Decode(&payload); err != nil { t.Fatalf("decode half-applied error: %v", err) } if payload["error"] != "publish_already_applied" { t.Fatalf("expected stable half-applied error publish_already_applied, got %+v", payload) } } func TestDiscoveryCandidateCreateAndListIntegration(t *testing.T) { application := app.New() createReq := httptest.NewRequest(http.MethodPost, "/internal/supply-intelligence/discovery/candidates", bytes.NewBufferString(`{"candidate_id":"cand-int-1","account_id":701,"platform":"openai","model":"gpt-4.1-mini","source":"manual_seed","reason_code":"new_model","discovered_at":"2026-05-06T20:30:00Z"}`)) createRR := httptest.NewRecorder() application.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() application.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-int-1" { t.Fatalf("unexpected discovery list items: %+v", listResp.Items) } }