package releasekit_test import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "runtime" "strings" "testing" releasekit "gitea.maximumdirect.net/eric/ReleaseKit" ) func TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput(t *testing.T) { repository := newTaggedDeliveryConsumer(t) temporaryRoot := t.TempDir() t.Setenv("TMPDIR", temporaryRoot) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() var progress strings.Builder result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, Output: &progress, }) if err != nil { t.Fatalf("Deliver() error = %v; result = %#v", err, result) } if result.State != releasekit.DeliveryComplete || result.CandidateCommit != remote.commit { t.Fatalf("delivery result = %#v", result) } if len(result.Uploaded) != 2 || len(remote.assets) != 2 { t.Fatalf("uploaded = %#v; hosted assets = %v", result.Uploaded, mapKeys(remote.assets)) } if !strings.Contains(progress.String(), "checking fixture version output") { t.Fatalf("build progress = %q", progress.String()) } if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 { t.Fatalf("managed build output remains: entries=%v error=%v", entries, err) } } func TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets(t *testing.T) { repository := newTaggedDeliveryConsumer(t) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() var progress strings.Builder result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{ {OS: "linux", Arch: "amd64"}, {OS: "linux", Arch: "arm64"}, }, }, Output: &progress, }) if err != nil { t.Fatalf("Deliver() error = %v; result = %#v", err, result) } if result.State != releasekit.DeliveryComplete || result.CandidateCommit != remote.commit { t.Fatalf("delivery result = %#v", result) } wantAssets := []string{ "fixture-v1.2.3-linux-amd64", "fixture-v1.2.3-linux-arm64", "SHA256SUMS", } if len(result.Uploaded) != len(wantAssets) || len(remote.assets) != len(wantAssets) { t.Fatalf("uploaded = %#v; hosted assets = %v", result.Uploaded, mapKeys(remote.assets)) } for _, name := range wantAssets { if _, ok := remote.assets[name]; !ok { t.Errorf("hosted assets omit %q: %v", name, mapKeys(remote.assets)) } } if !strings.Contains(progress.String(), "checking fixture version output") { t.Fatalf("build progress = %q", progress.String()) } } func TestDeliverPrebuiltArtifactsRemainCallerOwned(t *testing.T) { repository := newTaggedDeliveryConsumer(t) outputDir := filepath.Join(t.TempDir(), "caller-owned-assets") prebuilt, err := releasekit.Build(context.Background(), releasekit.BuildRequest{ RepositoryDir: repository, Version: "v1.2.3", Spec: releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, OutputDir: outputDir, }) if err != nil { t.Fatalf("Build() error = %v", err) } server, _ := newFacadeDeliveryServer(t, repository) defer server.Close() result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Prebuilt: &prebuilt, }) if err != nil || result.State != releasekit.DeliveryComplete { t.Fatalf("Deliver() = %#v, %v", result, err) } for _, artifact := range prebuilt.Artifacts { if _, err := os.Stat(artifact.Path); err != nil { t.Errorf("caller-owned artifact %q was removed: %v", artifact.Path, err) } } } func TestDeliverRejectsSameSizePrebuiltTamperingBeforeHostedWrites(t *testing.T) { repository := newTaggedDeliveryConsumer(t) prebuilt, err := releasekit.Build(context.Background(), releasekit.BuildRequest{ RepositoryDir: repository, Version: "v1.2.3", Spec: releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, OutputDir: filepath.Join(t.TempDir(), "caller-owned-assets"), }) if err != nil { t.Fatalf("Build() error = %v", err) } contents, err := os.ReadFile(prebuilt.Artifacts[0].Path) if err != nil { t.Fatal(err) } if len(contents) == 0 { t.Fatal("built artifact is empty") } contents[0] ^= 0xff if err := os.WriteFile(prebuilt.Artifacts[0].Path, contents, 0o600); err != nil { t.Fatal(err) } server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Prebuilt: &prebuilt, }) if err == nil || !strings.Contains(err.Error(), "verify build assets") || result.State != releasekit.DeliveryNotAttempted { t.Fatalf("Deliver() = %#v, %v; want local asset verification failure", result, err) } if remote.requests != 0 { t.Fatalf("hosted requests = %d, want none before local verification", remote.requests) } } func TestDeliverPageOnlySkipsBuildAndHostedAssets(t *testing.T) { for _, test := range []struct { name string build *releasekit.BuildSpec prebuilt *releasekit.BuildResult }{ {name: "no build"}, {name: "empty target set", build: &releasekit.BuildSpec{}}, {name: "empty prebuilt result", prebuilt: &releasekit.BuildResult{}}, } { t.Run(test.name, func(t *testing.T) { repository := newTaggedDeliveryConsumer(t) prebuilt := test.prebuilt if prebuilt != nil { built, err := releasekit.Build(context.Background(), releasekit.BuildRequest{RepositoryDir: repository, Version: "v1.2.3"}) if err != nil { t.Fatalf("Build() for page-only prebuilt identity = %#v, %v", built, err) } prebuilt = &built } temporaryRoot := t.TempDir() t.Setenv("TMPDIR", temporaryRoot) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() var progress strings.Builder result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: test.build, Prebuilt: prebuilt, Output: &progress, }) if err != nil || result.State != releasekit.DeliveryComplete { t.Fatalf("Deliver() = %#v, %v", result, err) } if len(remote.assets) != 0 || len(result.Uploaded) != 0 || len(result.Pending) != 0 { t.Fatalf("page-only delivery included assets: result=%#v assets=%v", result, mapKeys(remote.assets)) } if progress.Len() != 0 { t.Fatalf("page-only delivery emitted build progress: %q", progress.String()) } if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 { t.Fatalf("page-only delivery allocated temporary output: entries=%v error=%v", entries, err) } }) } } func TestDeliverUsesCommittedModuleIdentityForSeparatePages(t *testing.T) { repository := newTwoModuleDeliveryConsumer(t) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() wrongTag, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", ExpectedTag: "integrations/wrong/v1.2.3", Destination: facadeDeliveryDestination(server.URL), }) if err == nil || wrongTag.Tag != "v1.2.3" || remote.requests != 0 { t.Fatalf("delivery with inconsistent expected tag = %#v, %v; hosted requests %d", wrongTag, err, remote.requests) } rootResult, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", ExpectedTag: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), }) if err != nil || rootResult.State != releasekit.DeliveryComplete || rootResult.Tag != "v1.2.3" { t.Fatalf("root delivery = %#v, %v", rootResult, err) } nestedTag := "integrations/ckconfig/v1.2.3" remote.tag = nestedTag remote.noteBody = "# Fixture v1.2.3\n\nNested delivery fixture.\n" nestedResult, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, ModuleDir: "integrations/ckconfig", Product: "Fixture", Version: "v1.2.3", ExpectedTag: nestedTag, Destination: facadeDeliveryDestination(server.URL), }) if err != nil || nestedResult.State != releasekit.DeliveryComplete || nestedResult.Tag != nestedTag || nestedResult.ModulePath != "example.com/ckconfig" { t.Fatalf("nested delivery = %#v, %v", nestedResult, err) } if len(remote.pages) != 2 || !remote.pages["v1.2.3"] || !remote.pages[nestedTag] { t.Fatalf("created hosted pages = %#v", remote.pages) } } func TestDeliverRejectsPrebuiltArtifactsForAnotherModuleBeforeHostedRequests(t *testing.T) { repository := newTwoModuleDeliveryConsumer(t) prebuilt, err := releasekit.Build(context.Background(), releasekit.BuildRequest{ RepositoryDir: repository, Version: "v1.2.3", Spec: releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, OutputDir: filepath.Join(t.TempDir(), "root-artifacts"), }) if err != nil || !prebuilt.SourceVerified { t.Fatalf("root Build() = %#v, %v", prebuilt, err) } server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() remote.tag = "integrations/ckconfig/v1.2.3" result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, ModuleDir: "integrations/ckconfig", Product: "Fixture", Version: "v1.2.3", ExpectedTag: remote.tag, Destination: facadeDeliveryDestination(server.URL), Prebuilt: &prebuilt, }) if err == nil || !strings.Contains(err.Error(), "selected tagged module") || result.Tag != remote.tag { t.Fatalf("nested delivery with root artifacts = %#v, %v", result, err) } if remote.requests != 0 { t.Fatalf("hosted requests before prebuilt identity rejection = %d", remote.requests) } } func newTwoModuleDeliveryConsumer(t *testing.T) string { t.Helper() repository := newTaggedDeliveryConsumer(t) writeNestedBuildConsumer(t, repository, "integrations/ckconfig", "example.com/ckconfig", `package main import ( "fmt" "os" "gitea.maximumdirect.net/eric/ReleaseKit/buildinfo" ) func main() { if len(os.Args) == 2 && os.Args[1] == "version" { fmt.Println("fixture", buildinfo.Version()) } } `) writeBuildFixture(t, repository, "integrations/ckconfig/docs/releases/v1.2.3.md", "# Fixture v1.2.3\n\nNested delivery fixture.\n") buildGit(t, repository, "add", "--all") buildGit(t, repository, "commit", "-m", "add nested release module") buildGit(t, repository, "tag", "-f", "v1.2.3") buildGit(t, repository, "tag", "integrations/ckconfig/v1.2.3") return repository } func TestDeliverRejectsDirtyTaggedSourceBeforeBuildOrHostedWrites(t *testing.T) { repository := newTaggedDeliveryConsumer(t) writeBuildFixture(t, repository, "local-change.txt", "dirty source") temporaryRoot := t.TempDir() t.Setenv("TMPDIR", temporaryRoot) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() var progress strings.Builder result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, Output: &progress, }) if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.commit { t.Fatalf("Deliver() = %#v, %v; want preflight failure for known commit", result, err) } if progress.Len() != 0 { t.Fatalf("build started before source eligibility passed: %q", progress.String()) } if remote.requests != 0 { t.Fatalf("hosted requests = %d, want none", remote.requests) } } func TestDeliverRejectsCheckoutAwayFromTagBeforeBuildOrHostedWrites(t *testing.T) { repository := newTaggedDeliveryConsumer(t) tagCommit := buildGit(t, repository, "rev-parse", "HEAD") writeBuildFixture(t, repository, "later-change.txt", "new commit") buildGit(t, repository, "add", "--all") buildGit(t, repository, "commit", "-m", "advance checkout after release tag") temporaryRoot := t.TempDir() t.Setenv("TMPDIR", temporaryRoot) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() var progress strings.Builder result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, Output: &progress, }) if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit == tagCommit { t.Fatalf("Deliver() = %#v, %v; want tag/HEAD mismatch", result, err) } if progress.Len() != 0 { t.Fatalf("build started before tag/HEAD agreement: %q", progress.String()) } if remote.requests != 0 { t.Fatalf("hosted requests = %d, want none", remote.requests) } } func TestDeliverRejectsInvalidCommittedNoteBeforeBuildOrHostedWrites(t *testing.T) { repository := newTaggedDeliveryConsumerWithNote(t, "# Different Product v1.2.3\n\nWrong product.\n") temporaryRoot := t.TempDir() t.Setenv("TMPDIR", temporaryRoot) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() var progress strings.Builder result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, Output: &progress, }) if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.commit { t.Fatalf("Deliver() = %#v, %v; want committed-note preflight failure", result, err) } if progress.Len() != 0 { t.Fatalf("build started before committed-note validation: %q", progress.String()) } if remote.requests != 0 { t.Fatalf("hosted requests = %d, want none", remote.requests) } } func TestDeliverManagedBuildRejectsCheckoutMutationBeforeHostedWrites(t *testing.T) { repository := newTaggedDeliveryConsumer(t) temporaryRoot := t.TempDir() t.Setenv("TMPDIR", temporaryRoot) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() writer := &sourceChangeWriter{change: func() { source, err := os.ReadFile(filepath.Join(repository, "cmd/app/main.go")) if err != nil { t.Errorf("read application source: %v", err) return } writeBuildFixture(t, repository, "cmd/app/main.go", string(source)+"\n// changed during delivery build\n") }} result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, Output: writer, }) if err == nil || !strings.Contains(err.Error(), "build source") || result.State != releasekit.DeliveryNotAttempted { t.Fatalf("Deliver() = %#v, %v; want build-source eligibility failure", result, err) } if remote.requests != 0 { t.Fatalf("hosted requests = %d, want none", remote.requests) } if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 { t.Fatalf("ineligible managed build output remains: entries=%v error=%v", entries, err) } } func TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites(t *testing.T) { repository := newTaggedDeliveryConsumer(t) temporaryRoot := t.TempDir() t.Setenv("TMPDIR", temporaryRoot) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{ MainPackage: "./cmd/missing", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, }) if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.commit { t.Fatalf("Deliver() = %#v, %v; want managed build failure before hosted writes", result, err) } if remote.requests != 0 { t.Fatalf("hosted requests = %d, want none", remote.requests) } if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 { t.Fatalf("failed managed build output remains: entries=%v error=%v", entries, err) } } func TestDeliverManagedBuildCancellationCleansTemporaryOutput(t *testing.T) { repository := newTaggedDeliveryConsumer(t) temporaryRoot := t.TempDir() t.Setenv("TMPDIR", temporaryRoot) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() ctx, cancel := context.WithCancel(context.Background()) defer cancel() writer := deliveryCancelWriter{cancel: cancel} result, err := releasekit.Deliver(ctx, releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{ MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}, }, Output: writer, }) if !errors.Is(err, context.Canceled) || result.State != releasekit.DeliveryNotAttempted { t.Fatalf("Deliver() = %#v, %v; want cancellation before hosted writes", result, err) } if remote.requests != 0 { t.Fatalf("hosted requests = %d, want none", remote.requests) } if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 { t.Fatalf("canceled managed build output remains: entries=%v error=%v", entries, err) } } func TestDeliverRejectsBothBuildSourcesBeforeHostedWrites(t *testing.T) { repository := newTaggedDeliveryConsumer(t) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Build: &releasekit.BuildSpec{}, Prebuilt: &releasekit.BuildResult{}, }) if err == nil || result.State != releasekit.DeliveryNotAttempted || !strings.Contains(err.Error(), "both") { t.Fatalf("Deliver() = %#v, %v; want invalid build-source combination", result, err) } if remote.requests != 0 { t.Fatalf("hosted requests = %d, want none", remote.requests) } } func newTaggedDeliveryConsumer(t *testing.T) string { return newTaggedDeliveryConsumerWithNote(t, "# Fixture v1.2.3\n\nDelivery fixture.\n") } func newTaggedDeliveryConsumerWithNote(t *testing.T, note string) string { t.Helper() repository := newBuildConsumer(t, `package main import ( "fmt" "os" "gitea.maximumdirect.net/eric/ReleaseKit/buildinfo" ) func main() { if len(os.Args) == 2 && os.Args[1] == "version" { fmt.Println("fixture", buildinfo.Version()) } } `) writeBuildFixture(t, repository, "docs/releases/v1.2.3.md", note) buildGit(t, repository, "add", "--all") buildGit(t, repository, "commit", "-m", "add release note") buildGit(t, repository, "tag", "v1.2.3") return repository } func facadeDeliveryDestination(baseURL string) releasekit.GiteaDestination { return releasekit.GiteaDestination{BaseURL: baseURL, Owner: "owner", Repository: "example", Token: "fixture-token"} } type deliveryCancelWriter struct{ cancel context.CancelFunc } func (writer deliveryCancelWriter) Write(content []byte) (int, error) { writer.cancel() return len(content), nil } type facadeDeliveryServer struct { t *testing.T commit string tag string version string noteBody string pages map[string]bool requests int assets map[string][]byte url string } func newFacadeDeliveryServer(t *testing.T, repository string) (*httptest.Server, *facadeDeliveryServer) { t.Helper() state := &facadeDeliveryServer{t: t, commit: buildGit(t, repository, "rev-parse", "HEAD"), tag: "v1.2.3", version: "v1.2.3", noteBody: "# Fixture v1.2.3\n\nDelivery fixture.\n", pages: make(map[string]bool), assets: make(map[string][]byte)} server := httptest.NewServer(http.HandlerFunc(state.serveHTTP)) state.url = server.URL return server, state } func (state *facadeDeliveryServer) serveHTTP(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/") { state.requests++ if r.Header.Get("Authorization") != "token fixture-token" { state.t.Errorf("authorization = %q", r.Header.Get("Authorization")) } } switch r.URL.EscapedPath() { case "/api/v1/repos/owner/example/tags/" + url.PathEscape(state.tag): _, _ = fmt.Fprintf(w, `{"name":%q,"commit":{"sha":%q}}`, state.tag, state.commit) case "/api/v1/repos/owner/example/releases/tags/" + url.PathEscape(state.tag): if !state.pages[state.tag] { http.NotFound(w, r) return } state.writeRelease(w) case "/api/v1/repos/owner/example/releases": var payload struct { Tag string `json:"tag_name"` Target string `json:"target_commitish"` Name string `json:"name"` Body string `json:"body"` } if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { state.t.Errorf("decode release payload: %v", err) http.Error(w, "invalid payload", http.StatusBadRequest) return } if payload.Tag != state.tag || payload.Target != state.commit || payload.Name != "Fixture "+state.version { state.t.Errorf("release payload = %#v", payload) } state.pages[state.tag] = true state.writeRelease(w) case "/api/v1/repos/owner/example/releases/10/assets": if r.Method == http.MethodGet { list := make([]map[string]any, 0, len(state.assets)) for name, content := range state.assets { list = append(list, map[string]any{"id": len(list) + 1, "name": name, "size": len(content), "browser_download_url": state.url + "/files/" + name}) } _ = json.NewEncoder(w).Encode(list) return } name := r.URL.Query().Get("name") file, _, err := r.FormFile("attachment") if err != nil { state.t.Errorf("read upload %q: %v", name, err) http.Error(w, "invalid multipart upload", http.StatusBadRequest) return } content, err := io.ReadAll(file) _ = file.Close() if err != nil { state.t.Errorf("read upload %q: %v", name, err) } state.assets[name] = content w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(map[string]any{"id": len(state.assets), "name": name, "size": len(content)}) default: if strings.HasPrefix(r.URL.Path, "/files/") { name := strings.TrimPrefix(r.URL.Path, "/files/") content, ok := state.assets[name] if !ok { http.NotFound(w, r) return } _, _ = w.Write(content) return } state.t.Errorf("unexpected delivery request %s %s", r.Method, r.URL) http.NotFound(w, r) } } func (state *facadeDeliveryServer) writeRelease(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "id": 10, "html_url": state.url + "/releases/10", "tag_name": state.tag, "target_commitish": state.commit, "name": "Fixture " + state.version, "body": state.noteBody, "draft": false, "prerelease": false, }) } func mapKeys(values map[string][]byte) []string { keys := make([]string, 0, len(values)) for key := range values { keys = append(keys, key) } return keys }