Files
zero/internal/release/release_test.go
Anandan ce8dec087d Replace npm wrapper with Node entrypoint (#88)
* Replace npm wrapper with Node entrypoint

* Fix npm wrapper test portability
2026-06-06 13:21:57 +05:30

431 lines
13 KiB
Go

package release
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/hex"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestSHA256FileHashesArchiveBytes(t *testing.T) {
dir := t.TempDir()
archivePath := filepath.Join(dir, "zero-v0.1.0-linux-x64.tar.gz")
archiveBytes := []byte("zero archive bytes")
if err := os.WriteFile(archivePath, archiveBytes, 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
sum := sha256.Sum256(archiveBytes)
expected := hex.EncodeToString(sum[:])
got, err := SHA256File(archivePath)
if err != nil {
t.Fatalf("SHA256File returned error: %v", err)
}
if got != expected {
t.Fatalf("SHA256File = %q, want %q", got, expected)
}
}
func TestWriteAndVerifyReleaseChecksums(t *testing.T) {
dir := t.TempDir()
archiveName := "zero-v0.1.0-linux-x64.tar.gz"
archivePath := filepath.Join(dir, archiveName)
if err := os.WriteFile(archivePath, []byte("zero archive bytes"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
written, err := WriteSHA256Checksum(archivePath)
if err != nil {
t.Fatalf("WriteSHA256Checksum returned error: %v", err)
}
checksumBytes, err := os.ReadFile(written.ChecksumPath)
if err != nil {
t.Fatalf("ReadFile checksum: %v", err)
}
if !strings.HasSuffix(string(checksumBytes), " "+archiveName+"\n") {
t.Fatalf("checksum text = %q, want shasum-compatible archive name", string(checksumBytes))
}
verifiedFile, err := VerifySHA256Checksum(written.ChecksumPath)
if err != nil {
t.Fatalf("VerifySHA256Checksum returned error: %v", err)
}
if verifiedFile.ArchiveName != archiveName || verifiedFile.ExpectedChecksum != verifiedFile.ActualChecksum {
t.Fatalf("verified checksum = %#v", verifiedFile)
}
verifiedRelease, err := VerifyReleaseChecksums(VerifyOptions{ReleaseDir: dir})
if err != nil {
t.Fatalf("VerifyReleaseChecksums returned error: %v", err)
}
if len(verifiedRelease) != 1 || verifiedRelease[0].ArchiveName != archiveName {
t.Fatalf("verified release = %#v, want %s", verifiedRelease, archiveName)
}
}
func TestChecksumParsingRejectsMalformedAndUnsafeNames(t *testing.T) {
if _, err := ParseSHA256Checksum("not a checksum"); err == nil || !strings.Contains(err.Error(), "checksum file must contain") {
t.Fatalf("ParseSHA256Checksum malformed error = %v", err)
}
if _, err := FormatSHA256Checksum("abc", "zero.tar.gz"); err == nil || !strings.Contains(err.Error(), "64 hexadecimal") {
t.Fatalf("FormatSHA256Checksum invalid checksum error = %v", err)
}
if _, err := ParseSHA256Checksum(strings.Repeat("a", 64) + " ../zero.tar.gz\n"); err == nil || !strings.Contains(err.Error(), "same-directory") {
t.Fatalf("ParseSHA256Checksum unsafe path error = %v", err)
}
if _, err := ParseSHA256Checksum(strings.Repeat("a", 64) + " zero.tar.gz\n" + strings.Repeat("b", 64) + " other.tar.gz\n"); err == nil || !strings.Contains(err.Error(), "exactly one checksum line") {
t.Fatalf("ParseSHA256Checksum multi-line error = %v", err)
}
}
func TestVerifyChecksumDetectsArchiveChanges(t *testing.T) {
dir := t.TempDir()
archivePath := filepath.Join(dir, "zero-v0.1.0-linux-x64.tar.gz")
if err := os.WriteFile(archivePath, []byte("original bytes"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
written, err := WriteSHA256Checksum(archivePath)
if err != nil {
t.Fatalf("WriteSHA256Checksum returned error: %v", err)
}
if err := os.WriteFile(archivePath, []byte("changed bytes"), 0o644); err != nil {
t.Fatalf("WriteFile changed archive: %v", err)
}
_, err = VerifySHA256Checksum(written.ChecksumPath)
if err == nil || !strings.Contains(err.Error(), "checksum mismatch") {
t.Fatalf("VerifySHA256Checksum error = %v, want mismatch", err)
}
}
func TestVerifyReleaseChecksumsRequiresMatchingFiles(t *testing.T) {
dir := t.TempDir()
archivePath := filepath.Join(dir, "zero-v0.1.0-linux-x64.tar.gz")
if err := os.WriteFile(archivePath, []byte("archive bytes"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
_, err := VerifyReleaseChecksums(VerifyOptions{ReleaseDir: dir})
if err == nil || !strings.Contains(err.Error(), "missing checksum file") {
t.Fatalf("VerifyReleaseChecksums error = %v, want missing checksum", err)
}
if _, err := WriteSHA256Checksum(archivePath); err != nil {
t.Fatalf("WriteSHA256Checksum returned error: %v", err)
}
strayChecksum := filepath.Join(dir, "zero-v0.1.0-macos-arm64.tar.gz.sha256")
if err := os.WriteFile(strayChecksum, []byte(strings.Repeat("a", 64)+" zero-v0.1.0-macos-arm64.tar.gz\n"), 0o644); err != nil {
t.Fatalf("WriteFile stray checksum: %v", err)
}
_, err = VerifyReleaseChecksums(VerifyOptions{ReleaseDir: dir})
if err == nil || !strings.Contains(err.Error(), "unexpected checksum file") {
t.Fatalf("VerifyReleaseChecksums error = %v, want unexpected checksum", err)
}
}
func TestVerifyReleaseChecksumsRejectsEmptyReleaseDir(t *testing.T) {
_, err := VerifyReleaseChecksums(VerifyOptions{ReleaseDir: t.TempDir()})
if err == nil || !strings.Contains(err.Error(), "no release archives found") {
t.Fatalf("VerifyReleaseChecksums error = %v, want no archives", err)
}
}
func TestReleaseArchiveNamesMatchInstallerContracts(t *testing.T) {
tests := []struct {
name string
version string
goos string
goarch string
packageName string
archiveName string
}{
{
name: "linux amd64",
version: "0.1.0",
goos: "linux",
goarch: "amd64",
packageName: "zero-v0.1.0-linux-x64",
archiveName: "zero-v0.1.0-linux-x64.tar.gz",
},
{
name: "macos arm64",
version: "0.1.0",
goos: "darwin",
goarch: "arm64",
packageName: "zero-v0.1.0-macos-arm64",
archiveName: "zero-v0.1.0-macos-arm64.tar.gz",
},
{
name: "windows amd64",
version: "0.1.0",
goos: "windows",
goarch: "amd64",
packageName: "zero-v0.1.0-windows-x64",
archiveName: "zero-v0.1.0-windows-x64.zip",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
packageName, err := ReleasePackageName(tt.version, tt.goos, tt.goarch)
if err != nil {
t.Fatalf("ReleasePackageName returned error: %v", err)
}
archiveName, err := ReleaseArchiveName(tt.version, tt.goos, tt.goarch)
if err != nil {
t.Fatalf("ReleaseArchiveName returned error: %v", err)
}
if packageName != tt.packageName || archiveName != tt.archiveName {
t.Fatalf("package/archive = %q/%q, want %q/%q", packageName, archiveName, tt.packageName, tt.archiveName)
}
})
}
}
func TestBuildHelpersMatchScriptContracts(t *testing.T) {
root := t.TempDir()
mustWriteFile(t, filepath.Join(root, "package.json"), `{"version":"0.1.0"}`)
version, err := PackageVersion(root)
if err != nil {
t.Fatalf("PackageVersion returned error: %v", err)
}
if version != "0.1.0" {
t.Fatalf("PackageVersion = %q, want 0.1.0", version)
}
if got := DefaultBuildOutput(root, "windows"); got != filepath.Join(root, "zero.exe") {
t.Fatalf("DefaultBuildOutput(windows) = %q", got)
}
if got := DefaultBuildOutput(root, "linux"); got != filepath.Join(root, "zero") {
t.Fatalf("DefaultBuildOutput(linux) = %q", got)
}
if got := BuildLdflags(version); !strings.Contains(got, "-X github.com/Gitlawb/zero/internal/cli.version=0.1.0") {
t.Fatalf("BuildLdflags = %q", got)
}
}
func TestSmokeRejectsMissingDefaultArtifact(t *testing.T) {
root := t.TempDir()
mustWriteFile(t, filepath.Join(root, "package.json"), `{"version":"0.1.0"}`)
_, err := Smoke(context.Background(), SmokeOptions{RootDir: root, GOOS: "linux"})
if err == nil || !strings.Contains(err.Error(), "build artifact not found: zero") {
t.Fatalf("Smoke error = %v, want missing artifact", err)
}
}
func TestPackageRejectsCrossTargetBecauseItSmokesTheBinary(t *testing.T) {
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, "package.json"), []byte(`{"version":"0.1.0"}`), 0o644); err != nil {
t.Fatalf("WriteFile package.json: %v", err)
}
goos := "linux"
if runtime.GOOS == "linux" {
goos = "darwin"
}
_, err := Package(context.Background(), PackageOptions{
RootDir: root,
GOOS: goos,
GOARCH: runtime.GOARCH,
})
if err == nil || !strings.Contains(err.Error(), "target must match host platform") {
t.Fatalf("Package error = %v, want host-target mismatch", err)
}
}
func TestResolvePackageDirsRejectsDangerousDeleteTargets(t *testing.T) {
root := t.TempDir()
home := t.TempDir()
tests := []struct {
name string
releaseDir string
stagingDir string
want string
}{
{
name: "repo root release dir",
releaseDir: ".",
stagingDir: "dist/package",
want: "inside",
},
{
name: "dist root release dir",
releaseDir: "dist",
stagingDir: "dist/package",
want: "inside",
},
{
name: "outside absolute release dir",
releaseDir: home,
stagingDir: "dist/package",
want: "inside",
},
{
name: "same release and staging dir",
releaseDir: "dist/release",
stagingDir: "dist/release",
want: "overlap",
},
{
name: "staging contains release dir",
releaseDir: "dist/package/release",
stagingDir: "dist/package",
want: "overlap",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, err := resolvePackageDirs(root, tt.releaseDir, tt.stagingDir)
if err == nil || !strings.Contains(err.Error(), tt.want) {
t.Fatalf("resolvePackageDirs error = %v, want %q", err, tt.want)
}
})
}
}
func TestPackageRejectsDangerousDirsBeforeDeleting(t *testing.T) {
root := t.TempDir()
mustWriteFile(t, filepath.Join(root, "package.json"), `{"version":"0.1.0"}`)
markerPath := filepath.Join(root, "DO_NOT_DELETE")
mustWriteFile(t, markerPath, "marker")
_, err := Package(context.Background(), PackageOptions{
RootDir: root,
ReleaseDir: ".",
})
if err == nil || !strings.Contains(err.Error(), "inside") {
t.Fatalf("Package error = %v, want unsafe path rejection", err)
}
if _, statErr := os.Stat(markerPath); statErr != nil {
t.Fatalf("Package removed marker before rejecting unsafe dir: %v", statErr)
}
}
func TestResolvePackageDirsAcceptsDistSubdirs(t *testing.T) {
root := t.TempDir()
releaseDir, stagingDir, err := resolvePackageDirs(root, "dist/custom-release", "dist/custom-package")
if err != nil {
t.Fatalf("resolvePackageDirs returned error: %v", err)
}
if releaseDir != filepath.Join(root, "dist", "custom-release") || stagingDir != filepath.Join(root, "dist", "custom-package") {
t.Fatalf("release/staging dirs = %q/%q", releaseDir, stagingDir)
}
}
func TestCreateArchivesWithRootPackageFiles(t *testing.T) {
t.Run("tar gz", func(t *testing.T) {
stagingDir := packageStagingFixture(t, "zero")
archivePath := filepath.Join(t.TempDir(), "zero-v0.1.0-linux-x64.tar.gz")
if err := createArchive(stagingDir, archivePath, "linux"); err != nil {
t.Fatalf("createArchive returned error: %v", err)
}
names := tarArchiveNames(t, archivePath)
for _, want := range []string{"zero", "README.md", "bin/zero.js", "VERSION"} {
if !names[want] {
t.Fatalf("tar archive missing %s: %#v", want, names)
}
}
})
t.Run("zip", func(t *testing.T) {
stagingDir := packageStagingFixture(t, "zero.exe")
archivePath := filepath.Join(t.TempDir(), "zero-v0.1.0-windows-x64.zip")
if err := createArchive(stagingDir, archivePath, "windows"); err != nil {
t.Fatalf("createArchive returned error: %v", err)
}
names := zipArchiveNames(t, archivePath)
for _, want := range []string{"zero.exe", "README.md", "bin/zero.js", "VERSION"} {
if !names[want] {
t.Fatalf("zip archive missing %s: %#v", want, names)
}
}
})
}
func packageStagingFixture(t *testing.T, binaryName string) string {
t.Helper()
dir := t.TempDir()
files := map[string]string{
binaryName: "binary",
"README.md": "readme",
"bin/zero.js": "wrapper",
"VERSION": "0.1.0\n",
}
for name, content := range files {
path := filepath.Join(dir, name)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("WriteFile(%s): %v", name, err)
}
}
return dir
}
func mustWriteFile(t *testing.T, path string, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("MkdirAll(%s): %v", path, err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("WriteFile(%s): %v", path, err)
}
}
func tarArchiveNames(t *testing.T, archivePath string) map[string]bool {
t.Helper()
file, err := os.Open(archivePath)
if err != nil {
t.Fatalf("Open tar archive: %v", err)
}
defer func() {
_ = file.Close()
}()
gzipReader, err := gzip.NewReader(file)
if err != nil {
t.Fatalf("NewReader gzip: %v", err)
}
defer func() {
_ = gzipReader.Close()
}()
reader := tar.NewReader(gzipReader)
names := map[string]bool{}
for {
header, err := reader.Next()
if err != nil {
if err == io.EOF {
break
}
t.Fatalf("tar Next: %v", err)
}
names[header.Name] = true
}
return names
}
func zipArchiveNames(t *testing.T, archivePath string) map[string]bool {
t.Helper()
reader, err := zip.OpenReader(archivePath)
if err != nil {
t.Fatalf("OpenReader zip: %v", err)
}
defer func() {
_ = reader.Close()
}()
names := map[string]bool{}
for _, file := range reader.File {
names[file.Name] = true
}
return names
}