package delivery import ( "context" "errors" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" "go.maximumdirect.net/releasekit/internal/artifact" internalbuild "go.maximumdirect.net/releasekit/internal/build" "go.maximumdirect.net/releasekit/internal/forge" ) func deliveryCleanupFixture(t *testing.T) (string, string) { t.Helper() repo := t.TempDir() files := map[string]string{ "go.mod": "module example.com/delivery\n\ngo 1.25.0\n", "main.go": "package main\nimport \"fmt\"\nfunc main() { fmt.Println(\"fixture v1.2.3\") }\n", "docs/releases/v1.2.3.md": "# Fixture v1.2.3\n\nNote.\n", } for name, contents := range files { path := filepath.Join(repo, filepath.FromSlash(name)) if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(contents), 0o600); err != nil { t.Fatal(err) } } for _, args := range [][]string{{"init", "-b", "main"}, {"config", "user.name", "Fixture"}, {"config", "user.email", "fixture@example.invalid"}, {"add", "."}, {"commit", "-m", "fixture"}, {"tag", "v1.2.3"}} { command := exec.Command("git", args...) command.Dir = repo if data, err := command.CombinedOutput(); err != nil { t.Fatalf("git %v: %v: %s", args, err, data) } } command := exec.Command("git", "rev-parse", "HEAD") command.Dir = repo commit, err := command.Output() if err != nil { t.Fatal(err) } return repo, strings.TrimSpace(string(commit)) } type cancelBuildOutput struct{ cancel context.CancelFunc } func (writer cancelBuildOutput) Write(p []byte) (int, error) { if strings.Contains(string(p), "building ") { writer.cancel() } return len(p), nil } func TestManagedDeliveryCleanupPreservesOutcome(t *testing.T) { for _, mode := range []string{"complete", "build failure", "cancellation"} { t.Run(mode, func(t *testing.T) { repo, commit := deliveryCleanupFixture(t) note := "# Fixture v1.2.3\n\nNote.\n" backend := &assetBackend{pageBackend: pageBackend{commit: commit, findResults: []pageFindResult{{release: forge.Release{Tag: "v1.2.3", Body: note, Handle: "release", URL: "https://example.invalid/release"}}}}, lists: []assetListResult{{}}} ctx, cancel := context.WithCancel(t.Context()) defer cancel() spec := internalbuild.Spec{MainPackage: ".", BinaryName: "fixture", Targets: []artifact.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}} if mode == "build failure" { spec.MainPackage = "./missing" } request := Request{RepositoryDir: repo, Product: "Fixture", Version: "v1.2.3", Backend: backend, Build: &spec} if mode == "cancellation" { request.Output = cancelBuildOutput{cancel} } cleanupErr := errors.New("cleanup failed") var removed string result, err := deliver(ctx, request, func(path string) error { removed = path _ = os.RemoveAll(path) return cleanupErr }) if !errors.Is(err, cleanupErr) || removed == "" || !strings.Contains(err.Error(), removed) || result.CandidateCommit != commit { t.Fatalf("delivery = %#v, cleanup = %q, error = %v", result, removed, err) } switch mode { case "complete": if result.State != Complete || len(result.Uploaded) != 2 || result.ReleaseURL == "" { t.Fatalf("completed delivery lost observations: %#v", result) } case "build failure": if result.State != NotAttempted || !strings.Contains(err.Error(), "build delivery assets") { t.Fatalf("build failure lost: %#v, %v", result, err) } case "cancellation": if result.State != NotAttempted || !errors.Is(err, context.Canceled) { t.Fatalf("cancellation lost: %#v, %v", result, err) } } }) } }