package state import ( "errors" "testing" "time" "gitea.maximumdirect.net/eric/distributor/internal/bundle" ) func TestCompareOutcomes(t *testing.T) { source := validManifest(t) tests := []struct { name string status DestinationStatus want Outcome }{ { name: "destination absent", status: DestinationStatus{}, want: OutcomeDestinationAbsent, }, { name: "destination unmanaged", status: DestinationStatus{HasContents: true}, want: OutcomeDestinationUnmanaged, }, { name: "invalid destination state", status: DestinationStatus{StateErr: errors.New("invalid json")}, want: OutcomeInvalidState, }, { name: "pipeline mismatch", status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.PipelineID = "other" })}, want: OutcomeIdentityMismatch, }, { name: "destination mismatch", status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.DestinationID = "other" })}, want: OutcomeIdentityMismatch, }, { name: "same source manifest", status: DestinationStatus{State: withState(t, source, nil)}, want: OutcomeSameSource, }, { name: "destination older", status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.Source.Manifest.Created = source.Created.Add(-time.Hour) })}, want: OutcomeDestinationOlder, }, { name: "destination newer", status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.Source.Manifest.Created = source.Created.Add(time.Hour) })}, want: OutcomeDestinationNewer, }, { name: "same created digest conflict", status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.Source.Manifest.Files[0].SHA256 = "sha256:3333333333333333333333333333333333333333333333333333333333333333" s.Source.Manifest.Digest = bundle.BundleDigest(s.Source.Manifest.Files) })}, want: OutcomeSameCreatedConflict, }, { name: "different source id", status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.Source.Manifest.ID = "other.source" })}, want: OutcomeDifferentSourceConflict, }, { name: "invalid state object", status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.Outputs[0].Kind = "other" })}, want: OutcomeInvalidState, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Compare(source, "reports", "archive", tt.status) if got.Outcome != tt.want { t.Fatalf("Compare() outcome = %s reason=%q, want %s", got.Outcome, got.Reason, tt.want) } if got.Reason == "" { t.Fatal("Compare() reason is empty") } }) } } func withState(t *testing.T, source bundle.Manifest, mutate func(*DistributorState)) *DistributorState { t.Helper() stateManifest := source stateManifest.Files = append([]bundle.ManifestFile(nil), source.Files...) state := DistributorState{ SchemaVersion: SchemaVersion, PipelineID: "reports", DestinationID: "archive", PublishedAt: time.Date(2026, 5, 30, 11, 12, 0, 0, time.UTC), Source: SourceState{Manifest: stateManifest}, Outputs: []OutputFile{{ Path: "report.md", Kind: OutputKindSource, SourcePath: "report.md", SHA256: source.Files[0].SHA256, Size: source.Files[0].Size, }}, } if mutate != nil { mutate(&state) } return &state }