github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/golang/licenses_test.go (about) 1 package golang 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "context" 7 "fmt" 8 "io/fs" 9 "net/http" 10 "net/http/httptest" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15 "testing" 16 17 "github.com/stretchr/testify/require" 18 19 "github.com/anchore/syft/syft/file" 20 "github.com/anchore/syft/syft/internal/fileresolver" 21 "github.com/anchore/syft/syft/license" 22 "github.com/anchore/syft/syft/pkg" 23 "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" 24 ) 25 26 func Test_LicenseSearch(t *testing.T) { 27 ctx := pkgtest.Context() 28 29 loc1 := file.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE") 30 loc2 := file.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt") 31 loc3 := file.NewLocation("github.com/someorg/strangelicense@v1.2.3/LiCeNsE.tXt") 32 33 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 34 buf := &bytes.Buffer{} 35 uri := strings.TrimPrefix(strings.TrimSuffix(r.RequestURI, ".zip"), "/") 36 37 parts := strings.Split(uri, "/@v/") 38 modPath := parts[0] 39 modVersion := parts[1] 40 41 wd, err := os.Getwd() 42 require.NoError(t, err) 43 testDir := filepath.Join(wd, "test-fixtures", "licenses", "pkg", "mod", processCaps(modPath)+"@"+modVersion) 44 45 archive := zip.NewWriter(buf) 46 47 entries, err := os.ReadDir(testDir) 48 require.NoError(t, err) 49 for _, f := range entries { 50 // the zip files downloaded contain a path to the repo that somewhat matches where it ends up on disk, 51 // so prefix entries with something similar 52 writer, err := archive.Create(path.Join(moduleDir(modPath, modVersion), f.Name())) 53 require.NoError(t, err) 54 contents, err := os.ReadFile(filepath.Join(testDir, f.Name())) 55 require.NoError(t, err) 56 _, err = writer.Write(contents) 57 require.NoError(t, err) 58 } 59 60 err = archive.Close() 61 require.NoError(t, err) 62 63 w.Header().Add("Content-Length", fmt.Sprintf("%d", buf.Len())) 64 65 _, err = w.Write(buf.Bytes()) 66 require.NoError(t, err) 67 })) 68 defer server.Close() 69 70 wd, err := os.Getwd() 71 require.NoError(t, err) 72 73 localVendorDir := filepath.Join(wd, "test-fixtures", "licenses-vendor") 74 75 tests := []struct { 76 name string 77 version string 78 config CatalogerConfig 79 expected []pkg.License 80 }{ 81 { 82 name: "github.com/someorg/somename", 83 version: "v0.3.2", 84 config: CatalogerConfig{ 85 SearchLocalModCacheLicenses: true, 86 LocalModCacheDir: filepath.Join(wd, "test-fixtures", "licenses", "pkg", "mod"), 87 }, 88 expected: []pkg.License{{ 89 Value: "Apache-2.0", 90 SPDXExpression: "Apache-2.0", 91 Type: license.Concluded, 92 Contents: mustContentsFromLocation(t, loc1), 93 URLs: []string{"file://$GOPATH/pkg/mod/" + loc1.RealPath}, 94 Locations: file.NewLocationSet(), 95 }}, 96 }, 97 { 98 name: "github.com/CapORG/CapProject", 99 version: "v4.111.5", 100 config: CatalogerConfig{ 101 SearchLocalModCacheLicenses: true, 102 LocalModCacheDir: filepath.Join(wd, "test-fixtures", "licenses", "pkg", "mod"), 103 }, 104 expected: []pkg.License{{ 105 Value: "MIT", 106 SPDXExpression: "MIT", 107 Type: license.Concluded, 108 Contents: mustContentsFromLocation(t, loc2, 23, 1105), 109 URLs: []string{"file://$GOPATH/pkg/mod/" + loc2.RealPath}, 110 Locations: file.NewLocationSet(), 111 }}, 112 }, 113 { 114 name: "github.com/someorg/strangelicense", 115 version: "v1.2.3", 116 config: CatalogerConfig{ 117 SearchLocalModCacheLicenses: true, 118 LocalModCacheDir: filepath.Join(wd, "test-fixtures", "licenses", "pkg", "mod"), 119 }, 120 expected: []pkg.License{{ 121 Value: "Apache-2.0", 122 SPDXExpression: "Apache-2.0", 123 Type: license.Concluded, 124 Contents: mustContentsFromLocation(t, loc3), 125 URLs: []string{"file://$GOPATH/pkg/mod/" + loc3.RealPath}, 126 Locations: file.NewLocationSet(), 127 }}, 128 }, 129 { 130 name: "github.com/someorg/somename", 131 version: "v0.3.2", 132 config: CatalogerConfig{ 133 SearchRemoteLicenses: true, 134 Proxies: []string{server.URL}, 135 }, 136 expected: []pkg.License{{ 137 Value: "Apache-2.0", 138 SPDXExpression: "Apache-2.0", 139 Contents: mustContentsFromLocation(t, loc1), 140 Type: license.Concluded, 141 URLs: []string{server.URL + "/github.com/someorg/somename/@v/v0.3.2.zip#" + loc1.RealPath}, 142 Locations: file.NewLocationSet(), 143 }}, 144 }, 145 { 146 name: "github.com/CapORG/CapProject", 147 version: "v4.111.5", 148 config: CatalogerConfig{ 149 SearchRemoteLicenses: true, 150 Proxies: []string{server.URL}, 151 }, 152 expected: []pkg.License{{ 153 Value: "MIT", 154 SPDXExpression: "MIT", 155 Contents: mustContentsFromLocation(t, loc2, 23, 1105), // offset for correct scanner contents 156 Type: license.Concluded, 157 URLs: []string{server.URL + "/github.com/CapORG/CapProject/@v/v4.111.5.zip#" + loc2.RealPath}, 158 Locations: file.NewLocationSet(), 159 }}, 160 }, 161 { 162 name: "github.com/CapORG/CapProject", 163 version: "v4.111.5", 164 config: CatalogerConfig{ 165 SearchLocalModCacheLicenses: true, 166 LocalModCacheDir: filepath.Join(wd, "test-fixtures"), // valid dir but does not find modules 167 SearchRemoteLicenses: true, 168 Proxies: []string{server.URL}, 169 }, 170 expected: []pkg.License{{ 171 Value: "MIT", 172 SPDXExpression: "MIT", 173 Contents: mustContentsFromLocation(t, loc2, 23, 1105), // offset for correct scanner contents 174 Type: license.Concluded, 175 URLs: []string{server.URL + "/github.com/CapORG/CapProject/@v/v4.111.5.zip#" + loc2.RealPath}, 176 Locations: file.NewLocationSet(), 177 }}, 178 }, 179 { 180 name: "github.com/someorg/somename", 181 version: "v0.3.2", 182 config: CatalogerConfig{ 183 SearchLocalVendorLicenses: true, 184 LocalVendorDir: localVendorDir, 185 }, 186 expected: []pkg.License{{ 187 Value: "Apache-2.0", 188 SPDXExpression: "Apache-2.0", 189 Type: license.Concluded, 190 Contents: mustContentsFromLocation(t, loc1), 191 URLs: []string{"file://$GO_VENDOR/github.com/someorg/somename/LICENSE"}, 192 Locations: file.NewLocationSet(), 193 }}, 194 }, 195 { 196 name: "github.com/CapORG/CapProject", 197 version: "v4.111.5", 198 config: CatalogerConfig{ 199 SearchLocalVendorLicenses: true, 200 LocalVendorDir: localVendorDir, 201 }, 202 expected: []pkg.License{{ 203 Value: "MIT", 204 SPDXExpression: "MIT", 205 Contents: mustContentsFromLocation(t, loc2, 23, 1105), // offset for correct scanner contents 206 Type: license.Concluded, 207 URLs: []string{"file://$GO_VENDOR/github.com/!cap!o!r!g/!cap!project/LICENSE.txt"}, 208 Locations: file.NewLocationSet(), 209 }}, 210 }, 211 { 212 name: "github.com/someorg/strangelicense", 213 version: "v1.2.3", 214 config: CatalogerConfig{ 215 SearchLocalVendorLicenses: true, 216 LocalVendorDir: localVendorDir, 217 }, 218 expected: []pkg.License{{ 219 Value: "Apache-2.0", 220 SPDXExpression: "Apache-2.0", 221 Contents: mustContentsFromLocation(t, loc1), 222 Type: license.Concluded, 223 URLs: []string{"file://$GO_VENDOR/github.com/someorg/strangelicense/LiCeNsE.tXt"}, 224 Locations: file.NewLocationSet(), 225 }}, 226 }, 227 } 228 229 for _, test := range tests { 230 t.Run(test.name, func(t *testing.T) { 231 l := newGoLicenseResolver("", test.config) 232 lics := l.getLicenses(ctx, fileresolver.Empty{}, test.name, test.version) 233 require.EqualValues(t, test.expected, lics) 234 }) 235 } 236 } 237 238 func Test_processCaps(t *testing.T) { 239 tests := []struct { 240 name string 241 expected string 242 }{ 243 { 244 name: "CycloneDX", 245 expected: "!cyclone!d!x", 246 }, 247 { 248 name: "Azure", 249 expected: "!azure", 250 }, 251 { 252 name: "xkcd", 253 expected: "xkcd", 254 }, 255 } 256 257 for _, test := range tests { 258 t.Run(test.name, func(t *testing.T) { 259 got := processCaps(test.name) 260 261 require.Equal(t, test.expected, got) 262 }) 263 } 264 } 265 266 func Test_remotesForModule(t *testing.T) { 267 allProxies := []string{"https://somewhere.org", "direct"} 268 directProxy := []string{"direct"} 269 270 tests := []struct { 271 module string 272 noProxy string 273 expected []string 274 }{ 275 { 276 module: "github.com/anchore/syft", 277 expected: allProxies, 278 }, 279 { 280 module: "github.com/anchore/sbom-action", 281 noProxy: "*/anchore/*", 282 expected: directProxy, 283 }, 284 { 285 module: "github.com/anchore/sbom-action", 286 noProxy: "*/user/mod,*/anchore/sbom-action", 287 expected: directProxy, 288 }, 289 } 290 291 for _, test := range tests { 292 t.Run(test.module, func(t *testing.T) { 293 got := remotesForModule(allProxies, strings.Split(test.noProxy, ","), test.module) 294 require.Equal(t, test.expected, got) 295 }) 296 } 297 } 298 299 func Test_findVersionPath(t *testing.T) { 300 f := os.DirFS("test-fixtures/zip-fs") 301 vp := findVersionPath(f, ".") 302 require.Equal(t, "github.com/someorg/somepkg@version", vp) 303 } 304 305 func Test_walkDirErrors(t *testing.T) { 306 resolver := newGoLicenseResolver("", CatalogerConfig{}) 307 _, err := resolver.findLicensesInFS(context.Background(), "somewhere", badFS{}) 308 require.Error(t, err) 309 } 310 311 type badFS struct{} 312 313 func (b badFS) Open(_ string) (fs.File, error) { 314 return nil, fmt.Errorf("error") 315 } 316 317 var _ fs.FS = (*badFS)(nil) 318 319 func Test_noLocalGoModDir(t *testing.T) { 320 emptyTmp := t.TempDir() 321 322 validTmp := t.TempDir() 323 require.NoError(t, os.MkdirAll(filepath.Join(validTmp, "mod@ver"), 0700|os.ModeDir)) 324 ctx := pkgtest.Context() 325 tests := []struct { 326 name string 327 dir string 328 wantErr require.ErrorAssertionFunc 329 }{ 330 { 331 name: "empty", 332 dir: "", 333 wantErr: require.Error, 334 }, 335 { 336 name: "invalid dir", 337 dir: filepath.Join(emptyTmp, "invalid-dir"), 338 wantErr: require.Error, 339 }, 340 { 341 name: "missing mod dir", 342 dir: emptyTmp, 343 wantErr: require.Error, 344 }, 345 { 346 name: "valid mod dir", 347 dir: validTmp, 348 wantErr: require.NoError, 349 }, 350 } 351 352 for _, test := range tests { 353 t.Run(test.name, func(t *testing.T) { 354 resolver := newGoLicenseResolver("", CatalogerConfig{ 355 SearchLocalModCacheLicenses: true, 356 LocalModCacheDir: test.dir, 357 }) 358 _, err := resolver.getLicensesFromLocal(ctx, "mod", "ver") 359 test.wantErr(t, err) 360 }) 361 } 362 } 363 364 func mustContentsFromLocation(t *testing.T, loc file.Location, offset ...int) string { 365 t.Helper() 366 367 contentsPath := "test-fixtures/licenses/pkg/mod/" + loc.RealPath 368 contents, err := os.ReadFile(contentsPath) 369 require.NoErrorf(t, err, "could not open contents for fixture at %s", contentsPath) 370 371 if len(offset) == 0 { 372 return string(contents) 373 } 374 375 require.Equal(t, 2, len(offset), "invalid offset provided, expected two integers: start and end") 376 377 start, end := offset[0], offset[1] 378 require.GreaterOrEqual(t, start, 0, "offset start must be >= 0") 379 require.LessOrEqual(t, end, len(contents), "offset end must be <= content length") 380 require.LessOrEqual(t, start, end, "offset start must be <= end") 381 382 return string(contents[start:end]) 383 }