github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/python/uvlock/uvlock_test.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package uvlock_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/google/go-cmp/cmp"
    21  	"github.com/google/go-cmp/cmp/cmpopts"
    22  	"github.com/google/osv-scalibr/extractor"
    23  	"github.com/google/osv-scalibr/extractor/filesystem/language/python/uvlock"
    24  	"github.com/google/osv-scalibr/extractor/filesystem/osv"
    25  	"github.com/google/osv-scalibr/extractor/filesystem/simplefileapi"
    26  	"github.com/google/osv-scalibr/inventory"
    27  	"github.com/google/osv-scalibr/purl"
    28  	"github.com/google/osv-scalibr/testing/extracttest"
    29  )
    30  
    31  func pkg(t *testing.T, name string, version string, location string) *extractor.Package {
    32  	t.Helper()
    33  
    34  	return &extractor.Package{
    35  		Name:      name,
    36  		Version:   version,
    37  		PURLType:  purl.TypePyPi,
    38  		Locations: []string{location},
    39  		Metadata: osv.DepGroupMetadata{
    40  			DepGroupVals: []string{},
    41  		},
    42  	}
    43  }
    44  
    45  func TestExtractor_FileRequired(t *testing.T) {
    46  	tests := []struct {
    47  		name      string
    48  		inputPath string
    49  		want      bool
    50  	}{
    51  		{
    52  			name:      "",
    53  			inputPath: "",
    54  			want:      false,
    55  		},
    56  		{
    57  			name:      "",
    58  			inputPath: "uv.lock",
    59  			want:      true,
    60  		},
    61  		{
    62  			name:      "",
    63  			inputPath: "path/to/my/uv.lock",
    64  			want:      true,
    65  		},
    66  		{
    67  			name:      "",
    68  			inputPath: "path/to/my/uv.lock/file",
    69  			want:      false,
    70  		},
    71  		{
    72  			name:      "",
    73  			inputPath: "path/to/my/uv.lock.file",
    74  			want:      false,
    75  		},
    76  		{
    77  			name:      "",
    78  			inputPath: "path.to.my.uv.lock",
    79  			want:      false,
    80  		},
    81  	}
    82  	for _, tt := range tests {
    83  		t.Run(tt.name, func(t *testing.T) {
    84  			e := uvlock.Extractor{}
    85  			got := e.FileRequired(simplefileapi.New(tt.inputPath, nil))
    86  			if got != tt.want {
    87  				t.Errorf("FileRequired(%q, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want)
    88  			}
    89  		})
    90  	}
    91  }
    92  
    93  func TestExtractor_Extract(t *testing.T) {
    94  	tests := []extracttest.TestTableEntry{
    95  		{
    96  			Name: "invalid toml",
    97  			InputConfig: extracttest.ScanInputMockConfig{
    98  				Path: "testdata/not-toml.txt",
    99  			},
   100  			WantErr:      extracttest.ContainsErrStr{Str: "could not extract"},
   101  			WantPackages: nil,
   102  		},
   103  		{
   104  			Name: "empty file",
   105  			InputConfig: extracttest.ScanInputMockConfig{
   106  				Path: "testdata/empty.lock",
   107  			},
   108  			WantPackages: []*extractor.Package{},
   109  		},
   110  		{
   111  			Name: "no dependencies",
   112  			InputConfig: extracttest.ScanInputMockConfig{
   113  				Path: "testdata/empty.lock",
   114  			},
   115  			WantPackages: []*extractor.Package{},
   116  		},
   117  		{
   118  			Name: "no packages",
   119  			InputConfig: extracttest.ScanInputMockConfig{
   120  				Path: "testdata/empty.lock",
   121  			},
   122  			WantPackages: []*extractor.Package{},
   123  		},
   124  		{
   125  			Name: "one package",
   126  			InputConfig: extracttest.ScanInputMockConfig{
   127  				Path: "testdata/one-package.lock",
   128  			},
   129  			WantPackages: []*extractor.Package{
   130  				pkg(t, "emoji", "2.14.0", "testdata/one-package.lock"),
   131  			},
   132  		},
   133  		{
   134  			Name: "two packages",
   135  			InputConfig: extracttest.ScanInputMockConfig{
   136  				Path: "testdata/two-packages.lock",
   137  			},
   138  			WantPackages: []*extractor.Package{
   139  				pkg(t, "emoji", "2.14.0", "testdata/two-packages.lock"),
   140  				pkg(t, "protobuf", "4.25.5", "testdata/two-packages.lock"),
   141  			},
   142  		},
   143  		{
   144  			Name: "source git",
   145  			InputConfig: extracttest.ScanInputMockConfig{
   146  				Path: "testdata/source-git.lock",
   147  			},
   148  			WantPackages: []*extractor.Package{
   149  				{
   150  					Name:      "ruff",
   151  					Version:   "0.8.1",
   152  					PURLType:  purl.TypePyPi,
   153  					Locations: []string{"testdata/source-git.lock"},
   154  					SourceCode: &extractor.SourceCodeIdentifier{
   155  						Commit: "84748be16341b76e073d117329f7f5f4ee2941ad",
   156  					},
   157  					Metadata: osv.DepGroupMetadata{
   158  						DepGroupVals: []string{},
   159  					},
   160  				},
   161  			},
   162  		},
   163  		{
   164  			Name: "grouped packages",
   165  			InputConfig: extracttest.ScanInputMockConfig{
   166  				Path: "testdata/grouped-packages.lock",
   167  			},
   168  			WantPackages: []*extractor.Package{
   169  				pkg(t, "emoji", "2.14.0", "testdata/grouped-packages.lock"),
   170  				{
   171  					Name:      "click",
   172  					Version:   "8.1.7",
   173  					PURLType:  purl.TypePyPi,
   174  					Locations: []string{"testdata/grouped-packages.lock"},
   175  					Metadata: osv.DepGroupMetadata{
   176  						DepGroupVals: []string{"cli"},
   177  					},
   178  				},
   179  				pkg(t, "colorama", "0.4.6", "testdata/grouped-packages.lock"),
   180  				{
   181  					Name:      "black",
   182  					Version:   "24.10.0",
   183  					PURLType:  purl.TypePyPi,
   184  					Locations: []string{"testdata/grouped-packages.lock"},
   185  					Metadata: osv.DepGroupMetadata{
   186  						DepGroupVals: []string{"dev", "test"},
   187  					},
   188  				},
   189  				{
   190  					Name:      "flake8",
   191  					Version:   "7.1.1",
   192  					PURLType:  purl.TypePyPi,
   193  					Locations: []string{"testdata/grouped-packages.lock"},
   194  					Metadata: osv.DepGroupMetadata{
   195  						DepGroupVals: []string{"test"},
   196  					},
   197  				},
   198  				pkg(t, "mccabe", "0.7.0", "testdata/grouped-packages.lock"),
   199  				pkg(t, "mypy-extensions", "1.0.0", "testdata/grouped-packages.lock"),
   200  				pkg(t, "packaging", "24.2", "testdata/grouped-packages.lock"),
   201  				pkg(t, "pathspec", "0.12.1", "testdata/grouped-packages.lock"),
   202  				pkg(t, "platformdirs", "4.3.6", "testdata/grouped-packages.lock"),
   203  				pkg(t, "pycodestyle", "2.12.1", "testdata/grouped-packages.lock"),
   204  				pkg(t, "pyflakes", "3.2.0", "testdata/grouped-packages.lock"),
   205  				pkg(t, "tomli", "2.2.1", "testdata/grouped-packages.lock"),
   206  				pkg(t, "typing-extensions", "4.12.2", "testdata/grouped-packages.lock"),
   207  			},
   208  		},
   209  	}
   210  
   211  	for _, tt := range tests {
   212  		t.Run(tt.Name, func(t *testing.T) {
   213  			extr := uvlock.Extractor{}
   214  
   215  			scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig)
   216  			defer extracttest.CloseTestScanInput(t, scanInput)
   217  
   218  			got, err := extr.Extract(t.Context(), &scanInput)
   219  
   220  			if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" {
   221  				t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   222  				return
   223  			}
   224  
   225  			wantInv := inventory.Inventory{Packages: tt.WantPackages}
   226  			if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" {
   227  				t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   228  			}
   229  		})
   230  	}
   231  }