github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/misc/vscodeextensions/vscodeextensions_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 vscodeextensions_test 16 17 import ( 18 "os" 19 "runtime" 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/misc/vscodeextensions" 26 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 27 "github.com/google/osv-scalibr/inventory" 28 "github.com/google/osv-scalibr/purl" 29 "github.com/google/osv-scalibr/testing/extracttest" 30 ) 31 32 func TestExtractor_FileRequired(t *testing.T) { 33 tests := []struct { 34 name string 35 inputPath string 36 want bool 37 separator rune 38 }{ 39 { 40 inputPath: "", want: false, 41 }, 42 { 43 inputPath: "/home/username/.vscode/extensions/extensions.json", want: true, 44 }, 45 { 46 inputPath: "/home/username/.vscode/extensions/bad.json", want: false, 47 }, 48 { 49 inputPath: "/home/username/.vscode/extensions/bad.json", want: false, 50 }, 51 { 52 inputPath: "/home/username/.vscode/extensions/bad.json", want: false, 53 }, 54 { 55 inputPath: "C:\\Users\\username\\.vscode\\extensions\\bad.json", want: false, 56 separator: '\\', 57 }, 58 { 59 inputPath: "C:\\Users\\username\\.vscode\\extensions\\extensions.json", want: true, 60 separator: '\\', 61 }, 62 { 63 inputPath: "/home/username/.vscode/extensions/extensions/badjson", want: false, 64 }, 65 { 66 inputPath: "/home/username/.vscode/extensions/bad/extensions.json", want: false, 67 }, 68 } 69 for _, tt := range tests { 70 t.Run(tt.inputPath, func(t *testing.T) { 71 if tt.separator == 0 { 72 tt.separator = '/' 73 } 74 75 if !os.IsPathSeparator(uint8(tt.separator)) { 76 t.Skipf("Skipping test on %s", runtime.GOOS) 77 } 78 79 e := vscodeextensions.Extractor{} 80 got := e.FileRequired(simplefileapi.New(tt.inputPath, nil)) 81 if got != tt.want { 82 t.Errorf("FileRequired(%s) got = %v, want %v", tt.inputPath, got, tt.want) 83 } 84 }) 85 } 86 } 87 88 func TestExtractor_Extract(t *testing.T) { 89 tests := []extracttest.TestTableEntry{ 90 { 91 Name: "invalid", 92 InputConfig: extracttest.ScanInputMockConfig{ 93 Path: "testdata/invalid.json", 94 }, 95 WantErr: extracttest.ContainsErrStr{Str: "could not extract"}, 96 }, 97 { 98 Name: "one extension", 99 InputConfig: extracttest.ScanInputMockConfig{ 100 Path: "testdata/one-extension.json", 101 }, 102 WantPackages: []*extractor.Package{ 103 { 104 Name: "ms-vscode.cpptools", 105 Version: "1.23.6", 106 PURLType: purl.TypeGeneric, 107 Locations: []string{ 108 "/home/username/.vscode/extensions/ms-vscode.cpptools-1.23.6-linux-arm64", 109 "testdata/one-extension.json", 110 }, 111 Metadata: &vscodeextensions.Metadata{ 112 ID: "690b692e-e8a9-493f-b802-8089d50ac1b2", 113 PublisherID: "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", 114 PublisherDisplayName: "Microsoft", 115 TargetPlatform: "linux-arm64", 116 InstalledTimestamp: 1741171252441, 117 }, 118 }, 119 }, 120 }, 121 { 122 Name: "extensions", 123 InputConfig: extracttest.ScanInputMockConfig{ 124 Path: "testdata/extensions.json", 125 }, 126 WantPackages: []*extractor.Package{ 127 { 128 Name: "golang.go", 129 Version: "0.46.1", 130 PURLType: purl.TypeGeneric, 131 Locations: []string{"/home/username/.vscode/extensions/golang.go-0.46.1", "testdata/extensions.json"}, 132 Metadata: &vscodeextensions.Metadata{ 133 ID: "d6f6cfea-4b6f-41f4-b571-6ad2ab7918da", 134 PublisherID: "dbf6ae0a-da75-4167-ac8b-75b4512f2153", 135 PublisherDisplayName: "Go Team at Google", 136 TargetPlatform: "undefined", 137 InstalledTimestamp: 1741172422528, 138 }, 139 }, 140 { 141 Name: "google.geminicodeassist", 142 Version: "2.28.0", 143 PURLType: purl.TypeGeneric, 144 Locations: []string{ 145 "/home/username/.vscode/extensions/google.geminicodeassist-2.28.0", 146 "testdata/extensions.json", 147 }, 148 Metadata: &vscodeextensions.Metadata{ 149 ID: "51643712-2cb2-4384-b7cc-d55b01b8274b", 150 PublisherID: "93a45bde-b507-401c-9deb-7a098ebcded8", 151 PublisherDisplayName: "Google", 152 TargetPlatform: "undefined", 153 InstalledTimestamp: 1741172541483, 154 }, 155 }, 156 { 157 Name: "googlecloudtools.cloudcode", 158 Version: "2.27.0", 159 PURLType: purl.TypeGeneric, 160 Locations: []string{ 161 "/home/username/.vscode/extensions/googlecloudtools.cloudcode-2.27.0", 162 "testdata/extensions.json", 163 }, 164 Metadata: &vscodeextensions.Metadata{ 165 ID: "5e8803a2-3dc8-42b3-9c5f-ea9d37828c03", 166 PublisherID: "f24fd523-af08-49d8-bb0b-f4eda502706e", 167 PublisherDisplayName: "Google Cloud", 168 TargetPlatform: "undefined", 169 InstalledTimestamp: 1741172563601, 170 }, 171 }, 172 { 173 Name: "ms-vscode.cpptools", 174 Version: "1.23.6", 175 PURLType: purl.TypeGeneric, 176 Locations: []string{ 177 "/home/username/.vscode/extensions/ms-vscode.cpptools-1.23.6-linux-arm64", 178 "testdata/extensions.json", 179 }, 180 Metadata: &vscodeextensions.Metadata{ 181 ID: "690b692e-e8a9-493f-b802-8089d50ac1b2", 182 PublisherID: "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", 183 PublisherDisplayName: "Microsoft", 184 TargetPlatform: "linux-arm64", 185 InstalledTimestamp: 1741171252441, 186 }, 187 }, 188 }, 189 }, 190 { 191 Name: "extensions on windows", 192 InputConfig: extracttest.ScanInputMockConfig{ 193 Path: "testdata/extensions-windows.json", 194 }, 195 WantPackages: []*extractor.Package{ 196 { 197 Name: "ms-python.debugpy", 198 Version: "2025.4.0", 199 PURLType: purl.TypeGeneric, 200 Locations: []string{ 201 "/c:/Users/username/.vscode/extensions/ms-python.debugpy-2025.4.0-win32-arm64", 202 "testdata/extensions-windows.json", 203 }, 204 Metadata: &vscodeextensions.Metadata{ 205 ID: "4bd5d2c9-9d65-401a-b0b2-7498d9f17615", 206 PublisherID: "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8", 207 PublisherDisplayName: "Microsoft", 208 TargetPlatform: "win32-arm64", 209 InstalledTimestamp: 1741259706875, 210 }, 211 }, 212 { 213 Name: "ms-python.python", 214 Version: "2025.2.0", 215 PURLType: purl.TypeGeneric, 216 Locations: []string{ 217 "/c:/Users/username/.vscode/extensions/ms-python.python-2025.2.0-win32-arm64", 218 "testdata/extensions-windows.json", 219 }, 220 Metadata: &vscodeextensions.Metadata{ 221 ID: "f1f59ae4-9318-4f3c-a9b5-81b2eaa5f8a5", 222 PublisherID: "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8", 223 PublisherDisplayName: "Microsoft", 224 TargetPlatform: "win32-arm64", 225 InstalledTimestamp: 1741259706874, 226 }, 227 }, 228 { 229 Name: "ms-vscode.cpptools", 230 Version: "1.23.6", 231 PURLType: purl.TypeGeneric, 232 Locations: []string{ 233 "/c:/Users/username/.vscode/extensions/ms-vscode.cpptools-1.23.6-win32-arm64", 234 "testdata/extensions-windows.json", 235 }, 236 Metadata: &vscodeextensions.Metadata{ 237 ID: "690b692e-e8a9-493f-b802-8089d50ac1b2", 238 PublisherID: "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", 239 PublisherDisplayName: "Microsoft", 240 TargetPlatform: "win32-arm64", 241 InstalledTimestamp: 1741259863413, 242 }, 243 }, 244 }, 245 }, 246 } 247 248 for _, tt := range tests { 249 t.Run(tt.Name, func(t *testing.T) { 250 extr := vscodeextensions.New() 251 252 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 253 defer extracttest.CloseTestScanInput(t, scanInput) 254 255 got, err := extr.Extract(t.Context(), &scanInput) 256 257 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 258 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 259 return 260 } 261 262 want := inventory.Inventory{Packages: tt.WantPackages} 263 if diff := cmp.Diff(want, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 264 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 265 } 266 }) 267 } 268 }