package httpapi import ( "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/probe" "supply-intelligence/internal/publish" "supply-intelligence/internal/repository" ) func TestAdmissionStateEndpointReturnsCurrentCandidateAndPackageTruth(t *testing.T) { repo := repository.NewMemoryRepository() repo.UpsertDiscoveryCandidateContext(nil, domain.DiscoveryCandidate{ CandidateID: "cand-1", AccountID: 301, Platform: "openai", Model: "gpt-4.1-mini", Source: "manual_seed", Status: domain.DiscoveryCandidateStatusDiscovered, ReasonCode: "earlier_state", DiscoveredAt: time.Unix(90, 0).UTC(), UpdatedAt: time.Unix(90, 0).UTC(), Version: 1, }) repo.UpsertDiscoveryCandidateContext(nil, domain.DiscoveryCandidate{ CandidateID: "cand-2", AccountID: 301, Platform: "openai", Model: "gpt-4.1-mini", Source: "manual_seed", Status: domain.DiscoveryCandidateStatusTestPassed, ReasonCode: "ready_for_package", DiscoveredAt: time.Unix(100, 0).UTC(), UpdatedAt: time.Unix(110, 0).UTC(), Version: 2, }) repo.UpsertSupplyPackage(nil, domain.SupplyPackage{ PackageID: 9, Platform: "openai", Model: "gpt-4.1-mini", Status: "draft", Source: "manual_seed", }) _, _ = repo.AppendPackageEventContext(nil, domain.PackageChangeEvent{ EventID: "evt-other-newer", EventType: publish.PackagePublishedEventType, PackageID: 10, Platform: "openai", Model: "gpt-4.1", OccurredAt: time.Unix(130, 0).UTC(), Version: 1, GatewaySyncStatus: domain.GatewaySyncStatusFailed, }) _, _ = repo.AppendPackageEventContext(nil, domain.PackageChangeEvent{ EventID: "evt-old", EventType: publish.PackagePublishedEventType, PackageID: 9, Platform: "openai", Model: "gpt-4.1-mini", OccurredAt: time.Unix(100, 0).UTC(), Version: 1, GatewaySyncStatus: domain.GatewaySyncStatusPending, }) _, _ = repo.AppendPackageEventContext(nil, domain.PackageChangeEvent{ EventID: "evt-latest", EventType: publish.PackagePublishedEventType, PackageID: 9, Platform: "openai", Model: "gpt-4.1-mini", OccurredAt: time.Unix(120, 0).UTC(), Version: 2, GatewaySyncStatus: domain.GatewaySyncStatusApplied, }) 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/models/openai/gpt-4.1-mini/admission-state", nil) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected implemented admission-state endpoint, got status=%d body=%s", rr.Code, rr.Body.String()) } var body struct { Platform string `json:"platform"` Model string `json:"model"` Candidate *domain.DiscoveryCandidate `json:"candidate"` Package *domain.SupplyPackage `json:"package"` GatewaySyncStatus domain.GatewaySyncStatus `json:"gateway_sync_status"` LastEvent *domain.PackageChangeEvent `json:"last_event"` } if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { t.Fatalf("decode response: %v", err) } if body.Candidate == nil || body.Candidate.CandidateID != "cand-2" || body.Candidate.Status != domain.DiscoveryCandidateStatusTestPassed { t.Fatalf("expected latest candidate truth, got %+v", body.Candidate) } if body.Package == nil || body.Package.Status != "draft" { t.Fatalf("expected package truth, got %+v", body.Package) } if body.LastEvent == nil || body.LastEvent.EventID != "evt-latest" { t.Fatalf("expected latest matching event truth, got %+v", body.LastEvent) } if body.GatewaySyncStatus != domain.GatewaySyncStatusApplied { t.Fatalf("expected gateway sync status from latest matching event, got %q", body.GatewaySyncStatus) } } func TestAdmissionStateEndpointReflectsPublishTransitionAndAck(t *testing.T) { repo := repository.NewMemoryRepository() repo.UpsertDiscoveryCandidateContext(nil, domain.DiscoveryCandidate{ CandidateID: "cand-publish", AccountID: 401, Platform: "openai", Model: "gpt-4.1-mini", Source: "manual_seed", Status: domain.DiscoveryCandidateStatusTestPassed, DiscoveredAt: time.Unix(100, 0).UTC(), UpdatedAt: time.Unix(110, 0).UTC(), Version: 2, }) repo.UpsertSupplyPackage(nil, domain.SupplyPackage{ PackageID: 21, Platform: "openai", Model: "gpt-4.1-mini", Status: "draft", Source: "manual_seed", UpdatedAt: time.Unix(110, 0).UTC(), Version: 1, }) publishService := publish.NewService(repo) if _, err := publishService.PublishDraft(nil, publish.PublishDraftInput{EventID: "evt-publish", Platform: "openai", Model: "gpt-4.1-mini", OccurredAt: time.Unix(120, 0).UTC()}); err != nil { t.Fatalf("publish draft: %v", err) } server := NewServer(repo, probe.NewService(repo), publishService, 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/models/openai/gpt-4.1-mini/admission-state", nil) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected status 200, got=%d body=%s", rr.Code, rr.Body.String()) } var body struct { Candidate *domain.DiscoveryCandidate `json:"candidate"` Package *domain.SupplyPackage `json:"package"` GatewaySyncStatus domain.GatewaySyncStatus `json:"gateway_sync_status"` } if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { t.Fatalf("decode response: %v", err) } if body.Candidate == nil || body.Candidate.Status != domain.DiscoveryCandidateStatusPublished { t.Fatalf("expected published candidate, got %+v", body.Candidate) } if body.Package == nil || body.Package.Status != "active" { t.Fatalf("expected active package, got %+v", body.Package) } if body.GatewaySyncStatus != domain.GatewaySyncStatusPending { t.Fatalf("expected pending sync status, got %q", body.GatewaySyncStatus) } _, err := repo.AckPackageEvent(nil, "evt-publish", "gateway", domain.GatewayAckResultApplied, "ok", time.Unix(130, 0).UTC()) if err != nil { t.Fatalf("ack event: %v", err) } ackedReq := httptest.NewRequest(http.MethodGet, "/internal/supply-intelligence/models/openai/gpt-4.1-mini/admission-state", nil) ackedRR := httptest.NewRecorder() server.Routes().ServeHTTP(ackedRR, ackedReq) var ackedBody struct { GatewaySyncStatus domain.GatewaySyncStatus `json:"gateway_sync_status"` } if err := json.NewDecoder(ackedRR.Body).Decode(&ackedBody); err != nil { t.Fatalf("decode acked response: %v", err) } if ackedBody.GatewaySyncStatus != domain.GatewaySyncStatusApplied { t.Fatalf("expected applied sync status after ack, got %q", ackedBody.GatewaySyncStatus) } } func TestAdmissionStateEndpointOmitsForeignLatestEvent(t *testing.T) { repo := repository.NewMemoryRepository() repo.UpsertSupplyPackage(nil, domain.SupplyPackage{ PackageID: 9, Platform: "openai", Model: "gpt-4.1-mini", Status: "draft", Source: "manual_seed", }) _, _ = repo.AppendPackageEventContext(nil, domain.PackageChangeEvent{ EventID: "evt-only-other-model", EventType: publish.PackagePublishedEventType, PackageID: 10, Platform: "openai", Model: "gpt-4.1", OccurredAt: time.Unix(130, 0).UTC(), Version: 1, 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.MethodGet, "/internal/supply-intelligence/models/openai/gpt-4.1-mini/admission-state", nil) rr := httptest.NewRecorder() server.Routes().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected implemented admission-state endpoint, got status=%d body=%s", rr.Code, rr.Body.String()) } var body struct { GatewaySyncStatus domain.GatewaySyncStatus `json:"gateway_sync_status"` LastEvent *domain.PackageChangeEvent `json:"last_event"` } if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { t.Fatalf("decode response: %v", err) } if body.LastEvent != nil { t.Fatalf("expected no last event for unrelated latest event, got %+v", body.LastEvent) } if body.GatewaySyncStatus != "" { t.Fatalf("expected empty gateway sync status without matching event, got %q", body.GatewaySyncStatus) } }