internal/deps/manifest_test.go

v1.27.0
gitbay/internal/deps/manifest_test.go history · blame · raw

176 lines · 4201 bytes

  1package deps
  2
  3import (
  4	"fmt"
  5	"os"
  6	"testing"
  7)
  8
  9// tree serves manifests from a map, standing in for a git tree.
 10func tree(files map[string]string) ReadFile {
 11	return func(path string) ([]byte, error) {
 12		body, ok := files[path]
 13		if !ok {
 14			return nil, os.ErrNotExist
 15		}
 16		return []byte(body), nil
 17	}
 18}
 19
 20func found(t *testing.T, deps []Dep) map[string]string {
 21	t.Helper()
 22	m := map[string]string{}
 23	for _, d := range deps {
 24		m[d.Ecosystem+":"+d.Name] = d.Current
 25	}
 26	return m
 27}
 28
 29func TestParseGoMod(t *testing.T) {
 30	deps := parseGoMod(tree(map[string]string{"go.mod": `
 31module gitbay.org/gitbay
 32
 33go 1.27.0
 34
 35require (
 36	github.com/BurntSushi/toml v1.6.0
 37	golang.org/x/crypto v0.55.0
 38	github.com/vendored/thing v0.1.0
 39)
 40
 41require github.com/spf13/cobra v1.10.2
 42
 43require (
 44	github.com/gorilla/css v1.0.1 // indirect
 45	golang.org/x/sys v0.47.0 // indirect
 46)
 47
 48replace github.com/vendored/thing => ./vendor/thing
 49`}))
 50	got := found(t, deps)
 51	want := map[string]string{
 52		"go:github.com/BurntSushi/toml": "v1.6.0",
 53		"go:golang.org/x/crypto":        "v0.55.0",
 54		"go:github.com/spf13/cobra":     "v1.10.2",
 55	}
 56	if fmt.Sprint(got) != fmt.Sprint(want) {
 57		t.Errorf("go.mod deps = %v, want %v", got, want)
 58	}
 59}
 60
 61func TestParseNPMPrefersLockfile(t *testing.T) {
 62	files := map[string]string{
 63		"package.json": `{
 64			"dependencies": {"react": "^18.0.0", "left-pad": "1.3.0", "local": "file:../local"},
 65			"devDependencies": {"@types/node": "^20.0.0", "typescript": "*"}
 66		}`,
 67		"package-lock.json": `{
 68			"packages": {
 69				"": {"version": "1.0.0"},
 70				"node_modules/react": {"version": "18.2.0"},
 71				"node_modules/@types/node": {"version": "20.11.5"},
 72				"node_modules/react/node_modules/scheduler": {"version": "0.23.0"}
 73			}
 74		}`,
 75	}
 76	got := found(t, parseNPM(tree(files)))
 77	want := map[string]string{
 78		"npm:react":       "18.2.0", // lockfile beats the ^18.0.0 floor
 79		"npm:@types/node": "20.11.5",
 80		"npm:left-pad":    "1.3.0",
 81	}
 82	if fmt.Sprint(got) != fmt.Sprint(want) {
 83		t.Errorf("npm deps = %v, want %v", got, want)
 84	}
 85
 86	// Without a lockfile the declared floor is what there is to compare.
 87	delete(files, "package-lock.json")
 88	if got := found(t, parseNPM(tree(files)))["npm:react"]; got != "18.0.0" {
 89		t.Errorf("react without lockfile = %q, want 18.0.0", got)
 90	}
 91}
 92
 93func TestParseCargo(t *testing.T) {
 94	files := map[string]string{
 95		"Cargo.toml": `
 96[dependencies]
 97serde = "1.0.100"
 98tokio = { version = "1.20", features = ["full"] }
 99helper = { path = "../helper" }
100upstream = { git = "https://example.invalid/x" }
101
102[dev-dependencies]
103criterion = "0.5"
104`,
105		"Cargo.lock": `
106[[package]]
107name = "serde"
108version = "1.0.197"
109
110[[package]]
111name = "tokio"
112version = "1.36.0"
113`,
114	}
115	got := found(t, parseCargo(tree(files)))
116	want := map[string]string{
117		"cargo:serde":     "1.0.197",
118		"cargo:tokio":     "1.36.0",
119		"cargo:criterion": "0.5", // not in the lock, so the requirement stands
120	}
121	if fmt.Sprint(got) != fmt.Sprint(want) {
122		t.Errorf("cargo deps = %v, want %v", got, want)
123	}
124}
125
126func TestParsePython(t *testing.T) {
127	files := map[string]string{
128		"requirements.txt": `
129# comment
130requests==2.31.0
131django[bcrypt]==5.0.1 ; python_version >= "3.10"
132flask>=2,<3
133uvicorn
134-r other.txt
135`,
136		"pyproject.toml": `
137[project]
138dependencies = ["httpx==0.27.0", "pydantic>=2"]
139
140[tool.poetry.dependencies]
141python = "^3.11"
142rich = "^13.7.0"
143`,
144	}
145	got := found(t, parsePython(tree(files)))
146	want := map[string]string{
147		"pypi:requests": "2.31.0",
148		"pypi:django":   "5.0.1",
149		"pypi:httpx":    "0.27.0",
150		"pypi:pydantic": "2",
151		"pypi:rich":     "13.7.0",
152	}
153	if fmt.Sprint(got) != fmt.Sprint(want) {
154		t.Errorf("python deps = %v, want %v", got, want)
155	}
156}
157
158func TestScanSortsAndCaps(t *testing.T) {
159	files := map[string]string{
160		"go.mod":       "module x\n\nrequire github.com/a/b v1.0.0\n",
161		"package.json": `{"dependencies": {"z": "1.0.0"}}`,
162	}
163	got := Scan(tree(files))
164	if len(got) != 2 {
165		t.Fatalf("Scan = %v, want 2 deps", got)
166	}
167	if got[0].Ecosystem != EcoGo || got[1].Ecosystem != EcoNPM {
168		t.Errorf("Scan order = %s, %s", got[0].Ecosystem, got[1].Ecosystem)
169	}
170}
171
172func TestScanEmptyTree(t *testing.T) {
173	if got := Scan(tree(nil)); len(got) != 0 {
174		t.Errorf("Scan of empty tree = %v", got)
175	}
176}