Files
distributor/internal/state/compare_test.go

217 lines
6.9 KiB
Go

package state
import (
"errors"
"testing"
"time"
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
"gitea.maximumdirect.net/eric/distributor/internal/config"
)
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 TestCompareReportsStructuredDetails(t *testing.T) {
source := validManifest(t)
tests := []struct {
name string
status DestinationStatus
wantKind ComparisonDetailKind
assertions func(t *testing.T, detail ComparisonDetail)
}{
{
name: "pipeline mismatch",
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.PipelineID = "other" })},
wantKind: ComparisonDetailPipelineIDMismatch,
assertions: func(t *testing.T, detail ComparisonDetail) {
t.Helper()
if detail.DestinationPipelineID != "other" || detail.CurrentPipelineID != "reports" {
t.Fatalf("detail = %#v, want pipeline ids", detail)
}
},
},
{
name: "destination mismatch",
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.DestinationID = "other" })},
wantKind: ComparisonDetailDestinationIDMismatch,
assertions: func(t *testing.T, detail ComparisonDetail) {
t.Helper()
if detail.DestinationDestinationID != "other" || detail.CurrentDestinationID != "archive" {
t.Fatalf("detail = %#v, want destination ids", detail)
}
},
},
{
name: "different source id",
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.Source.Manifest.ID = "other.source" })},
wantKind: ComparisonDetailDifferentSourceID,
assertions: func(t *testing.T, detail ComparisonDetail) {
t.Helper()
if detail.DestinationSourceID != "other.source" || detail.CurrentSourceID != source.ID {
t.Fatalf("detail = %#v, want source ids", detail)
}
},
},
{
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)
})},
wantKind: ComparisonDetailSameCreatedDigestConflict,
assertions: func(t *testing.T, detail ComparisonDetail) {
t.Helper()
if detail.CurrentSourceDigest == "" || detail.DestinationSourceDigest == "" || detail.CurrentSourceDigest == detail.DestinationSourceDigest {
t.Fatalf("detail = %#v, want different source digests", detail)
}
},
},
{
name: "destination newer",
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) {
s.Source.Manifest.Created = source.Created.Add(time.Hour)
})},
wantKind: ComparisonDetailDestinationNewer,
},
{
name: "invalid state",
status: DestinationStatus{StateErr: errors.New("invalid json")},
wantKind: ComparisonDetailInvalidState,
},
{
name: "unmanaged content",
status: DestinationStatus{HasContents: true},
wantKind: ComparisonDetailUnmanagedContent,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Compare(source, "reports", "archive", tt.status)
if got.Detail.Kind != tt.wantKind {
t.Fatalf("Compare() detail kind = %q, want %q; comparison=%#v", got.Detail.Kind, tt.wantKind, got)
}
if tt.assertions != nil {
tt.assertions(t, got.Detail)
}
})
}
}
func withState(t *testing.T, source bundle.Manifest, mutate func(*DistributorState)) *DistributorState {
t.Helper()
stateManifest := source
stateManifest.Files = append([]bundle.ManifestFile(nil), source.Files...)
publishedAt := time.Date(2026, 5, 30, 11, 12, 0, 0, time.UTC)
state := DistributorState{
SchemaVersion: SchemaVersion,
PipelineID: "reports",
DestinationID: "archive",
PublishedAt: publishedAt,
CreatedAt: publishedAt,
UpdatedAt: publishedAt,
State: StatePolicy{Mode: StateModeSingleOwner},
Reconciliation: ReconciliationPolicy{Mode: config.ReconciliationModeReplace},
Source: SourceState{Manifest: stateManifest},
Outputs: []OutputFile{{
Path: "report.md",
Kind: OutputKindSource,
SourcePath: "report.md",
SHA256: source.Files[0].SHA256,
Size: source.Files[0].Size,
CreatedAt: publishedAt,
UpdatedAt: publishedAt,
}},
}
if mutate != nil {
mutate(&state)
}
return &state
}