github.com/google/osv-scalibr@v0.4.1/detector/detectorrunner/detectorrunner_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 detectorrunner_test
    16  
    17  import (
    18  	"errors"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/google/go-cmp/cmp/cmpopts"
    23  	"github.com/google/osv-scalibr/detector"
    24  	"github.com/google/osv-scalibr/detector/detectorrunner"
    25  	"github.com/google/osv-scalibr/extractor"
    26  	scalibrfs "github.com/google/osv-scalibr/fs"
    27  	"github.com/google/osv-scalibr/inventory"
    28  	"github.com/google/osv-scalibr/packageindex"
    29  	"github.com/google/osv-scalibr/plugin"
    30  	"github.com/google/osv-scalibr/stats"
    31  	fd "github.com/google/osv-scalibr/testing/fakedetector"
    32  	osvpb "github.com/ossf/osv-schema/bindings/go/osvschema"
    33  	"google.golang.org/protobuf/testing/protocmp"
    34  )
    35  
    36  func TestRun(t *testing.T) {
    37  	finding1 := &inventory.GenericFinding{
    38  		Adv: &inventory.GenericFindingAdvisory{
    39  			ID: &inventory.AdvisoryID{
    40  				Publisher: "CVE",
    41  				Reference: "CVE-1234",
    42  			},
    43  			Sev: inventory.SeverityMedium,
    44  		},
    45  	}
    46  	identicalFinding1 := &inventory.GenericFinding{
    47  		Adv: &inventory.GenericFindingAdvisory{
    48  			ID: &inventory.AdvisoryID{
    49  				Publisher: "CVE",
    50  				Reference: "CVE-1234",
    51  			},
    52  			Sev: inventory.SeverityMedium,
    53  		},
    54  	}
    55  	finding2 := &inventory.GenericFinding{
    56  		Adv: &inventory.GenericFindingAdvisory{
    57  			ID: &inventory.AdvisoryID{
    58  				Publisher: "CVE",
    59  				Reference: "CVE-5678",
    60  			},
    61  		},
    62  	}
    63  	findingNoAdvisory := &inventory.GenericFinding{}
    64  	findingNoAdvisoryID := &inventory.GenericFinding{Adv: &inventory.GenericFindingAdvisory{}}
    65  	packageVuln := &inventory.PackageVuln{
    66  		Vulnerability: &osvpb.Vulnerability{Id: "CVE-9012"},
    67  	}
    68  	det1 := fd.New().WithName("det1").WithVersion(1)
    69  	det2 := fd.New().WithName("det2").WithVersion(2)
    70  	success := &plugin.ScanStatus{Status: plugin.ScanStatusSucceeded}
    71  
    72  	testCases := []struct {
    73  		desc         string
    74  		det          []detector.Detector
    75  		wantFindings inventory.Finding
    76  		wantStatus   []*plugin.Status
    77  		wantErr      error
    78  	}{
    79  		{
    80  			desc: "Plugins_successful",
    81  			det: []detector.Detector{
    82  				det1.WithGenericFinding(finding1),
    83  				det2.WithGenericFinding(finding2),
    84  			},
    85  			wantFindings: inventory.Finding{
    86  				GenericFindings: []*inventory.GenericFinding{
    87  					withDetectorName(finding1, "det1"),
    88  					withDetectorName(finding2, "det2"),
    89  				},
    90  			},
    91  			wantStatus: []*plugin.Status{
    92  				{Name: "det1", Version: 1, Status: success},
    93  				{Name: "det2", Version: 2, Status: success},
    94  			},
    95  		},
    96  		{
    97  			desc: "One_plugin_failed",
    98  			det: []detector.Detector{
    99  				det1.WithGenericFinding(finding1),
   100  				det2.WithErr(errors.New("detection failed")),
   101  			},
   102  			wantFindings: inventory.Finding{
   103  				GenericFindings: []*inventory.GenericFinding{withDetectorName(finding1, "det1")},
   104  			},
   105  			wantStatus: []*plugin.Status{
   106  				{Name: "det1", Version: 1, Status: success},
   107  				{Name: "det2", Version: 2, Status: &plugin.ScanStatus{
   108  					Status: plugin.ScanStatusFailed, FailureReason: "detection failed",
   109  				}},
   110  			},
   111  		},
   112  		{
   113  			desc: "Duplicate_findings_with_identical_advisories",
   114  			det: []detector.Detector{
   115  				det1.WithGenericFinding(finding1),
   116  				det2.WithGenericFinding(identicalFinding1),
   117  			},
   118  			wantFindings: inventory.Finding{GenericFindings: []*inventory.GenericFinding{
   119  				withDetectorName(finding1, "det1"),
   120  				withDetectorName(finding1, "det2"),
   121  			}},
   122  			wantStatus: []*plugin.Status{
   123  				{Name: "det1", Version: 1, Status: success},
   124  				{Name: "det2", Version: 2, Status: success},
   125  			},
   126  		},
   127  		{
   128  			desc: "Duplicate_findings_with_different_advisories",
   129  			det: []detector.Detector{
   130  				det1.WithGenericFinding(finding1),
   131  				det2.WithGenericFinding(&inventory.GenericFinding{
   132  					Adv: &inventory.GenericFindingAdvisory{ID: finding1.Adv.ID, Title: "different title"},
   133  				}),
   134  			},
   135  			wantFindings: inventory.Finding{},
   136  			wantStatus: []*plugin.Status{
   137  				{Name: "det1", Version: 1, Status: success},
   138  				{Name: "det2", Version: 2, Status: success},
   139  			},
   140  			wantErr: cmpopts.AnyError,
   141  		},
   142  		{
   143  			desc: "Error_when_Advisory_is_not_set",
   144  			det: []detector.Detector{
   145  				det1.WithGenericFinding(findingNoAdvisory),
   146  			},
   147  			wantFindings: inventory.Finding{},
   148  			wantStatus: []*plugin.Status{
   149  				{Name: "det1", Version: 1, Status: success},
   150  			},
   151  			wantErr: cmpopts.AnyError,
   152  		},
   153  		{
   154  			desc: "Error_when_Advisory_ID_is_not_set",
   155  			det: []detector.Detector{
   156  				det1.WithGenericFinding(findingNoAdvisoryID),
   157  			},
   158  			wantFindings: inventory.Finding{},
   159  			wantStatus: []*plugin.Status{
   160  				{Name: "det1", Version: 1, Status: success},
   161  			},
   162  			wantErr: cmpopts.AnyError,
   163  		},
   164  		{
   165  			desc: "Package_and_generic_vulns",
   166  			det: []detector.Detector{
   167  				det1.WithGenericFinding(finding1),
   168  				det2.WithPackageVuln(packageVuln),
   169  			},
   170  			wantFindings: inventory.Finding{
   171  				GenericFindings: []*inventory.GenericFinding{withDetectorName(finding1, "det1")},
   172  				PackageVulns:    []*inventory.PackageVuln{pkgVulnWithDetectorName(packageVuln, "det2")},
   173  			},
   174  			wantStatus: []*plugin.Status{
   175  				{Name: "det1", Version: 1, Status: success},
   176  				{Name: "det2", Version: 2, Status: success},
   177  			},
   178  		},
   179  	}
   180  
   181  	for _, tc := range testCases {
   182  		t.Run(tc.desc, func(t *testing.T) {
   183  			px, _ := packageindex.New([]*extractor.Package{})
   184  			tmp := t.TempDir()
   185  			gotFindings, gotStatus, err := detectorrunner.Run(
   186  				t.Context(), stats.NoopCollector{}, tc.det, scalibrfs.RealFSScanRoot(tmp), px,
   187  			)
   188  			if diff := cmp.Diff(tc.wantErr, err, cmpopts.EquateErrors()); diff != "" {
   189  				t.Errorf("detectorrunner.Run(%v): unexpected error (-want +got):\n%s", tc.det, diff)
   190  			}
   191  			if diff := cmp.Diff(tc.wantFindings, gotFindings, protocmp.Transform()); diff != "" {
   192  				t.Errorf("detectorrunner.Run(%v): unexpected findings (-want +got):\n%s", tc.det, diff)
   193  			}
   194  			if diff := cmp.Diff(tc.wantStatus, gotStatus); diff != "" {
   195  				t.Errorf("detectorrunner.Run(%v): unexpected status (-want +got):\n%s", tc.det, diff)
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func withDetectorName(f *inventory.GenericFinding, det string) *inventory.GenericFinding {
   202  	c := *f
   203  	c.Plugins = []string{det}
   204  	return &c
   205  }
   206  
   207  func pkgVulnWithDetectorName(v *inventory.PackageVuln, det string) *inventory.PackageVuln {
   208  	c := *v
   209  	c.Plugins = []string{det}
   210  	return &c
   211  }