github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/php/composerlock/composerlock_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 composerlock_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/php/composerlock"
    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 TestExtractor_FileRequired(t *testing.T) {
    32  	tests := []struct {
    33  		name      string
    34  		inputPath string
    35  		want      bool
    36  	}{
    37  		{
    38  			name:      "empty name",
    39  			inputPath: "",
    40  			want:      false,
    41  		},
    42  		{
    43  			name:      "composer.lock from root",
    44  			inputPath: "composer.lock",
    45  			want:      true,
    46  		},
    47  		{
    48  			name:      "composer.lock from subpath",
    49  			inputPath: "path/to/my/composer.lock",
    50  			want:      true,
    51  		},
    52  		{
    53  			name:      "composer.lock as a dir",
    54  			inputPath: "path/to/my/composer.lock/file",
    55  			want:      false,
    56  		},
    57  		{
    58  			name:      "composer.lock with additional extension",
    59  			inputPath: "path/to/my/composer.lock.file",
    60  			want:      false,
    61  		},
    62  		{
    63  			name:      "composer.lock as substring",
    64  			inputPath: "path.to.my.composer.lock",
    65  			want:      false,
    66  		},
    67  	}
    68  	for _, tt := range tests {
    69  		t.Run(tt.name, func(t *testing.T) {
    70  			e := composerlock.Extractor{}
    71  			got := e.FileRequired(simplefileapi.New(tt.inputPath, nil))
    72  			if got != tt.want {
    73  				t.Errorf("FileRequired(%s, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want)
    74  			}
    75  		})
    76  	}
    77  }
    78  
    79  func TestExtractor_Extract(t *testing.T) {
    80  	tests := []extracttest.TestTableEntry{
    81  		{
    82  			Name: "invalid json",
    83  			InputConfig: extracttest.ScanInputMockConfig{
    84  				Path: "testdata/not-json.txt",
    85  			},
    86  			WantPackages: nil,
    87  			WantErr:      extracttest.ContainsErrStr{Str: "could not extract"},
    88  		},
    89  		{
    90  			Name: "no packages",
    91  			InputConfig: extracttest.ScanInputMockConfig{
    92  				Path: "testdata/empty.json",
    93  			},
    94  			WantPackages: []*extractor.Package{},
    95  		},
    96  		{
    97  			Name: "one package",
    98  			InputConfig: extracttest.ScanInputMockConfig{
    99  				Path: "testdata/one-package.json",
   100  			},
   101  			WantPackages: []*extractor.Package{
   102  				{
   103  					Name:      "sentry/sdk",
   104  					Version:   "2.0.4",
   105  					PURLType:  purl.TypeComposer,
   106  					Locations: []string{"testdata/one-package.json"},
   107  					SourceCode: &extractor.SourceCodeIdentifier{
   108  						Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874",
   109  					},
   110  					Metadata: osv.DepGroupMetadata{
   111  						DepGroupVals: []string{},
   112  					},
   113  				},
   114  			},
   115  		},
   116  		{
   117  			Name: "one package dev",
   118  			InputConfig: extracttest.ScanInputMockConfig{
   119  				Path: "testdata/one-package-dev.json",
   120  			},
   121  			WantPackages: []*extractor.Package{
   122  				{
   123  					Name:      "sentry/sdk",
   124  					Version:   "2.0.4",
   125  					PURLType:  purl.TypeComposer,
   126  					Locations: []string{"testdata/one-package-dev.json"},
   127  					SourceCode: &extractor.SourceCodeIdentifier{
   128  						Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874",
   129  					},
   130  					Metadata: osv.DepGroupMetadata{
   131  						DepGroupVals: []string{"dev"},
   132  					},
   133  				},
   134  			},
   135  		},
   136  		{
   137  			Name: "two packages",
   138  			InputConfig: extracttest.ScanInputMockConfig{
   139  				Path: "testdata/two-packages.json",
   140  			},
   141  			WantPackages: []*extractor.Package{
   142  				{
   143  					Name:      "sentry/sdk",
   144  					Version:   "2.0.4",
   145  					PURLType:  purl.TypeComposer,
   146  					Locations: []string{"testdata/two-packages.json"},
   147  					SourceCode: &extractor.SourceCodeIdentifier{
   148  						Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874",
   149  					},
   150  					Metadata: osv.DepGroupMetadata{
   151  						DepGroupVals: []string{},
   152  					},
   153  				},
   154  				{
   155  					Name:      "theseer/tokenizer",
   156  					Version:   "1.1.3",
   157  					PURLType:  purl.TypeComposer,
   158  					Locations: []string{"testdata/two-packages.json"},
   159  					SourceCode: &extractor.SourceCodeIdentifier{
   160  						Commit: "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
   161  					},
   162  					Metadata: osv.DepGroupMetadata{
   163  						DepGroupVals: []string{"dev"},
   164  					},
   165  				},
   166  			},
   167  		},
   168  		{
   169  			Name: "two packages alt",
   170  			InputConfig: extracttest.ScanInputMockConfig{
   171  				Path: "testdata/two-packages-alt.json",
   172  			},
   173  			WantPackages: []*extractor.Package{
   174  				{
   175  					Name:      "sentry/sdk",
   176  					Version:   "2.0.4",
   177  					PURLType:  purl.TypeComposer,
   178  					Locations: []string{"testdata/two-packages-alt.json"},
   179  					SourceCode: &extractor.SourceCodeIdentifier{
   180  						Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874",
   181  					},
   182  					Metadata: osv.DepGroupMetadata{
   183  						DepGroupVals: []string{},
   184  					},
   185  				},
   186  				{
   187  					Name:      "theseer/tokenizer",
   188  					Version:   "1.1.3",
   189  					PURLType:  purl.TypeComposer,
   190  					Locations: []string{"testdata/two-packages-alt.json"},
   191  					SourceCode: &extractor.SourceCodeIdentifier{
   192  						Commit: "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
   193  					},
   194  					Metadata: osv.DepGroupMetadata{
   195  						DepGroupVals: []string{},
   196  					},
   197  				},
   198  			},
   199  		},
   200  		{
   201  			Name: "drupal_packages",
   202  			InputConfig: extracttest.ScanInputMockConfig{
   203  				Path: "testdata/drupal-packages.json",
   204  			},
   205  			WantPackages: []*extractor.Package{
   206  				{
   207  					Name:      "drupal/core",
   208  					Version:   "10.4.5",
   209  					PURLType:  purl.TypeComposer,
   210  					Locations: []string{"testdata/drupal-packages.json"},
   211  					SourceCode: &extractor.SourceCodeIdentifier{
   212  						Commit: "5247dbaa65b42b601058555f4a8b2bd541f5611f",
   213  					},
   214  					Metadata: osv.DepGroupMetadata{
   215  						DepGroupVals: []string{},
   216  					},
   217  				},
   218  				{
   219  					Name:      "drupal/tfa",
   220  					Version:   "2.0.0-alpha4",
   221  					PURLType:  purl.TypeComposer,
   222  					Locations: []string{"testdata/drupal-packages.json"},
   223  					SourceCode: &extractor.SourceCodeIdentifier{
   224  						Commit: "",
   225  					},
   226  					Metadata: osv.DepGroupMetadata{
   227  						DepGroupVals: []string{},
   228  					},
   229  				},
   230  				{
   231  					Name:      "drupal/field_time",
   232  					Version:   "1.0.0-beta5",
   233  					PURLType:  purl.TypeComposer,
   234  					Locations: []string{"testdata/drupal-packages.json"},
   235  					SourceCode: &extractor.SourceCodeIdentifier{
   236  						Commit: "",
   237  					},
   238  					Metadata: osv.DepGroupMetadata{
   239  						DepGroupVals: []string{"dev"},
   240  					},
   241  				},
   242  				{
   243  					Name:      "theseer/tokenizer",
   244  					Version:   "1.1.3",
   245  					PURLType:  purl.TypeComposer,
   246  					Locations: []string{"testdata/drupal-packages.json"},
   247  					SourceCode: &extractor.SourceCodeIdentifier{
   248  						Commit: "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
   249  					},
   250  					Metadata: osv.DepGroupMetadata{
   251  						DepGroupVals: []string{},
   252  					},
   253  				},
   254  			},
   255  		},
   256  	}
   257  
   258  	for _, tt := range tests {
   259  		t.Run(tt.Name, func(t *testing.T) {
   260  			extr := composerlock.Extractor{}
   261  
   262  			scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig)
   263  			defer extracttest.CloseTestScanInput(t, scanInput)
   264  
   265  			got, err := extr.Extract(t.Context(), &scanInput)
   266  
   267  			if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" {
   268  				t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   269  				return
   270  			}
   271  
   272  			wantInv := inventory.Inventory{Packages: tt.WantPackages}
   273  			if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" {
   274  				t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   275  			}
   276  		})
   277  	}
   278  }