feat(zeta): manifest model, validation, package.lua emitter
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <[email protected]>
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user