github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/dotnet/packageslockjson/packageslockjson_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 packageslockjson_test
    16  
    17  import (
    18  	"io/fs"
    19  	"os"
    20  	"path/filepath"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	"github.com/google/osv-scalibr/extractor"
    26  	"github.com/google/osv-scalibr/extractor/filesystem"
    27  	"github.com/google/osv-scalibr/extractor/filesystem/internal/units"
    28  	"github.com/google/osv-scalibr/extractor/filesystem/language/dotnet/packageslockjson"
    29  	"github.com/google/osv-scalibr/extractor/filesystem/simplefileapi"
    30  	scalibrfs "github.com/google/osv-scalibr/fs"
    31  	"github.com/google/osv-scalibr/inventory"
    32  	"github.com/google/osv-scalibr/purl"
    33  	"github.com/google/osv-scalibr/stats"
    34  	"github.com/google/osv-scalibr/testing/fakefs"
    35  	"github.com/google/osv-scalibr/testing/testcollector"
    36  )
    37  
    38  func TestFileRequired(t *testing.T) {
    39  	tests := []struct {
    40  		name             string
    41  		path             string
    42  		fileSizeBytes    int64
    43  		maxFileSizeBytes int64
    44  		wantRequired     bool
    45  		wantResultMetric stats.FileRequiredResult
    46  	}{
    47  		{
    48  			name:             "some project's packages.lock.json",
    49  			path:             "project/packages.lock.json",
    50  			wantRequired:     true,
    51  			wantResultMetric: stats.FileRequiredResultOK,
    52  		},
    53  		{
    54  			name:             "just packages.lock.json",
    55  			path:             "packages.lock.json",
    56  			wantRequired:     true,
    57  			wantResultMetric: stats.FileRequiredResultOK,
    58  		},
    59  		{
    60  			name:         "non packages.lock.json",
    61  			path:         "project/some.csproj",
    62  			wantRequired: false,
    63  		},
    64  		{
    65  			name:             "packages.lock.json required if file size < max file size",
    66  			path:             "project/packages.lock.json",
    67  			fileSizeBytes:    100 * units.KiB,
    68  			maxFileSizeBytes: 1000 * units.KiB,
    69  			wantRequired:     true,
    70  			wantResultMetric: stats.FileRequiredResultOK,
    71  		},
    72  		{
    73  			name:             "packages.lock.json required if file size == max file size",
    74  			path:             "project/packages.lock.json",
    75  			fileSizeBytes:    1000 * units.KiB,
    76  			maxFileSizeBytes: 1000 * units.KiB,
    77  			wantRequired:     true,
    78  			wantResultMetric: stats.FileRequiredResultOK,
    79  		},
    80  		{
    81  			name:             "packages.lock.json not required if file size > max file size",
    82  			path:             "project/packages.lock.json",
    83  			fileSizeBytes:    1000 * units.KiB,
    84  			maxFileSizeBytes: 100 * units.KiB,
    85  			wantRequired:     false,
    86  			wantResultMetric: stats.FileRequiredResultSizeLimitExceeded,
    87  		},
    88  		{
    89  			name:             "packages.lock.json required if max file size set to 0",
    90  			path:             "project/packages.lock.json",
    91  			fileSizeBytes:    1000 * units.KiB,
    92  			maxFileSizeBytes: 0,
    93  			wantRequired:     true,
    94  			wantResultMetric: stats.FileRequiredResultOK,
    95  		},
    96  	}
    97  
    98  	for _, test := range tests {
    99  		t.Run(test.name, func(t *testing.T) {
   100  			collector := testcollector.New()
   101  			var e filesystem.Extractor = packageslockjson.New(
   102  				packageslockjson.Config{
   103  					Stats:            collector,
   104  					MaxFileSizeBytes: test.maxFileSizeBytes,
   105  				},
   106  			)
   107  
   108  			// Set default size if not provided.
   109  			fileSizeBytes := test.fileSizeBytes
   110  			if fileSizeBytes == 0 {
   111  				fileSizeBytes = 100 * units.KiB
   112  			}
   113  
   114  			isRequired := e.FileRequired(simplefileapi.New(test.path, fakefs.FakeFileInfo{
   115  				FileName: filepath.Base(test.path),
   116  				FileMode: fs.ModePerm,
   117  				FileSize: fileSizeBytes,
   118  			}))
   119  			if isRequired != test.wantRequired {
   120  				t.Fatalf("FileRequired(%s): got %v, want %v", test.path, isRequired, test.wantRequired)
   121  			}
   122  
   123  			gotResultMetric := collector.FileRequiredResult(test.path)
   124  			if gotResultMetric != test.wantResultMetric {
   125  				t.Errorf("FileRequired(%s) recorded result metric %v, want result metric %v", test.path, gotResultMetric, test.wantResultMetric)
   126  			}
   127  		})
   128  	}
   129  }
   130  
   131  func TestExtractor(t *testing.T) {
   132  	tests := []struct {
   133  		name             string
   134  		path             string
   135  		wantPackages     []*extractor.Package
   136  		wantErr          error
   137  		wantResultMetric stats.FileExtractedResult
   138  	}{
   139  		{
   140  			name: "valid_packages.lock.json",
   141  			path: "testdata/valid/packages.lock.json",
   142  			wantPackages: []*extractor.Package{
   143  				{
   144  					Name:      "Core.Dep",
   145  					Version:   "1.24.0",
   146  					PURLType:  purl.TypeNuget,
   147  					Locations: []string{"testdata/valid/packages.lock.json"},
   148  				},
   149  				{
   150  					Name:      "Some.Dep.One",
   151  					Version:   "1.1.1",
   152  					PURLType:  purl.TypeNuget,
   153  					Locations: []string{"testdata/valid/packages.lock.json"},
   154  				},
   155  				{
   156  					Name:      "Some.Dep.Two",
   157  					Version:   "4.6.0",
   158  					PURLType:  purl.TypeNuget,
   159  					Locations: []string{"testdata/valid/packages.lock.json"},
   160  				},
   161  				{
   162  					Name:      "Some.Dep.Three",
   163  					Version:   "1.0.2",
   164  					PURLType:  purl.TypeNuget,
   165  					Locations: []string{"testdata/valid/packages.lock.json"},
   166  				},
   167  				{
   168  					Name:      "Some.Dep.Four",
   169  					Version:   "4.5.0",
   170  					PURLType:  purl.TypeNuget,
   171  					Locations: []string{"testdata/valid/packages.lock.json"},
   172  				},
   173  				{
   174  					Name:      "Some.Longer.Name.Dep",
   175  					Version:   "4.7.2",
   176  					PURLType:  purl.TypeNuget,
   177  					Locations: []string{"testdata/valid/packages.lock.json"},
   178  				},
   179  				{
   180  					Name:      "Some.Dep.Five",
   181  					Version:   "4.7.2",
   182  					PURLType:  purl.TypeNuget,
   183  					Locations: []string{"testdata/valid/packages.lock.json"},
   184  				},
   185  				{
   186  					Name:      "Another.Longer.Name.Dep",
   187  					Version:   "4.5.4",
   188  					PURLType:  purl.TypeNuget,
   189  					Locations: []string{"testdata/valid/packages.lock.json"},
   190  				},
   191  			},
   192  			wantResultMetric: stats.FileExtractedResultSuccess,
   193  		},
   194  		{
   195  			name:             "non json input",
   196  			path:             "testdata/invalid/invalid",
   197  			wantErr:          cmpopts.AnyError,
   198  			wantResultMetric: stats.FileExtractedResultErrorUnknown,
   199  		},
   200  	}
   201  
   202  	for _, test := range tests {
   203  		t.Run(test.name, func(t *testing.T) {
   204  			collector := testcollector.New()
   205  			var e filesystem.Extractor = packageslockjson.New(packageslockjson.Config{Stats: collector})
   206  
   207  			r, err := os.Open(test.path)
   208  			defer func() {
   209  				if err = r.Close(); err != nil {
   210  					t.Errorf("Close(): %v", err)
   211  				}
   212  			}()
   213  			if err != nil {
   214  				t.Fatal(err)
   215  			}
   216  
   217  			info, err := os.Stat(test.path)
   218  			if err != nil {
   219  				t.Fatalf("Failed to stat test file: %v", err)
   220  			}
   221  
   222  			input := &filesystem.ScanInput{
   223  				FS:     scalibrfs.DirFS("."),
   224  				Path:   test.path,
   225  				Reader: r,
   226  				Info:   info,
   227  			}
   228  			got, err := e.Extract(t.Context(), input)
   229  			if !cmp.Equal(err, test.wantErr, cmpopts.EquateErrors()) {
   230  				t.Fatalf("Extract(%+v) error: got %v, want %v\n", test.name, err, test.wantErr)
   231  			}
   232  
   233  			sort := func(a, b *extractor.Package) bool { return a.Name < b.Name }
   234  			wantInv := inventory.Inventory{Packages: test.wantPackages}
   235  			if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(sort)); diff != "" {
   236  				t.Errorf("Extract(%s) (-want +got):\n%s", test.path, diff)
   237  			}
   238  
   239  			gotResultMetric := collector.FileExtractedResult(test.path)
   240  			if gotResultMetric != test.wantResultMetric {
   241  				t.Errorf("Extract(%s) recorded result metric %v, want result metric %v", test.path, gotResultMetric, test.wantResultMetric)
   242  			}
   243  
   244  			gotFileSizeMetric := collector.FileExtractedFileSize(test.path)
   245  			if gotFileSizeMetric != info.Size() {
   246  				t.Errorf("Extract(%s) recorded file size %v, want file size %v", test.path, gotFileSizeMetric, info.Size())
   247  			}
   248  		})
   249  	}
   250  }