Files
distributor/internal/publish/execute_test.go

228 lines
9.6 KiB
Go

package publish
import (
"context"
"testing"
"gitea.maximumdirect.net/eric/distributor/internal/config"
"gitea.maximumdirect.net/eric/distributor/internal/state"
"gitea.maximumdirect.net/eric/distributor/internal/storage"
"gitea.maximumdirect.net/eric/distributor/internal/storage/fake"
"gitea.maximumdirect.net/eric/distributor/internal/testutil"
)
func TestExecuteAdditiveWritesOutputsAndCatalog(t *testing.T) {
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
existing := baseCatalog(req)
existing.Outputs = []state.CatalogOutputFile{
catalogOutput(req, "reports", "archive", "report.md", state.OutputKindSource, planCreatedAt),
catalogOutput(req, "reports", "web", "old.txt", state.OutputKindSource, planCreatedAt),
}
writeCatalogState(t, destinationBackend, "", existing)
testutil.WriteFakeFile(t, destinationBackend, "report.md", "old report")
testutil.WriteFakeFile(t, destinationBackend, "old.txt", "retained")
plan, err := Build(context.Background(), req)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if err := Execute(context.Background(), req, plan); err != nil {
t.Fatalf("Execute() error = %v", err)
}
testutil.AssertFakeFile(t, destinationBackend, "report.md", "# Report\nSunny.\n")
testutil.AssertFakeFile(t, destinationBackend, "summary.txt", "Summary\n")
testutil.AssertFakeFile(t, destinationBackend, "old.txt", "retained")
catalog := readCatalogState(t, destinationBackend, "")
if catalog.SchemaVersion != state.CatalogSchemaVersion || catalog.State.Mode != state.StateModeCatalog {
t.Fatalf("catalog identity = schema %d mode %s", catalog.SchemaVersion, catalog.State.Mode)
}
if len(catalog.Outputs) != 3 {
t.Fatalf("catalog outputs = %#v, want three outputs", catalog.Outputs)
}
report, ok := state.FindCatalogOutputByPath(catalog.Outputs, "report.md")
if !ok {
t.Fatalf("catalog outputs = %#v, want report.md", catalog.Outputs)
}
if !report.CreatedAt.Equal(planCreatedAt) || !report.UpdatedAt.Equal(planUpdatedAt) {
t.Fatalf("report times = %s/%s, want created preserved and updated now", report.CreatedAt, report.UpdatedAt)
}
if report.SourcePath != "" {
t.Fatalf("source catalog output source_path = %q, want empty", report.SourcePath)
}
}
func TestExecuteReplacementDeletesCurrentOwnerAndPreservesOtherOwners(t *testing.T) {
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowReplacement)
existing := baseCatalog(req)
existing.Outputs = []state.CatalogOutputFile{
catalogOutput(req, "reports", "archive", "report.md", state.OutputKindSource, planCreatedAt),
catalogOutput(req, "reports", "archive", "stale.txt", state.OutputKindSource, planCreatedAt),
catalogOutput(req, "reports", "web", "shared.txt", state.OutputKindSource, planCreatedAt),
}
writeCatalogState(t, destinationBackend, "", existing)
testutil.WriteFakeFile(t, destinationBackend, "report.md", "old report")
testutil.WriteFakeFile(t, destinationBackend, "stale.txt", "delete me")
testutil.WriteFakeFile(t, destinationBackend, "shared.txt", "keep me")
plan, err := Build(context.Background(), req)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if err := Execute(context.Background(), req, plan); err != nil {
t.Fatalf("Execute() error = %v", err)
}
testutil.AssertFakeFile(t, destinationBackend, "report.md", "# Report\nSunny.\n")
testutil.AssertFakeFile(t, destinationBackend, "summary.txt", "Summary\n")
testutil.AssertFakeMissing(t, destinationBackend, "stale.txt")
testutil.AssertFakeFile(t, destinationBackend, "shared.txt", "keep me")
catalog := readCatalogState(t, destinationBackend, "")
if _, ok := state.FindCatalogOutputByPath(catalog.Outputs, "stale.txt"); ok {
t.Fatalf("catalog outputs = %#v, want stale.txt removed", catalog.Outputs)
}
if _, ok := state.FindCatalogOutputByPath(catalog.Outputs, "shared.txt"); !ok {
t.Fatalf("catalog outputs = %#v, want shared.txt retained", catalog.Outputs)
}
}
func TestExecuteSupersededReplacementClearsDestinationRootOnly(t *testing.T) {
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowReplacement)
req.DestinationBundlePath = "bundle"
legacyState := legacyStateDocument(2)
writeJSONState(t, destinationBackend, req.DestinationBundlePath, legacyState)
testutil.WriteFakeFile(t, destinationBackend, "bundle/unplanned.txt", "remove")
testutil.WriteFakeFile(t, destinationBackend, "outside.txt", "keep")
plan, err := Build(context.Background(), req)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if err := Execute(context.Background(), req, plan); err != nil {
t.Fatalf("Execute() error = %v", err)
}
testutil.AssertFakeMissing(t, destinationBackend, "bundle/unplanned.txt")
testutil.AssertFakeFile(t, destinationBackend, "bundle/report.md", "# Report\nSunny.\n")
testutil.AssertFakeFile(t, destinationBackend, "outside.txt", "keep")
readCatalogState(t, destinationBackend, "bundle")
}
func TestExecuteSupersededAdditiveLeavesUnplannedFilesUnmanaged(t *testing.T) {
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
legacyState := legacyStateDocument(2)
writeJSONState(t, destinationBackend, "", legacyState)
testutil.WriteFakeFile(t, destinationBackend, "report.md", "legacy report")
testutil.WriteFakeFile(t, destinationBackend, "unplanned.txt", "leave me")
plan, err := Build(context.Background(), req)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if err := Execute(context.Background(), req, plan); err != nil {
t.Fatalf("Execute() error = %v", err)
}
testutil.AssertFakeFile(t, destinationBackend, "report.md", "# Report\nSunny.\n")
testutil.AssertFakeFile(t, destinationBackend, "unplanned.txt", "leave me")
catalog := readCatalogState(t, destinationBackend, "")
if _, ok := state.FindCatalogOutputByPath(catalog.Outputs, "unplanned.txt"); ok {
t.Fatalf("catalog outputs = %#v, want unplanned file omitted", catalog.Outputs)
}
}
func TestExecuteForceReplaceClearsDestinationBundlePathOnly(t *testing.T) {
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
req.DestinationBundlePath = "bundle"
req.Force = true
testutil.WriteFakeFile(t, destinationBackend, "bundle/report.md", "old report")
testutil.WriteFakeFile(t, destinationBackend, "bundle/unplanned.txt", "remove")
testutil.WriteFakeFile(t, destinationBackend, "bundle-child/keep.txt", "keep")
testutil.WriteFakeFile(t, destinationBackend, "outside.txt", "keep")
plan, err := Build(context.Background(), req)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if plan.Action != ActionForceReplace {
t.Fatalf("plan action = %s, want %s", plan.Action, ActionForceReplace)
}
if err := Execute(context.Background(), req, plan); err != nil {
t.Fatalf("Execute() error = %v", err)
}
testutil.AssertFakeFile(t, destinationBackend, "bundle/report.md", "# Report\nSunny.\n")
testutil.AssertFakeFile(t, destinationBackend, "bundle/summary.txt", "Summary\n")
testutil.AssertFakeMissing(t, destinationBackend, "bundle/unplanned.txt")
testutil.AssertFakeFile(t, destinationBackend, "bundle-child/keep.txt", "keep")
testutil.AssertFakeFile(t, destinationBackend, "outside.txt", "keep")
catalog := readCatalogState(t, destinationBackend, "bundle")
if catalog.SchemaVersion != state.CatalogSchemaVersion || catalog.State.Mode != state.StateModeCatalog {
t.Fatalf("catalog identity = schema %d mode %s", catalog.SchemaVersion, catalog.State.Mode)
}
if len(catalog.Outputs) != 2 {
t.Fatalf("catalog outputs = %#v, want planned outputs only", catalog.Outputs)
}
}
func TestExecuteForceReplaceClearsFixedDestinationRoot(t *testing.T) {
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
req.DestinationBundlePath = ""
req.PathMapping = config.PathMappingFixed
req.Force = true
testutil.WriteFakeFile(t, destinationBackend, "report.md", "old report")
testutil.WriteFakeFile(t, destinationBackend, "unplanned.txt", "remove")
plan, err := Build(context.Background(), req)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if plan.Action != ActionForceReplace || storage.DisplayPath(plan.DestinationBundlePath) != "." {
t.Fatalf("plan action=%s destination=%s, want force_replace at root", plan.Action, storage.DisplayPath(plan.DestinationBundlePath))
}
if err := Execute(context.Background(), req, plan); err != nil {
t.Fatalf("Execute() error = %v", err)
}
testutil.AssertFakeFile(t, destinationBackend, "report.md", "# Report\nSunny.\n")
testutil.AssertFakeFile(t, destinationBackend, "summary.txt", "Summary\n")
testutil.AssertFakeMissing(t, destinationBackend, "unplanned.txt")
readCatalogState(t, destinationBackend, "")
}
func TestExecuteFailedWriteDoesNotWriteCatalogState(t *testing.T) {
_, destinationBackend, req := catalogPlanRequest(t, config.WorkflowAdditive)
plan, err := Build(context.Background(), req)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if err := destinationBackend.AddDirectory("report.md"); err != nil {
t.Fatalf("add conflicting directory: %v", err)
}
err = Execute(context.Background(), req, plan)
if err == nil {
t.Fatal("Execute() error = nil, want write failure")
}
if _, statErr := destinationBackend.Stat(context.Background(), storage.StateFileName); !storage.IsNotFound(statErr) {
t.Fatalf("state stat error = %v, want missing state", statErr)
}
}
func readCatalogState(t *testing.T, backend *fake.Backend, relative string) state.CatalogState {
t.Helper()
statePath, err := storage.StatePath(relative)
if err != nil {
t.Fatalf("state path: %v", err)
}
data, err := backend.ReadFile(context.Background(), statePath)
if err != nil {
t.Fatalf("read catalog state: %v", err)
}
catalog, err := state.ParseCatalog(data)
if err != nil {
t.Fatalf("parse catalog state: %v", err)
}
return catalog
}