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  }