github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/swift/podfilelock/podfilelock_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 podfilelock_test 16 17 import ( 18 "io/fs" 19 "path/filepath" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/google/go-cmp/cmp/cmpopts" 24 "github.com/google/osv-scalibr/extractor" 25 "github.com/google/osv-scalibr/extractor/filesystem" 26 "github.com/google/osv-scalibr/extractor/filesystem/internal/units" 27 "github.com/google/osv-scalibr/extractor/filesystem/language/swift/podfilelock" 28 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 29 "github.com/google/osv-scalibr/inventory" 30 "github.com/google/osv-scalibr/purl" 31 "github.com/google/osv-scalibr/stats" 32 "github.com/google/osv-scalibr/testing/extracttest" 33 "github.com/google/osv-scalibr/testing/fakefs" 34 "github.com/google/osv-scalibr/testing/testcollector" 35 ) 36 37 func TestNew(t *testing.T) { 38 tests := []struct { 39 name string 40 cfg podfilelock.Config 41 wantCfg podfilelock.Config 42 }{ 43 { 44 name: "default", 45 cfg: podfilelock.DefaultConfig(), 46 wantCfg: podfilelock.Config{ 47 MaxFileSizeBytes: 10 * units.MiB, 48 }, 49 }, 50 { 51 name: "custom", 52 cfg: podfilelock.Config{ 53 MaxFileSizeBytes: 10, 54 }, 55 wantCfg: podfilelock.Config{ 56 MaxFileSizeBytes: 10, 57 }, 58 }, 59 } 60 61 for _, tt := range tests { 62 t.Run(tt.name, func(t *testing.T) { 63 got := podfilelock.New(tt.cfg) 64 if diff := cmp.Diff(tt.wantCfg, got.Config()); diff != "" { 65 t.Errorf("New(%+v).Config(): (-want +got):\n%s", tt.cfg, diff) 66 } 67 }) 68 } 69 } 70 71 func TestFileRequired(t *testing.T) { 72 tests := []struct { 73 name string 74 path string 75 fileSizeBytes int64 76 maxFileSizeBytes int64 77 wantRequired bool 78 wantResultMetric stats.FileRequiredResult 79 }{ 80 { 81 name: "Podfile.lock file", 82 path: "Podfile.lock", 83 wantRequired: true, 84 wantResultMetric: stats.FileRequiredResultOK, 85 }, 86 { 87 name: "path Podfile.lock file", 88 path: "path/to/my/Podfile.lock", 89 wantRequired: true, 90 wantResultMetric: stats.FileRequiredResultOK, 91 }, 92 { 93 name: "file not required", 94 path: "test.lock", 95 wantRequired: false, 96 }, 97 { 98 name: "Podfile.lock file required if file size < max file size", 99 path: "Podfile.lock", 100 fileSizeBytes: 100 * units.KiB, 101 maxFileSizeBytes: 1000 * units.KiB, 102 wantRequired: true, 103 wantResultMetric: stats.FileRequiredResultOK, 104 }, 105 { 106 name: "Podfile.lock file required if file size == max file size", 107 path: "Podfile.lock", 108 fileSizeBytes: 1000 * units.KiB, 109 maxFileSizeBytes: 1000 * units.KiB, 110 wantRequired: true, 111 wantResultMetric: stats.FileRequiredResultOK, 112 }, 113 { 114 name: "Podfile.lock file not required if file size > max file size", 115 path: "Podfile.lock", 116 fileSizeBytes: 1000 * units.KiB, 117 maxFileSizeBytes: 100 * units.KiB, 118 wantRequired: false, 119 wantResultMetric: stats.FileRequiredResultSizeLimitExceeded, 120 }, 121 { 122 name: "Podfile.lock file required if max file size set to 0", 123 path: "Podfile.lock", 124 fileSizeBytes: 100 * units.KiB, 125 maxFileSizeBytes: 0, 126 wantRequired: true, 127 wantResultMetric: stats.FileRequiredResultOK, 128 }, 129 } 130 131 for _, tt := range tests { 132 t.Run(tt.name, func(t *testing.T) { 133 collector := testcollector.New() 134 var e filesystem.Extractor = podfilelock.New(podfilelock.Config{ 135 Stats: collector, 136 MaxFileSizeBytes: tt.maxFileSizeBytes, 137 }) 138 139 fileSizeBytes := tt.fileSizeBytes 140 if fileSizeBytes == 0 { 141 fileSizeBytes = 1000 142 } 143 144 isRequired := e.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{ 145 FileName: filepath.Base(tt.path), 146 FileMode: fs.ModePerm, 147 FileSize: fileSizeBytes, 148 })) 149 if isRequired != tt.wantRequired { 150 t.Fatalf("FileRequired(%s): got %v, want %v", tt.path, isRequired, tt.wantRequired) 151 } 152 153 gotResultMetric := collector.FileRequiredResult(tt.path) 154 if tt.wantResultMetric != "" && gotResultMetric != tt.wantResultMetric { 155 t.Errorf("FileRequired(%s) recorded result metric %v, want result metric %v", tt.path, gotResultMetric, tt.wantResultMetric) 156 } 157 }) 158 } 159 } 160 161 func TestExtract(t *testing.T) { 162 tests := []extracttest.TestTableEntry{ 163 { 164 Name: "valid Podfile.lock file, map[string] case", 165 InputConfig: extracttest.ScanInputMockConfig{ 166 Path: "testdata/valid", 167 }, 168 WantPackages: []*extractor.Package{ 169 { 170 Name: "GlossButtonNode", 171 Version: "3.1.2", 172 PURLType: purl.TypeCocoapods, 173 Locations: []string{"testdata/valid"}, 174 }, 175 { 176 Name: "PINCache", 177 Version: "3.0.3", 178 PURLType: purl.TypeCocoapods, 179 Locations: []string{"testdata/valid"}, 180 }, 181 }, 182 }, 183 { 184 Name: "valid Podfile.lock file, string case", 185 InputConfig: extracttest.ScanInputMockConfig{ 186 Path: "testdata/valid2", 187 }, 188 WantPackages: []*extractor.Package{ 189 { 190 Name: "GlossButtonNode", 191 Version: "3.1.2", 192 PURLType: purl.TypeCocoapods, 193 Locations: []string{"testdata/valid2"}, 194 }, 195 { 196 Name: "PINCache", 197 Version: "3.0.3", 198 PURLType: purl.TypeCocoapods, 199 Locations: []string{"testdata/valid2"}, 200 }, 201 { 202 Name: "Reveal-SDK", 203 Version: "1.5.0", 204 PURLType: purl.TypeCocoapods, 205 Locations: []string{"testdata/valid2"}, 206 }, 207 { 208 Name: "SwiftGen", 209 Version: "6.0.0", 210 PURLType: purl.TypeCocoapods, 211 Locations: []string{"testdata/valid2"}, 212 }, 213 }, 214 }, 215 { 216 Name: "Podfile.lock file not valid", 217 InputConfig: extracttest.ScanInputMockConfig{ 218 Path: "testdata/invalid", 219 }, 220 WantErr: cmpopts.AnyError, 221 }, 222 { 223 Name: "Podfile.lock file empty", 224 InputConfig: extracttest.ScanInputMockConfig{ 225 Path: "testdata/empty", 226 }, 227 WantErr: cmpopts.AnyError, 228 }, 229 } 230 231 for _, tt := range tests { 232 t.Run(tt.Name, func(t *testing.T) { 233 collector := testcollector.New() 234 var e filesystem.Extractor = podfilelock.New(podfilelock.Config{ 235 Stats: collector, 236 MaxFileSizeBytes: 100, 237 }) 238 239 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 240 defer extracttest.CloseTestScanInput(t, scanInput) 241 242 got, err := e.Extract(t.Context(), &scanInput) 243 244 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 245 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", e.Name(), tt.InputConfig.Path, diff) 246 return 247 } 248 249 wantInv := inventory.Inventory{Packages: tt.WantPackages} 250 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 251 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", e.Name(), tt.InputConfig.Path, diff) 252 } 253 }) 254 } 255 }