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  }