github.com/google/osv-scalibr@v0.4.1/enricher/transitivedependency/requirements/requirements_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 requirements_test
    16  
    17  import (
    18  	"sort"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto"
    23  	"github.com/google/osv-scalibr/clients/clienttest"
    24  	"github.com/google/osv-scalibr/enricher"
    25  	"github.com/google/osv-scalibr/enricher/transitivedependency/requirements"
    26  	"github.com/google/osv-scalibr/extractor"
    27  	requirementsextractor "github.com/google/osv-scalibr/extractor/filesystem/language/python/requirements"
    28  	scalibrfs "github.com/google/osv-scalibr/fs"
    29  	"github.com/google/osv-scalibr/inventory"
    30  	"github.com/google/osv-scalibr/purl"
    31  )
    32  
    33  func TestEnricher_Enrich(t *testing.T) {
    34  	input := enricher.ScanInput{
    35  		ScanRoot: &scalibrfs.ScanRoot{
    36  			Path: "testdata",
    37  			FS:   scalibrfs.DirFS("."),
    38  		},
    39  	}
    40  	inv := inventory.Inventory{
    41  		Packages: []*extractor.Package{
    42  			{
    43  				// Not a Python package.
    44  				Name:      "abc:xyz",
    45  				Version:   "1.0.0",
    46  				PURLType:  purl.TypeMaven,
    47  				Locations: []string{"testdata/maven/pom.xml"},
    48  				Plugins:   []string{"java/pomxml"},
    49  			},
    50  			{
    51  				// Not extracted in requirements.txt.
    52  				Name:      "abc",
    53  				Version:   "1.0.0",
    54  				PURLType:  purl.TypePyPi,
    55  				Locations: []string{"testdata/poetry/poetry.lock"},
    56  				Plugins:   []string{"python/poetrylock"},
    57  			},
    58  			{
    59  				Name:      "alice",
    60  				PURLType:  purl.TypePyPi,
    61  				Locations: []string{"testdata/requirements.txt"},
    62  				Metadata:  &requirementsextractor.Metadata{Requirement: "alice"},
    63  				Plugins:   []string{"python/requirements"},
    64  			},
    65  			{
    66  				Name:      "bob",
    67  				Version:   "2.0.0",
    68  				PURLType:  purl.TypePyPi,
    69  				Locations: []string{"testdata/requirements.txt"},
    70  				Metadata:  &requirementsextractor.Metadata{VersionComparator: "==", Requirement: "bob==2.0.0"},
    71  				Plugins:   []string{"python/requirements"},
    72  			},
    73  			{
    74  				// Hash checking mode.
    75  				Name:      "hash1",
    76  				Version:   "1.0.0",
    77  				PURLType:  purl.TypePyPi,
    78  				Locations: []string{"testdata/hash/requirements.txt"},
    79  				Metadata:  &requirementsextractor.Metadata{VersionComparator: "==", HashCheckingModeValues: []string{"sha256:123"}, Requirement: "hash1==1.0.0"},
    80  				Plugins:   []string{"python/requirements"},
    81  			},
    82  			{
    83  				// Hash checking mode.
    84  				Name:      "hash2",
    85  				Version:   "2.0.0",
    86  				PURLType:  purl.TypePyPi,
    87  				Locations: []string{"testdata/hash/requirements.txt"},
    88  				Metadata:  &requirementsextractor.Metadata{VersionComparator: "==", HashCheckingModeValues: []string{"sha256:456"}, Requirement: "hash2==2.0.0"},
    89  				Plugins:   []string{"python/requirements"},
    90  			},
    91  		},
    92  	}
    93  
    94  	resolutionClient := clienttest.NewMockResolutionClient(t, "testdata/universe.yaml")
    95  	enricher := requirements.New(&cpb.PluginConfig{})
    96  	enricher.(*requirements.Enricher).Client = resolutionClient
    97  	err := enricher.Enrich(t.Context(), &input, &inv)
    98  	if err != nil {
    99  		t.Fatalf("failed to enrich: %v", err)
   100  	}
   101  
   102  	wantInventory := inventory.Inventory{
   103  		Packages: []*extractor.Package{
   104  			{
   105  				// Not extracted in requirements.txt.
   106  				Name:      "abc",
   107  				Version:   "1.0.0",
   108  				PURLType:  purl.TypePyPi,
   109  				Locations: []string{"testdata/poetry/poetry.lock"},
   110  				Plugins:   []string{"python/poetrylock"},
   111  			},
   112  			{
   113  				// Not a Python package.
   114  				Name:      "abc:xyz",
   115  				Version:   "1.0.0",
   116  				PURLType:  purl.TypeMaven,
   117  				Locations: []string{"testdata/maven/pom.xml"},
   118  				Plugins:   []string{"java/pomxml"},
   119  			},
   120  			{
   121  				Name:      "alice",
   122  				Version:   "1.0.0",
   123  				PURLType:  purl.TypePyPi,
   124  				Locations: []string{"testdata/requirements.txt"},
   125  				Metadata:  &requirementsextractor.Metadata{Requirement: "alice"},
   126  				Plugins:   []string{"python/requirements", "transitivedependency/requirements"},
   127  			},
   128  			{
   129  				Name:      "bob",
   130  				Version:   "2.0.0",
   131  				PURLType:  purl.TypePyPi,
   132  				Locations: []string{"testdata/requirements.txt"},
   133  				Metadata:  &requirementsextractor.Metadata{VersionComparator: "==", Requirement: "bob==2.0.0"},
   134  				Plugins:   []string{"python/requirements", "transitivedependency/requirements"},
   135  			},
   136  			{
   137  				Name:      "chuck",
   138  				Version:   "2.0.0",
   139  				PURLType:  purl.TypePyPi,
   140  				Locations: []string{"testdata/requirements.txt"},
   141  				Plugins:   []string{"transitivedependency/requirements"},
   142  			},
   143  			{
   144  				Name:      "dave",
   145  				Version:   "2.0.0",
   146  				PURLType:  purl.TypePyPi,
   147  				Locations: []string{"testdata/requirements.txt"},
   148  				Plugins:   []string{"transitivedependency/requirements"},
   149  			},
   150  			{
   151  				Name:      "eve",
   152  				Version:   "1.5.0",
   153  				PURLType:  purl.TypePyPi,
   154  				Locations: []string{"testdata/requirements.txt"},
   155  				Plugins:   []string{"transitivedependency/requirements"},
   156  			},
   157  			{
   158  				Name:      "frank",
   159  				Version:   "2.0.0",
   160  				PURLType:  purl.TypePyPi,
   161  				Locations: []string{"testdata/requirements.txt"},
   162  				Plugins:   []string{"transitivedependency/requirements"},
   163  			},
   164  			{
   165  				// Hash checking mode.
   166  				Name:      "hash1",
   167  				Version:   "1.0.0",
   168  				PURLType:  purl.TypePyPi,
   169  				Locations: []string{"testdata/hash/requirements.txt"},
   170  				Metadata:  &requirementsextractor.Metadata{VersionComparator: "==", HashCheckingModeValues: []string{"sha256:123"}, Requirement: "hash1==1.0.0"},
   171  				Plugins:   []string{"python/requirements"},
   172  			},
   173  			{
   174  				// Hash checking mode.
   175  				Name:      "hash2",
   176  				Version:   "2.0.0",
   177  				PURLType:  purl.TypePyPi,
   178  				Locations: []string{"testdata/hash/requirements.txt"},
   179  				Metadata:  &requirementsextractor.Metadata{VersionComparator: "==", HashCheckingModeValues: []string{"sha256:456"}, Requirement: "hash2==2.0.0"},
   180  				Plugins:   []string{"python/requirements"},
   181  			},
   182  		},
   183  	}
   184  	sort.Slice(inv.Packages, func(i, j int) bool {
   185  		return inv.Packages[i].Name < inv.Packages[j].Name
   186  	})
   187  	if diff := cmp.Diff(wantInventory, inv); diff != "" {
   188  		t.Errorf("%s.Enrich() diff (-want +got):\n%s", enricher.Name(), diff)
   189  	}
   190  }