package zeta import ( "os" "path/filepath" "strings" "testing" ) // validManifest returns a fully valid archive-mode manifest that every // table case can copy-and-mutate. func validManifest() *Manifest { return &Manifest{ Name: "hello", Version: "1.0", Summary: "A tiny demonstration program", URL: "https://example.org/packages/hello/hello-1.0.tar.gz", SHA256: "731887527d1a72c57d1e64bad85cc341a4285354e773b2cd277420b744df2943", Archive: &Archive{Strip: 1}, } } func TestManifest(t *testing.T) { tests := []struct { name string mutate func(m *Manifest) wantErr bool wantErrSub string }{ { name: "valid archive manifest", mutate: func(m *Manifest) {}, }, { name: "missing version", mutate: func(m *Manifest) { m.Version = "" }, wantErr: true, wantErrSub: "missing version", }, { name: "bad sha256", mutate: func(m *Manifest) { m.SHA256 = "zzz" }, wantErr: true, wantErrSub: "64 hex", }, { name: "archive without url", mutate: func(m *Manifest) { m.URL = "" }, wantErr: true, wantErrSub: "archive mode requires a url", }, { name: "remote url without sha256", mutate: func(m *Manifest) { m.SHA256 = "" }, wantErr: true, wantErrSub: "must declare sha256 for a remote url", }, { name: "self dep", mutate: func(m *Manifest) { m.Deps = []string{"hello>=1.0"} }, wantErr: true, wantErrSub: "depends on itself", }, { name: "dep with bad operator", mutate: func(m *Manifest) { m.Deps = []string{"pcre2<<10.42"} }, wantErr: true, wantErrSub: "bad dependency constraint", }, { name: "dep without operator", mutate: func(m *Manifest) { m.Deps = []string{"pcre2 10.42"} }, wantErr: true, wantErrSub: "bad dependency constraint", }, { name: "dotdot in files", mutate: func(m *Manifest) { m.Files = []string{"../evil"} }, wantErr: true, wantErrSub: "escapes the root", }, { name: "empty files entry", mutate: func(m *Manifest) { m.Files = []string{""} }, wantErr: true, wantErrSub: "empty files entry", }, { name: "duplicate files entry", mutate: func(m *Manifest) { m.Files = []string{"usr/bin/hello", "usr/bin/hello"} }, wantErr: true, wantErrSub: "twice", }, { name: "negative strip", mutate: func(m *Manifest) { m.Archive = &Archive{Strip: -1} }, wantErr: true, wantErrSub: "non-negative integer", }, { name: "leading dot name", mutate: func(m *Manifest) { m.Name = ".hidden" }, wantErr: true, wantErrSub: "invalid package name", }, { name: "slash in name", mutate: func(m *Manifest) { m.Name = "a/b" }, wantErr: true, wantErrSub: "invalid package name", }, { name: "unsafe local url", mutate: func(m *Manifest) { m.URL = "/tmp/foo bar" m.SHA256 = "" }, wantErr: true, wantErrSub: "looks unsafe", }, { name: "valid deps with constraints", mutate: func(m *Manifest) { m.Deps = []string{"pcre2>=10.42", "libffi", "libz=1.2.13", "foo == 1.0"} }, }, { name: "local url without sha256 ok", mutate: func(m *Manifest) { m.URL = "/var/cache/zeta/hello-1.0.tar.gz" m.SHA256 = "" }, }, { name: "valid files entries cleaned", mutate: func(m *Manifest) { m.Files = []string{"usr/bin/hello", "/usr/share/doc/", "./etc/zeta"} }, }, { name: "empty summary ok", mutate: func(m *Manifest) { m.Summary = "" }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := validManifest() tt.mutate(m) err := m.Validate() if tt.wantErr { if err == nil { t.Fatalf("Validate() = nil, want error containing %q", tt.wantErrSub) } if tt.wantErrSub != "" && !strings.Contains(err.Error(), tt.wantErrSub) { t.Fatalf("Validate() error = %q, want it to contain %q", err, tt.wantErrSub) } return } if err != nil { t.Fatalf("Validate() = %v, want nil", err) } }) } } func TestManifestSHA256Lowered(t *testing.T) { m := validManifest() m.SHA256 = strings.ToUpper(m.SHA256) if err := m.Validate(); err != nil { t.Fatalf("Validate() = %v, want nil (uppercase hex accepted)", err) } if m.SHA256 != strings.ToLower(m.SHA256) { t.Fatalf("SHA256 = %q, want it lowercased", m.SHA256) } } func TestManifestEmitFormat(t *testing.T) { m := &Manifest{ Name: "tool", Version: "2.0", Summary: "say \"hi\"\nnewline", URL: "https://example.org/packages/tool/tool-2.0.tar.gz", SHA256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Deps: []string{"libz>=1.2.13", "pcre2"}, Archive: &Archive{Strip: 1}, } want := `return { name = "tool", version = "2.0", summary = "say \"hi\"\nnewline", url = "https://example.org/packages/tool/tool-2.0.tar.gz", sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", deps = { "libz>=1.2.13", "pcre2" }, archive = { strip = 1 }, } ` got, err := m.Emit() if err != nil { t.Fatalf("Emit() error = %v", err) } if got != want { t.Fatalf("Emit() output mismatch:\n--- got ---\n%s\n--- want ---\n%s", got, want) } } func TestManifestEmitInvalid(t *testing.T) { m := validManifest() m.Version = "" if _, err := m.Emit(); err == nil { t.Fatal("Emit() = nil error, want validation error for missing version") } } func TestManifestGolden(t *testing.T) { m := &Manifest{ Name: "hello", Version: "1.0", Summary: "A tiny demonstration program", URL: "https://example.org/packages/hello/hello-1.0.tar.gz", SHA256: "731887527d1a72c57d1e64bad85cc341a4285354e773b2cd277420b744df2943", Deps: []string{}, } got, err := m.Emit() if err != nil { t.Fatalf("Emit() error = %v", err) } goldenPath := filepath.Join("..", "..", "testdata", "golden", "hello.package.lua") if os.Getenv("UPDATE_GOLDEN") == "1" { if err := os.MkdirAll(filepath.Dir(goldenPath), 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } if err := os.WriteFile(goldenPath, []byte(got), 0o644); err != nil { t.Fatalf("WriteFile golden: %v", err) } t.Logf("wrote golden %s", goldenPath) return } want, err := os.ReadFile(goldenPath) if err != nil { t.Fatalf("ReadFile golden %s: %v (run UPDATE_GOLDEN=1 to regenerate)", goldenPath, err) } if got != string(want) { t.Fatalf("Emit() does not match golden %s:\n--- got ---\n%s\n--- want ---\n%s", goldenPath, got, want) } }