package publish import ( "context" "fmt" "io" "testing" "time" "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" ) func TestExecuteCleansUpAfterWriteFailure(t *testing.T) { sourceBackend := fake.New() destinationBackend := &failingBackend{Backend: fake.New(), failPath: "summary.txt"} sourceBundle := writeFakeSourceBundle(t, sourceBackend) req := Request{ PipelineID: "reports", DestinationID: "archive", SourceBundle: sourceBundle, SourceBackend: sourceBackend, DestinationBackend: destinationBackend, DestinationBundlePath: "", Publish: config.PublishPolicy{Source: true}, Transfer: config.TransferPolicy{OnDestinationSame: config.TransferActionSkip, OnDestinationOlder: config.TransferActionReplace, OnDestinationNewer: config.TransferActionSkip, OnConflict: config.TransferActionFail}, DistributorVersion: "test", } plan, err := Build(context.Background(), req) if err != nil { t.Fatalf("Build() error = %v", err) } err = Execute(context.Background(), req, plan) if err == nil { t.Fatal("Execute() error = nil, want error") } found, err := destinationBackend.HasAny(context.Background(), "") if err != nil { t.Fatalf("HasAny() error = %v", err) } if found { t.Fatal("destination has content after failed execution") } } type failingBackend struct { *fake.Backend failPath string } func (b *failingBackend) WriteFile(ctx context.Context, path string, data []byte, opts storage.WriteOptions) (storage.Entry, error) { if path == b.failPath { return storage.Entry{}, fmt.Errorf("injected write failure") } return b.Backend.WriteFile(ctx, path, data, opts) } func (b *failingBackend) WriteFrom(ctx context.Context, path string, r io.Reader, opts storage.WriteOptions) (storage.Entry, error) { if path == b.failPath { return storage.Entry{}, fmt.Errorf("injected write failure") } return b.Backend.WriteFrom(ctx, path, r, opts) } func writeFakeSourceBundle(t *testing.T, backend *fake.Backend) bundle.Bundle { t.Helper() files := []struct { path string data string }{ {path: "report.md", data: "# Report\nSunny.\n"}, {path: "summary.txt", data: "Summary\n"}, } manifestFiles := make([]bundle.ManifestFile, 0, len(files)) for _, file := range files { if _, err := backend.WriteFile(context.Background(), file.path, []byte(file.data), storage.WriteOptions{}); err != nil { t.Fatalf("WriteFile() error = %v", err) } manifestFiles = append(manifestFiles, bundle.ManifestFile{Path: file.path, SHA256: bundle.FileDigest([]byte(file.data)), Size: int64(len(file.data))}) } manifest := bundle.Manifest{ SchemaVersion: 1, ID: "weather.daily.brentwood.2026-05-30", Created: time.Date(2026, 5, 30, 11, 10, 0, 0, time.UTC), Files: manifestFiles, } manifest.Digest = bundle.BundleDigest(manifest.Files) return bundle.Bundle{Manifest: manifest} }