github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/python/parse_uv_lock_test.go (about) 1 package python 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/anchore/syft/syft/artifact" 8 "github.com/anchore/syft/syft/file" 9 "github.com/anchore/syft/syft/pkg" 10 "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" 11 ) 12 13 func TestParseUvLock(t *testing.T) { 14 fixture := "test-fixtures/uv/simple-deps/uv.lock" 15 16 locations := file.NewLocationSet(file.NewLocation(fixture)) 17 18 certifi := pkg.Package{ 19 Name: "certifi", 20 Version: "2025.1.31", 21 Locations: locations, 22 PURL: "pkg:pypi/certifi@2025.1.31", 23 Language: pkg.Python, 24 Type: pkg.PythonPkg, 25 Metadata: pkg.PythonUvLockEntry{Index: "https://pypi.org/simple"}, 26 } 27 28 charsetNormalizer := pkg.Package{ 29 Name: "charset-normalizer", 30 Version: "3.4.1", 31 Locations: locations, 32 PURL: "pkg:pypi/charset-normalizer@3.4.1", 33 Language: pkg.Python, 34 Type: pkg.PythonPkg, 35 Metadata: pkg.PythonUvLockEntry{Index: "https://pypi.org/simple"}, 36 } 37 38 idna := pkg.Package{ 39 Name: "idna", 40 Version: "3.10", 41 Locations: locations, 42 PURL: "pkg:pypi/idna@3.10", 43 Language: pkg.Python, 44 Type: pkg.PythonPkg, 45 Metadata: pkg.PythonUvLockEntry{Index: "https://pypi.org/simple"}, 46 } 47 48 requests := pkg.Package{ 49 Name: "requests", 50 Version: "2.32.3", 51 Locations: locations, 52 PURL: "pkg:pypi/requests@2.32.3", 53 Language: pkg.Python, 54 Type: pkg.PythonPkg, 55 Metadata: pkg.PythonUvLockEntry{ 56 Index: "https://pypi.org/simple", 57 Dependencies: []pkg.PythonUvLockDependencyEntry{ 58 {Name: "certifi"}, 59 {Name: "charset-normalizer"}, 60 {Name: "idna"}, 61 {Name: "urllib3"}, 62 }, 63 }, 64 } 65 66 testpkg := pkg.Package{ 67 Name: "testpkg", 68 Version: "0.1.0", 69 Locations: locations, 70 PURL: "pkg:pypi/testpkg@0.1.0", 71 Language: pkg.Python, 72 Type: pkg.PythonPkg, 73 Metadata: pkg.PythonUvLockEntry{ 74 Index: ".", // virtual 75 Dependencies: []pkg.PythonUvLockDependencyEntry{ 76 {Name: "requests"}, 77 }, 78 }, 79 } 80 81 urllib3 := pkg.Package{ 82 Name: "urllib3", 83 Version: "2.3.0", 84 Locations: locations, 85 PURL: "pkg:pypi/urllib3@2.3.0", 86 Language: pkg.Python, 87 Type: pkg.PythonPkg, 88 Metadata: pkg.PythonUvLockEntry{Index: "https://pypi.org/simple"}, 89 } 90 91 expectedPkgs := []pkg.Package{ 92 certifi, 93 charsetNormalizer, 94 idna, 95 requests, 96 testpkg, 97 urllib3, 98 } 99 100 expectedRelationships := []artifact.Relationship{ 101 { 102 From: certifi, 103 To: requests, 104 Type: artifact.DependencyOfRelationship, 105 }, 106 { 107 From: charsetNormalizer, 108 To: requests, 109 Type: artifact.DependencyOfRelationship, 110 }, 111 { 112 From: idna, 113 To: requests, 114 Type: artifact.DependencyOfRelationship, 115 }, 116 { 117 From: requests, 118 To: testpkg, 119 Type: artifact.DependencyOfRelationship, 120 }, 121 { 122 From: urllib3, 123 To: requests, 124 Type: artifact.DependencyOfRelationship, 125 }, 126 } 127 128 uvLockParser := newUvLockParser(DefaultCatalogerConfig()) 129 pkgtest.TestFileParser(t, fixture, uvLockParser.parseUvLock, expectedPkgs, expectedRelationships) 130 } 131 132 func TestParseUvLockWithLicenseEnrichment(t *testing.T) { 133 ctx := context.TODO() 134 fixture := "test-fixtures/pypi-remote/uv.lock" 135 locations := file.NewLocationSet(file.NewLocation(fixture)) 136 mux, url, teardown := setupPypiRegistry() 137 defer teardown() 138 tests := []struct { 139 name string 140 fixture string 141 config CatalogerConfig 142 requestHandlers []handlerPath 143 expectedPackages []pkg.Package 144 }{ 145 { 146 name: "search remote licenses returns the expected licenses when search is set to true", 147 config: CatalogerConfig{SearchRemoteLicenses: true}, 148 requestHandlers: []handlerPath{ 149 { 150 path: "/certifi/2025.10.5/json", 151 handler: generateMockPypiRegistryHandler("test-fixtures/pypi-remote/registry_response.json"), 152 }, 153 }, 154 expectedPackages: []pkg.Package{ 155 { 156 Name: "certifi", 157 Version: "2025.10.5", 158 Locations: locations, 159 PURL: "pkg:pypi/certifi@2025.10.5", 160 Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "MPL-2.0")), 161 Language: pkg.Python, 162 Type: pkg.PythonPkg, 163 Metadata: pkg.PythonUvLockEntry{ 164 Index: "https://pypi.org/simple", 165 Dependencies: nil, 166 }, 167 }, 168 }, 169 }, 170 } 171 for _, tc := range tests { 172 t.Run(tc.name, func(t *testing.T) { 173 // set up the mock server 174 for _, handler := range tc.requestHandlers { 175 mux.HandleFunc(handler.path, handler.handler) 176 } 177 tc.config.PypiBaseURL = url 178 uvLockParser := newUvLockParser(tc.config) 179 pkgtest.TestFileParser(t, fixture, uvLockParser.parseUvLock, tc.expectedPackages, nil) 180 }) 181 } 182 }