github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/containers/k8simage/k8simage_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 k8simage_test
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/google/go-cmp/cmp/cmpopts"
    23  	"github.com/google/osv-scalibr/extractor"
    24  	"github.com/google/osv-scalibr/extractor/filesystem/containers/k8simage"
    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 TestExtract(t *testing.T) {
    32  	tests := []struct {
    33  		name         string
    34  		path         string
    35  		cfg          k8simage.Config
    36  		wantPackages []*extractor.Package
    37  	}{
    38  		{
    39  			name: "comprehensive_multi-resource_test_file",
    40  			path: "testdata/comprehensive-test.yaml",
    41  			cfg:  k8simage.DefaultConfig(),
    42  			wantPackages: []*extractor.Package{
    43  				{
    44  					Name:      "tag1",
    45  					Version:   "sha256:b692a91e4e531d2a9cd8e8c3b1e6d5c7f9d2e5a3b1c8d7f4e6a9b2c5d8f1e4a7",
    46  					Locations: []string{"testdata/comprehensive-test.yaml"},
    47  					PURLType:  purl.TypeK8s,
    48  				},
    49  				{
    50  					Name:      "tag2",
    51  					Version:   "2.0.0",
    52  					Locations: []string{"testdata/comprehensive-test.yaml"},
    53  					PURLType:  purl.TypeK8s,
    54  				},
    55  				{
    56  					Name:      "tag3",
    57  					Version:   "3.0.0",
    58  					Locations: []string{"testdata/comprehensive-test.yaml"},
    59  					PURLType:  purl.TypeK8s,
    60  				},
    61  				{
    62  					Name:      "tag4.io/prometheus/node-exporter",
    63  					Version:   "v4.0.0",
    64  					Locations: []string{"testdata/comprehensive-test.yaml"},
    65  					PURLType:  purl.TypeK8s,
    66  				},
    67  				{
    68  					Name:      "tag5",
    69  					Version:   "5.0.0",
    70  					Locations: []string{"testdata/comprehensive-test.yaml"},
    71  					PURLType:  purl.TypeK8s,
    72  				},
    73  				{
    74  					Name:      "tag6",
    75  					Version:   "6.0.0",
    76  					Locations: []string{"testdata/comprehensive-test.yaml"},
    77  					PURLType:  purl.TypeK8s,
    78  				},
    79  				{
    80  					Name:      "tag7:5000/my-app",
    81  					Version:   "dev",
    82  					Locations: []string{"testdata/comprehensive-test.yaml"},
    83  					PURLType:  purl.TypeK8s,
    84  				},
    85  				{
    86  					Name:      "centos",
    87  					Version:   "latest",
    88  					Locations: []string{"testdata/comprehensive-test.yaml"},
    89  					PURLType:  purl.TypeK8s,
    90  				},
    91  			},
    92  		},
    93  		{
    94  			name:         "larger than size limit",
    95  			path:         "testdata/comprehensive-test.yaml",
    96  			cfg:          k8simage.Config{MaxFileSizeBytes: 1},
    97  			wantPackages: nil,
    98  		},
    99  	}
   100  
   101  	for _, tc := range tests {
   102  		t.Run(tc.name, func(t *testing.T) {
   103  			extr := k8simage.New(tc.cfg)
   104  
   105  			input := extracttest.GenerateScanInputMock(t, extracttest.ScanInputMockConfig{
   106  				Path: tc.path,
   107  			})
   108  			defer extracttest.CloseTestScanInput(t, input)
   109  
   110  			got, err := extr.Extract(context.Background(), &input)
   111  			if err != nil {
   112  				t.Fatalf("%s.Extract(%q) failed: %v", extr.Name(), tc.path, err)
   113  			}
   114  
   115  			wantInv := inventory.Inventory{Packages: tc.wantPackages}
   116  			if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" {
   117  				t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tc.path, diff)
   118  			}
   119  		})
   120  	}
   121  }
   122  
   123  func TestFileRequired(t *testing.T) {
   124  	extr := k8simage.New(k8simage.DefaultConfig())
   125  
   126  	tests := []struct {
   127  		name string
   128  		path string
   129  		want bool
   130  	}{
   131  		{
   132  			name: "yaml_extension",
   133  			path: "deployment.yaml",
   134  			want: true,
   135  		},
   136  		{
   137  			name: "yml_extension",
   138  			path: "service.yml",
   139  			want: true,
   140  		},
   141  		{
   142  			name: "other_extension",
   143  			path: "config.json",
   144  			want: false,
   145  		},
   146  	}
   147  
   148  	for _, tc := range tests {
   149  		t.Run(tc.name, func(t *testing.T) {
   150  			isRequired := extr.FileRequired(simplefileapi.New(tc.path, nil))
   151  			if isRequired != tc.want {
   152  				t.Fatalf("FileRequired(%s): got %v, want %v", tc.path, isRequired, tc.want)
   153  			}
   154  		})
   155  	}
   156  }
   157  
   158  func TestExtract_empty_results(t *testing.T) {
   159  	tests := []struct {
   160  		name string
   161  		path string
   162  		cfg  k8simage.Config
   163  	}{
   164  		{
   165  			name: "empty_image_fields_should_result_in_no_packages",
   166  			path: "testdata/comprehensive-test-failures.yaml",
   167  			cfg:  k8simage.DefaultConfig(),
   168  		},
   169  	}
   170  
   171  	for _, tc := range tests {
   172  		t.Run(tc.name, func(t *testing.T) {
   173  			extr := k8simage.New(tc.cfg)
   174  
   175  			input := extracttest.GenerateScanInputMock(t, extracttest.ScanInputMockConfig{
   176  				Path: tc.path,
   177  			})
   178  			defer extracttest.CloseTestScanInput(t, input)
   179  
   180  			got, err := extr.Extract(context.Background(), &input)
   181  			if err != nil {
   182  				t.Fatalf("%s.Extract(%q) failed: %v", extr.Name(), tc.path, err)
   183  			}
   184  
   185  			// Should have no packages since all image fields are empty or with errors
   186  			if len(got.Packages) != 0 {
   187  				t.Errorf("%s.Extract(%q): got %d packages, want 0. Packages: %+v",
   188  					extr.Name(), tc.path, len(got.Packages), got.Packages)
   189  			}
   190  		})
   191  	}
   192  }
   193  
   194  func TestExtract_skip_non_k8s_files(t *testing.T) {
   195  	tests := []struct {
   196  		name string
   197  		path string
   198  	}{
   199  		{
   200  			name: "invalid_YAML_syntax",
   201  			path: "testdata/yaml-parsing-error.yaml",
   202  		},
   203  	}
   204  
   205  	for _, tc := range tests {
   206  		t.Run(tc.name, func(t *testing.T) {
   207  			extr := k8simage.New(k8simage.DefaultConfig())
   208  
   209  			input := extracttest.GenerateScanInputMock(t, extracttest.ScanInputMockConfig{
   210  				Path: tc.path,
   211  			})
   212  			defer extracttest.CloseTestScanInput(t, input)
   213  
   214  			inventory, err := extr.Extract(context.Background(), &input)
   215  			if err != nil {
   216  				t.Fatalf("Extract(): %v", err)
   217  			}
   218  			if len(inventory.Packages) != 0 {
   219  				t.Errorf("Extract(): got %v, want empty package", inventory.Packages)
   220  			}
   221  		})
   222  	}
   223  }