Harden release validation

This commit is contained in:
2026-08-30 20:36:23 +00:00
parent f8fa0a2623
commit 98139f7e8b
4 changed files with 73 additions and 31 deletions

View File

@@ -11,6 +11,45 @@ import (
"testing"
)
func TestReleaseVersionValidationIsExact(t *testing.T) {
repoRoot := releaseCheckRepoRoot(t)
library := filepath.Join(repoRoot, "scripts", "release-lib.sh")
workingDirectory := t.TempDir()
if err := os.WriteFile(filepath.Join(workingDirectory, "2"), nil, 0o644); err != nil {
t.Fatal(err)
}
for _, test := range []struct {
version string
valid bool
}{
{version: "v0.0.0", valid: true},
{version: "v1.2.3", valid: true},
{version: "v10.200.3000", valid: true},
{version: "1.2.3"},
{version: "v01.2.3"},
{version: "v1.02.3"},
{version: "v1.2.03"},
{version: "v1.2.3."},
{version: "v1.2.3-rc.1"},
{version: "v1.2.3+build"},
{version: "v1.*.3"},
{version: "v1.2.3\nv4.5.6"},
} {
t.Run(test.version, func(t *testing.T) {
command := exec.Command("sh", "-c", `. "$1"; narratio_release_validate_version "$2"`, "release-version-test", library, test.version)
command.Dir = workingDirectory
err := command.Run()
if test.valid && err != nil {
t.Fatalf("valid version %q rejected: %v", test.version, err)
}
if !test.valid && err == nil {
t.Fatalf("invalid version %q accepted", test.version)
}
})
}
}
func TestAssetBuilderRejectsUnsafeDestinations(t *testing.T) {
repoRoot := releaseCheckRepoRoot(t)
builder := filepath.Join(repoRoot, "scripts", "build-release-assets.sh")
@@ -62,10 +101,15 @@ func TestAssetBuilderBuildsNamedAssetsAndChecksEmbeddedVersion(t *testing.T) {
binDir := t.TempDir()
writeFakeGo(t, filepath.Join(binDir, "go"))
outputDir := filepath.Join(t.TempDir(), "assets")
goLog := filepath.Join(t.TempDir(), "go.log")
command := exec.Command("sh", builder, "v1.2.3", outputDir)
command.Dir = t.TempDir()
command.Env = append(os.Environ(), "PATH="+binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
command.Env = append(os.Environ(),
"PATH="+binDir+string(os.PathListSeparator)+os.Getenv("PATH"),
"GOWORK=/caller/controlled/go.work",
"NARRATIO_RELEASE_ASSET_GO_LOG="+goLog,
)
output, err := command.CombinedOutput()
if err != nil {
t.Fatalf("asset builder error = %v\n%s", err, output)
@@ -99,6 +143,15 @@ func TestAssetBuilderBuildsNamedAssetsAndChecksEmbeddedVersion(t *testing.T) {
if got := versionOutput.String(); got != "narratio v1.2.3\n" {
t.Fatalf("embedded version output = %q", got)
}
goCommands, err := os.ReadFile(goLog)
if err != nil {
t.Fatal(err)
}
for _, line := range strings.Split(strings.TrimSpace(string(goCommands)), "\n") {
if !strings.HasPrefix(line, "off ") {
t.Fatalf("asset builder Go command did not receive GOWORK=off: %q", line)
}
}
}
func TestCandidateCheckerRejectsMalformedVersionAndReleaseNotes(t *testing.T) {
@@ -233,6 +286,10 @@ func writeFakeGo(t *testing.T, path string) {
const fake = `#!/bin/sh
set -eu
if [ -n "${NARRATIO_RELEASE_ASSET_GO_LOG:-}" ]; then
printf '%s %s\n' "${GOWORK:-}" "$*" >> "$NARRATIO_RELEASE_ASSET_GO_LOG"
fi
if [ "$1" = env ]; then
case $2 in
GOOS) printf '%s\n' linux ;;