package releasekit_test import ( "errors" "os" "os/exec" "path/filepath" "strings" "testing" releasekit "gitea.maximumdirect.net/eric/ReleaseKit" ) func TestSourceOperationsRejectUnavailableNoteWithoutTransport(t *testing.T) { source := newPublishFixture(t) repository := newFilteredSparseCheckout(t, source.work, "/go.mod", "/fixture_test.go") commit := gitText(t, repository, "rev-parse", "HEAD") noteObject := gitText(t, repository, "rev-parse", "HEAD:docs/releases/v1.2.3.md") before := gitText(t, repository, "cat-file", "--batch-all-objects", "--batch-check=%(objectname)") if strings.Contains(before, noteObject) { t.Fatal("fixture already contains the release note blob") } buildGit(t, repository, "tag", "v1.2.3") attempts := forbidInspectionTransport(t, repository) checked, checkErr := releasekit.Check(t.Context(), releasekit.ReleaseRequest{ RepositoryDir: repository, Product: "Sample App", Version: "v1.2.3", }) server, remote := newFacadeDeliveryServer(t, repository) defer server.Close() delivered, deliveryErr := releasekit.Deliver(t.Context(), releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Sample App", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), }) for _, err := range []error{checkErr, deliveryErr} { var exitError *exec.ExitError if !errors.As(err, &exitError) || !strings.Contains(err.Error(), "docs/releases/v1.2.3.md") { t.Errorf("note inspection error = %v; want note path and Git cause", err) } } if checked.Commit != commit || checked.Tag != "v1.2.3" || delivered.CandidateCommit != commit || delivered.Tag != checked.Tag || delivered.State != releasekit.DeliveryNotAttempted { t.Fatalf("missing-note evidence: check=%#v, delivery=%#v", checked, delivered) } if remote.requests != 0 { t.Fatalf("missing note caused %d hosted requests", remote.requests) } assertInspectionStayedLocal(t, repository, attempts, before) } func TestScopedValidationReadsOnlyRequiredBlobsInPartialClone(t *testing.T) { source := newPublishFixture(t) addNestedReleaseModule(t, source.work, "nested", "example.com/nested", "v1.2.3") writeFile(t, source.work, "nested/NOTICE.txt", "nested notice\n") writeFile(t, source.work, "nested/README.md", "# Nested guide\n") writeFile(t, source.work, "nested/go.work", "go 1.26.0\nuse .\n") writeFile(t, source.work, "docs/links.md", "# Links\n\n[Guide](../nested/README.md#nested-guide)\n") gitRun(t, source.work, "add", ".") gitRun(t, source.work, "commit", "-m", "Add independently scoped module") repository := newFilteredSparseCheckout(t, source.work, "/go.mod", "/fixture_test.go", "/race_fixture_test.go", "/source.txt", "/docs/") before := gitText(t, repository, "cat-file", "--batch-all-objects", "--batch-check=%(objectname)") for _, name := range []string{"nested/fixture_test.go", "nested/NOTICE.txt", "nested/README.md"} { object := gitText(t, repository, "rev-parse", "HEAD:"+name) if strings.Contains(before, object) { t.Fatalf("fixture already contains excluded blob %q", name) } } attempts := forbidInspectionTransport(t, repository) for _, tc := range []struct { name string selection releasekit.ValidationSpec wantCheck releasekit.ValidationCheck wantPath string }{ {name: "formatting", selection: releasekit.ValidationSpec{GoFormat: true, SharedFiles: []string{}}}, {name: "file sizes", selection: releasekit.ValidationSpec{FileSize: &releasekit.FileSizeValidationSpec{}, SharedFiles: []string{}}}, {name: "workspace metadata", selection: releasekit.ValidationSpec{ModuleDistribution: releasekit.ModuleDistributionValidationSpec{WorkspaceFiles: true}, SharedFiles: []string{}}}, {name: "explicit shared metadata", selection: releasekit.ValidationSpec{SharedFiles: []string{"nested/NOTICE.txt"}}}, {name: "excluded shared size", selection: releasekit.ValidationSpec{FileSize: &releasekit.FileSizeValidationSpec{Exclusions: []string{"nested/NOTICE.txt"}}, SharedFiles: []string{"nested/NOTICE.txt"}}}, {name: "required shared size", selection: releasekit.ValidationSpec{FileSize: &releasekit.FileSizeValidationSpec{}, SharedFiles: []string{"nested/NOTICE.txt"}}, wantCheck: "file-size", wantPath: "nested/NOTICE.txt"}, {name: "required shared source", selection: releasekit.ValidationSpec{GoFormat: true, SharedFiles: []string{"nested/fixture_test.go"}}, wantCheck: "go-format", wantPath: "nested/fixture_test.go"}, {name: "required reference", selection: releasekit.ValidationSpec{Markdown: &releasekit.MarkdownValidationSpec{}, SharedFiles: []string{}}, wantCheck: "markdown-links", wantPath: "nested/README.md"}, } { t.Run(tc.name, func(t *testing.T) { _, err := releasekit.Check(t.Context(), releasekit.ReleaseRequest{ RepositoryDir: repository, Product: "Sample App", Version: "v1.2.3", Validation: tc.selection, }) if tc.wantCheck == "" { if err != nil { t.Fatalf("scoped validation failed: %v", err) } } else { var failure *releasekit.ValidationError if !errors.As(err, &failure) || failure.Check != tc.wantCheck || !strings.Contains(err.Error(), tc.wantPath) { t.Fatalf("validation error = %v; want %s inspection of %q", err, tc.wantCheck, tc.wantPath) } } assertInspectionStayedLocal(t, repository, attempts, before) }) } } func newFilteredSparseCheckout(t *testing.T, source string, paths ...string) string { t.Helper() gitRun(t, source, "config", "uploadpack.allowFilter", "true") repository := filepath.Join(t.TempDir(), "partial") gitRun(t, source, "clone", "--filter=blob:none", "--no-checkout", "--no-tags", "file://"+filepath.ToSlash(source), repository) gitRun(t, repository, "sparse-checkout", "init", "--no-cone") gitRun(t, repository, append([]string{"sparse-checkout", "set"}, paths...)...) gitRun(t, repository, "checkout", "main") if status := gitText(t, repository, "status", "--porcelain"); status != "" { t.Fatalf("sparse checkout is dirty: %s", status) } return repository } func forbidInspectionTransport(t *testing.T, repository string) string { t.Helper() gitRun(t, repository, "config", "remote.origin.url", "ssh://releasekit-test.invalid/source") directory := t.TempDir() transport := filepath.Join(directory, "ssh") attempts := filepath.Join(directory, "attempts") if err := os.WriteFile(transport, []byte("#!/bin/sh\nprintf 'remote accessed\\n' >> \"$RELEASEKIT_GIT_ATTEMPTS\"\nexit 1\n"), 0o755); err != nil { t.Fatal(err) } t.Setenv("GIT_SSH_COMMAND", transport) t.Setenv("RELEASEKIT_GIT_ATTEMPTS", attempts) t.Setenv("GIT_NO_LAZY_FETCH", "0") t.Setenv("GIT_ALLOW_PROTOCOL", "ssh") return attempts } func assertInspectionStayedLocal(t *testing.T, repository, attempts, before string) { t.Helper() if data, err := os.ReadFile(attempts); !errors.Is(err, os.ErrNotExist) { t.Fatalf("inspection invoked remote transport: %q, error = %v", data, err) } if after := gitText(t, repository, "cat-file", "--batch-all-objects", "--batch-check=%(objectname)"); after != before { t.Fatal("inspection changed the local object database") } }