Implement catalog force replacement
This commit is contained in:
@@ -137,6 +137,7 @@ func TestBuildTransfersManagedPathOwnership(t *testing.T) {
|
||||
|
||||
func TestBuildRejectsUnmanagedPlannedPathCollision(t *testing.T) {
|
||||
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
|
||||
writeCatalogState(t, destinationBackend, "", baseCatalog(req))
|
||||
testutil.WriteFakeFile(t, destinationBackend, "report.md", "unmanaged")
|
||||
|
||||
plan, err := Build(context.Background(), req)
|
||||
@@ -148,6 +149,55 @@ func TestBuildRejectsUnmanagedPlannedPathCollision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildForceReplacesUnmanagedPlannedPathCollision(t *testing.T) {
|
||||
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
|
||||
req.Force = true
|
||||
existing := baseCatalog(req)
|
||||
writeCatalogState(t, destinationBackend, "", existing)
|
||||
testutil.WriteFakeFile(t, destinationBackend, "report.md", "unmanaged")
|
||||
|
||||
plan, err := Build(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
if plan.Action != ActionForceReplace || !plan.Force || !plan.ClearDestinationRoot {
|
||||
t.Fatalf("plan action=%s force=%t clear=%t, want forced clear", plan.Action, plan.Force, plan.ClearDestinationRoot)
|
||||
}
|
||||
if len(plan.CatalogOutputsToWrite) != 2 || len(plan.CatalogOutputsToRetain) != 0 || len(plan.CatalogOutputsToDelete) != 0 {
|
||||
t.Fatalf("catalog write=%d retain=%d delete=%d", len(plan.CatalogOutputsToWrite), len(plan.CatalogOutputsToRetain), len(plan.CatalogOutputsToDelete))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRejectsNoStateNonEmptyDestinationWithoutForce(t *testing.T) {
|
||||
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
|
||||
testutil.WriteFakeFile(t, destinationBackend, "unplanned.txt", "unmanaged")
|
||||
|
||||
plan, err := Build(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatal("Build() error = nil, want unmanaged destination")
|
||||
}
|
||||
if plan.Action != ActionFailUnmanaged || !strings.Contains(err.Error(), "destination has content but no distributor state") {
|
||||
t.Fatalf("plan action=%s error=%v, want unmanaged destination", plan.Action, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildForceReplacesNoStateNonEmptyDestination(t *testing.T) {
|
||||
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
|
||||
req.Force = true
|
||||
testutil.WriteFakeFile(t, destinationBackend, "unplanned.txt", "unmanaged")
|
||||
|
||||
plan, err := Build(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
if plan.Action != ActionForceReplace || !plan.Force || !plan.ClearDestinationRoot {
|
||||
t.Fatalf("plan action=%s force=%t clear=%t, want forced clear", plan.Action, plan.Force, plan.ClearDestinationRoot)
|
||||
}
|
||||
if len(plan.CatalogOutputsToWrite) != 2 || len(plan.CatalogOutputsToRetain) != 0 || len(plan.CatalogOutputsToDelete) != 0 {
|
||||
t.Fatalf("catalog write=%d retain=%d delete=%d", len(plan.CatalogOutputsToWrite), len(plan.CatalogOutputsToRetain), len(plan.CatalogOutputsToDelete))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPlansSupersededLegacyAdditive(t *testing.T) {
|
||||
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
|
||||
legacyState := testutil.DestinationState(req.SourceBundle.Manifest, testutil.DestinationStateOptions{})
|
||||
@@ -204,6 +254,60 @@ func TestBuildRejectsInvalidOrFutureState(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildForceReplacesInvalidOrFutureState(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data string
|
||||
}{
|
||||
{name: "invalid json", data: `{"schema_version":`},
|
||||
{name: "future schema", data: `{"schema_version":99}`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
|
||||
req.Force = true
|
||||
testutil.WriteFakeFile(t, destinationBackend, storage.StateFileName, tt.data)
|
||||
|
||||
plan, err := Build(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
if plan.Action != ActionForceReplace || !plan.Force || !plan.ClearDestinationRoot {
|
||||
t.Fatalf("plan action=%s force=%t clear=%t, want forced clear", plan.Action, plan.Force, plan.ClearDestinationRoot)
|
||||
}
|
||||
if len(plan.CatalogOutputsToWrite) != 2 || len(plan.CatalogOutputsToRetain) != 0 || len(plan.CatalogOutputsToDelete) != 0 {
|
||||
t.Fatalf("catalog write=%d retain=%d delete=%d", len(plan.CatalogOutputsToWrite), len(plan.CatalogOutputsToRetain), len(plan.CatalogOutputsToDelete))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildForceDoesNotChangeValidCatalogActions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
workflow string
|
||||
want Action
|
||||
}{
|
||||
{name: "additive", workflow: config.WorkflowAdditive, want: ActionUpsertAdditive},
|
||||
{name: "replacement", workflow: config.WorkflowReplacement, want: ActionReplaceCatalog},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, destinationBackend, req := catalogPlanRequest(t, tt.workflow)
|
||||
req.Force = true
|
||||
writeCatalogState(t, destinationBackend, "", baseCatalog(req))
|
||||
|
||||
plan, err := Build(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
if plan.Action != tt.want || plan.Force || plan.ClearDestinationRoot {
|
||||
t.Fatalf("plan action=%s force=%t clear=%t, want %s without forced clear", plan.Action, plan.Force, plan.ClearDestinationRoot, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRequestRejectsInvalidWorkflow(t *testing.T) {
|
||||
sourceBackend, destinationBackend, req := catalogPlanRequest(t, "append")
|
||||
req.SourceBackend = sourceBackend
|
||||
|
||||
Reference in New Issue
Block a user