Files
supply-intelligence/internal/httpapi/admission_state_api_test.go
2026-05-12 18:49:52 +08:00

230 lines
9.0 KiB
Go

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)
}
}