internal/deps/manifest_test.go
176 lines · 4201 bytes
8 symbols in this file
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}