package publish_test import ( "context" "testing" "time" "supply-intelligence/internal/domain" "supply-intelligence/internal/publish" ) type txCaptureRepo struct { candidate domain.DiscoveryCandidate pkg domain.SupplyPackage event domain.PackageChangeEvent publishCalled bool } func (r *txCaptureRepo) AppendPackageEventContext(ctx context.Context, evt domain.PackageChangeEvent) (domain.PackageChangeEvent, error) { panic("AppendPackageEventContext should not be called directly when publish transaction is supported") } func (r *txCaptureRepo) GetLatestDiscoveryCandidateContext(ctx context.Context, platform, model string) (domain.DiscoveryCandidate, bool) { return r.candidate, r.candidate.Platform == platform && r.candidate.Model == model } func (r *txCaptureRepo) UpdateCandidateStatus(ctx context.Context, candidateID string, status domain.DiscoveryCandidateStatus, failureCode, failureSummary string) error { panic("UpdateCandidateStatus should not be called directly when publish transaction is supported") } func (r *txCaptureRepo) GetSupplyPackage(ctx context.Context, platform, model string) (domain.SupplyPackage, bool) { return r.pkg, r.pkg.Platform == platform && r.pkg.Model == model } func (r *txCaptureRepo) UpsertSupplyPackage(ctx context.Context, pkg domain.SupplyPackage) error { panic("UpsertSupplyPackage should not be called directly when publish transaction is supported") } func (r *txCaptureRepo) PublishPackageAtomically(ctx context.Context, input publish.PublishPackageAtomicInput) (publish.PublishPackageAtomicResult, error) { r.publishCalled = true r.event = input.Event r.candidate = input.Candidate r.pkg = input.Package return publish.PublishPackageAtomicResult{ Candidate: input.Candidate, Package: input.Package, Event: input.Event, }, nil } func TestServicePublishDraftUsesAtomicPublisherWhenAvailable(t *testing.T) { repo := &txCaptureRepo{ candidate: domain.DiscoveryCandidate{ CandidateID: "cand-atomic", AccountID: 9001, 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, }, pkg: domain.SupplyPackage{ PackageID: 88, Platform: "openai", Model: "gpt-4.1-mini", Status: "draft", Source: "admission", CreatedAt: time.Unix(90, 0).UTC(), UpdatedAt: time.Unix(110, 0).UTC(), Version: 5, }, } service := publish.NewService(repo) now := time.Unix(200, 0).UTC() out, err := service.PublishDraft(context.Background(), publish.PublishDraftInput{ EventID: "evt-atomic-1", Platform: "openai", Model: "gpt-4.1-mini", OccurredAt: now, }) if err != nil { t.Fatalf("unexpected error: %v", err) } if !repo.publishCalled { t.Fatal("expected atomic publish path to be used") } if out.Candidate.Status != domain.DiscoveryCandidateStatusPublished { t.Fatalf("expected published candidate, got %+v", out.Candidate) } if out.Package.Status != "active" { t.Fatalf("expected active package, got %+v", out.Package) } if out.Event.EventID != "evt-atomic-1" || out.Event.GatewaySyncStatus != domain.GatewaySyncStatusPending { t.Fatalf("unexpected event: %+v", out.Event) } if out.Package.Version != 6 { t.Fatalf("expected package version incremented, got %+v", out.Package) } }