Files
distributor/internal/publish/takeover_test.go

216 lines
7.8 KiB
Go

package publish
import (
"context"
"strings"
"testing"
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
"gitea.maximumdirect.net/eric/distributor/internal/config"
"gitea.maximumdirect.net/eric/distributor/internal/storage"
"gitea.maximumdirect.net/eric/distributor/internal/storage/fake"
"gitea.maximumdirect.net/eric/distributor/internal/testutil"
)
func TestBuildPlansSingleOwnerTakeoverByPolicy(t *testing.T) {
tests := []struct {
name string
takeover config.TakeoverPolicy
mutateState func(*bundle.Manifest, *testutil.DestinationStateOptions)
wantAction Action
wantErr string
}{
{
name: "default same pipeline different source",
takeover: config.TakeoverPolicy{},
mutateState: func(manifest *bundle.Manifest, opts *testutil.DestinationStateOptions) {
manifest.ID = "other.source"
},
wantAction: ActionReplaceTakeover,
},
{
name: "same pipeline different newer source",
takeover: config.TakeoverPolicy{Mode: config.TakeoverModeSamePipeline},
mutateState: func(manifest *bundle.Manifest, opts *testutil.DestinationStateOptions) {
manifest.ID = "other.source"
manifest.Created = manifest.Created.AddDate(0, 0, 1)
},
wantAction: ActionReplaceTakeover,
},
{
name: "default same pipeline different destination",
takeover: config.TakeoverPolicy{},
mutateState: func(manifest *bundle.Manifest, opts *testutil.DestinationStateOptions) {
opts.DestinationID = "web"
},
wantAction: ActionReplaceTakeover,
},
{
name: "same pipeline refuses different pipeline",
takeover: config.TakeoverPolicy{Mode: config.TakeoverModeSamePipeline},
mutateState: func(manifest *bundle.Manifest, opts *testutil.DestinationStateOptions) {
opts.PipelineID = "other"
},
wantErr: "fail_conflict",
},
{
name: "same source allows different pipeline",
takeover: config.TakeoverPolicy{Mode: config.TakeoverModeSameSource},
mutateState: func(manifest *bundle.Manifest, opts *testutil.DestinationStateOptions) {
opts.PipelineID = "other"
},
wantAction: ActionReplaceTakeover,
},
{
name: "same source refuses different source",
takeover: config.TakeoverPolicy{Mode: config.TakeoverModeSameSource},
mutateState: func(manifest *bundle.Manifest, opts *testutil.DestinationStateOptions) {
manifest.ID = "other.source"
},
wantErr: "fail_conflict",
},
{
name: "any managed allows different pipeline",
takeover: config.TakeoverPolicy{Mode: config.TakeoverModeAnyManaged},
mutateState: func(manifest *bundle.Manifest, opts *testutil.DestinationStateOptions) {
manifest.ID = "other.source"
opts.PipelineID = "other"
},
wantAction: ActionReplaceTakeover,
},
{
name: "never refuses different source",
takeover: config.TakeoverPolicy{Mode: config.TakeoverModeNever},
mutateState: func(manifest *bundle.Manifest, opts *testutil.DestinationStateOptions) {
manifest.ID = "other.source"
},
wantErr: "fail_conflict",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sourceBackend := fake.New()
sourceBundle := testutil.WriteFakeSourceBundle(t, sourceBackend, "bundle", testutil.BundleOptions{})
destinationBackend := fake.New()
destinationManifest := sourceBundle.Manifest
destinationManifest.Files = append([]bundle.ManifestFile(nil), sourceBundle.Manifest.Files...)
opts := testutil.DestinationStateOptions{}
tt.mutateState(&destinationManifest, &opts)
testutil.WriteFakeDestinationState(t, destinationBackend, "bundle", destinationManifest, opts)
req := takeoverRequest(sourceBackend, destinationBackend, sourceBundle, tt.takeover, config.ReconciliationModeReplace)
plan, err := Build(context.Background(), req)
if tt.wantErr != "" {
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("Build() error = %v, want %q", err, tt.wantErr)
}
return
}
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if plan.Action != tt.wantAction {
t.Fatalf("plan action = %s, want %s", plan.Action, tt.wantAction)
}
})
}
}
func TestBuildDoesNotTakeOverInvalidOrUnmanagedDestination(t *testing.T) {
tests := []struct {
name string
prepare func(t *testing.T, backend *fake.Backend)
wantErr string
}{
{
name: "invalid state",
prepare: func(t *testing.T, backend *fake.Backend) {
t.Helper()
statePath, err := storage.StatePath("bundle")
if err != nil {
t.Fatalf("state path: %v", err)
}
testutil.WriteFakeFile(t, backend, statePath, "{invalid")
},
wantErr: "fail_conflict",
},
{
name: "unmanaged content",
prepare: func(t *testing.T, backend *fake.Backend) {
t.Helper()
testutil.WriteFakeFile(t, backend, "bundle/old.txt", "old")
},
wantErr: "fail_unmanaged",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sourceBackend := fake.New()
sourceBundle := testutil.WriteFakeSourceBundle(t, sourceBackend, "bundle", testutil.BundleOptions{})
destinationBackend := fake.New()
tt.prepare(t, destinationBackend)
req := takeoverRequest(sourceBackend, destinationBackend, sourceBundle, config.TakeoverPolicy{Mode: config.TakeoverModeAnyManaged}, config.ReconciliationModeReplace)
_, err := Build(context.Background(), req)
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("Build() error = %v, want %q", err, tt.wantErr)
}
})
}
}
func TestExecuteTakeoverMergeDoesNotRetainOldSourceOutputs(t *testing.T) {
sourceBackend := fake.New()
sourceBundle := testutil.WriteFakeSourceBundle(t, sourceBackend, "bundle", testutil.BundleOptions{
Files: []testutil.SourceFile{{Path: "report.md", Data: "# Report\nNew.\n"}},
})
destinationBackend := fake.New()
oldManifest := sourceBundle.Manifest
oldManifest.ID = "old.source"
oldManifest.Files = []bundle.ManifestFile{
{Path: "report.md", SHA256: bundle.FileDigest([]byte("old\n")), Size: int64(len("old\n"))},
{Path: "summary.txt", SHA256: bundle.FileDigest([]byte("old summary\n")), Size: int64(len("old summary\n"))},
}
oldManifest.Digest = bundle.BundleDigest(oldManifest.Files)
testutil.WriteFakeDestinationState(t, destinationBackend, "bundle", oldManifest, testutil.DestinationStateOptions{})
req := takeoverRequest(sourceBackend, destinationBackend, sourceBundle, config.TakeoverPolicy{Mode: config.TakeoverModeSamePipeline}, config.ReconciliationModeMerge)
plan, err := Build(context.Background(), req)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if plan.Action != ActionReplaceTakeover {
t.Fatalf("plan action = %s, want %s", plan.Action, ActionReplaceTakeover)
}
if err := Execute(context.Background(), req, plan); err != nil {
t.Fatalf("Execute() error = %v", err)
}
testutil.AssertFakeFile(t, destinationBackend, "bundle/report.md", "# Report\nNew.\n")
testutil.AssertFakeMissing(t, destinationBackend, "bundle/summary.txt")
destinationState := readFakeState(t, destinationBackend, "bundle")
if got, want := len(destinationState.Outputs), 1; got != want {
t.Fatalf("state output count = %d, want %d", got, want)
}
if got, want := destinationState.Source.Manifest.ID, sourceBundle.Manifest.ID; got != want {
t.Fatalf("state source id = %q, want %q", got, want)
}
}
func takeoverRequest(sourceBackend, destinationBackend *fake.Backend, sourceBundle bundle.Bundle, takeover config.TakeoverPolicy, reconciliationMode string) Request {
return Request{
PipelineID: "reports",
DestinationID: "archive",
SourceBundle: sourceBundle,
SourceBackend: sourceBackend,
DestinationBackend: destinationBackend,
DestinationBundlePath: sourceBundle.RootRelativePath,
Publish: config.PublishPolicy{Source: true},
Reconciliation: config.ReconciliationPolicy{Mode: reconciliationMode},
Takeover: takeover,
Transfer: defaultTransfer(),
DistributorVersion: "test",
}
}