github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/remediation/match_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 remediation_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"deps.dev/util/resolve"
    21  	"github.com/google/osv-scalibr/guidedremediation/internal/remediation"
    22  	"github.com/google/osv-scalibr/guidedremediation/internal/resolution"
    23  	"github.com/google/osv-scalibr/guidedremediation/options"
    24  	osvpb "github.com/ossf/osv-schema/bindings/go/osvschema"
    25  )
    26  
    27  func TestMatchVuln(t *testing.T) {
    28  	var (
    29  		// ID: VULN-001, Dev: false, Severity: 6.6, Depth: 3, Aliases: CVE-111, OSV-2
    30  		vuln1 = resolution.Vulnerability{
    31  			OSV: &osvpb.Vulnerability{
    32  				Id: "VULN-001",
    33  				Severity: []*osvpb.Severity{
    34  					{Type: osvpb.Severity_CVSS_V3, Score: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H"}, // 6.6
    35  					{Type: osvpb.Severity_CVSS_V2, Score: "AV:L/AC:L/Au:S/C:P/I:P/A:C"},                   // 5.7
    36  				},
    37  				Aliases: []string{"CVE-111", "OSV-2"},
    38  			},
    39  			DevOnly: false,
    40  			Subgraphs: []*resolution.DependencySubgraph{{
    41  				Dependency: 3,
    42  				Nodes: map[resolve.NodeID]resolution.GraphNode{
    43  					3: {
    44  						Distance: 0,
    45  						Parents:  []resolve.Edge{{From: 2, To: 3}},
    46  						Children: []resolve.Edge{},
    47  					},
    48  					2: {
    49  						Distance: 1,
    50  						Parents:  []resolve.Edge{{From: 1, To: 2}},
    51  						Children: []resolve.Edge{{From: 2, To: 3}},
    52  					},
    53  					1: {
    54  						Distance: 2,
    55  						Parents:  []resolve.Edge{{From: 0, To: 1}},
    56  						Children: []resolve.Edge{{From: 1, To: 2}},
    57  					},
    58  					0: {
    59  						Distance: 3,
    60  						Parents:  []resolve.Edge{},
    61  						Children: []resolve.Edge{{From: 0, To: 1}},
    62  					},
    63  				},
    64  			}},
    65  		}
    66  		// ID: VULN-002, Dev: true, Severity: N/A, Depth: 2
    67  		vuln2 = resolution.Vulnerability{
    68  			OSV: &osvpb.Vulnerability{
    69  				Id: "VULN-002",
    70  				// No severity
    71  			},
    72  			DevOnly: true,
    73  			Subgraphs: []*resolution.DependencySubgraph{{
    74  				Dependency: 3,
    75  				Nodes: map[resolve.NodeID]resolution.GraphNode{
    76  					3: {
    77  						Distance: 0,
    78  						Parents:  []resolve.Edge{{From: 2, To: 3}, {From: 1, To: 3}},
    79  						Children: []resolve.Edge{},
    80  					},
    81  					2: {
    82  						Distance: 1,
    83  						Parents:  []resolve.Edge{{From: 1, To: 2}},
    84  						Children: []resolve.Edge{{From: 2, To: 3}},
    85  					},
    86  					1: {
    87  						Distance: 1,
    88  						Parents:  []resolve.Edge{{From: 0, To: 1}},
    89  						Children: []resolve.Edge{{From: 1, To: 2}, {From: 1, To: 3}},
    90  					},
    91  					0: {
    92  						Distance: 2,
    93  						Parents:  []resolve.Edge{},
    94  						Children: []resolve.Edge{{From: 0, To: 1}},
    95  					},
    96  				},
    97  			}},
    98  		}
    99  
   100  		// ID: VULN-003, Dev: false, Severity: 7.0, Depth: 1
   101  		vuln3 = resolution.Vulnerability{
   102  			OSV: &osvpb.Vulnerability{
   103  				Id:      "VULN-003",
   104  				Aliases: []string{"CVE-111", "OSV-2"},
   105  				Affected: []*osvpb.Affected{
   106  					{
   107  						Package: &osvpb.Package{
   108  							Ecosystem: "npm",
   109  							Name:      "pkg",
   110  						},
   111  						Ranges: []*osvpb.Range{
   112  							{
   113  								Type: osvpb.Range_SEMVER,
   114  								Events: []*osvpb.Event{
   115  									{Introduced: "0"},
   116  									{Fixed: "1.9.1"},
   117  								},
   118  							},
   119  						},
   120  						Severity: []*osvpb.Severity{
   121  							{Type: osvpb.Severity_CVSS_V4, Score: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:H/SA:H"}, // 9.9
   122  						},
   123  					},
   124  					{
   125  						Package: &osvpb.Package{
   126  							Ecosystem: "npm",
   127  							Name:      "pkg",
   128  						},
   129  						Ranges: []*osvpb.Range{
   130  							{
   131  								Type: osvpb.Range_SEMVER,
   132  								Events: []*osvpb.Event{
   133  									{Introduced: "2.0.0"},
   134  									{Fixed: "2.9.9"},
   135  								},
   136  							},
   137  						},
   138  						Severity: []*osvpb.Severity{
   139  							{Type: osvpb.Severity_CVSS_V4, Score: "CVSS:4.0/AV:L/AC:H/AT:P/PR:H/UI:A/VC:H/VI:H/VA:L/SC:N/SI:H/SA:H"}, // 7.0
   140  						},
   141  					},
   142  				},
   143  			},
   144  			DevOnly: false,
   145  			Subgraphs: []*resolution.DependencySubgraph{{
   146  				Dependency: 1,
   147  				Nodes: map[resolve.NodeID]resolution.GraphNode{
   148  					1: {
   149  						Distance: 0,
   150  						Parents:  []resolve.Edge{{From: 0, To: 1}},
   151  						Version: resolve.VersionKey{
   152  							PackageKey: resolve.PackageKey{
   153  								System: resolve.NPM,
   154  								Name:   "pkg",
   155  							},
   156  							Version: "2.0.2",
   157  						},
   158  					},
   159  					0: {
   160  						Distance: 1,
   161  						Parents:  []resolve.Edge{},
   162  						Children: []resolve.Edge{{From: 0, To: 1}},
   163  					},
   164  				},
   165  			}},
   166  		}
   167  	)
   168  	tests := []struct {
   169  		name string
   170  		vuln resolution.Vulnerability
   171  		opt  options.RemediationOptions
   172  		want bool
   173  	}{
   174  		{
   175  			name: "basic_match",
   176  			vuln: vuln1,
   177  			opt: options.RemediationOptions{
   178  				DevDeps:  true,
   179  				MaxDepth: -1,
   180  			},
   181  			want: true,
   182  		},
   183  		{
   184  			name: "accept_depth",
   185  			vuln: vuln2,
   186  			opt: options.RemediationOptions{
   187  				DevDeps:  true,
   188  				MaxDepth: 2,
   189  			},
   190  			want: true,
   191  		},
   192  		{
   193  			name: "reject_depth",
   194  			vuln: vuln2,
   195  			opt: options.RemediationOptions{
   196  				DevDeps:  true,
   197  				MaxDepth: 1,
   198  			},
   199  			want: false,
   200  		},
   201  		{
   202  			name: "accept_severity",
   203  			vuln: vuln1,
   204  			opt: options.RemediationOptions{
   205  				DevDeps:     true,
   206  				MaxDepth:    -1,
   207  				MinSeverity: 6.6,
   208  			},
   209  			want: true,
   210  		},
   211  		{
   212  			name: "reject_severity",
   213  			vuln: vuln1,
   214  			opt: options.RemediationOptions{
   215  				DevDeps:     true,
   216  				MaxDepth:    -1,
   217  				MinSeverity: 6.7,
   218  			},
   219  			want: false,
   220  		},
   221  		{
   222  			name: "accept_unknown_severity",
   223  			vuln: vuln2,
   224  			opt: options.RemediationOptions{
   225  				DevDeps:     true,
   226  				MaxDepth:    -1,
   227  				MinSeverity: 10.0,
   228  			},
   229  			want: true,
   230  		},
   231  		{
   232  			name: "accept_non-dev",
   233  			vuln: vuln1,
   234  			opt: options.RemediationOptions{
   235  				DevDeps:  false,
   236  				MaxDepth: -1,
   237  			},
   238  			want: true,
   239  		},
   240  		{
   241  			name: "reject_dev",
   242  			vuln: vuln2,
   243  			opt: options.RemediationOptions{
   244  				DevDeps:  false,
   245  				MaxDepth: -1,
   246  			},
   247  			want: false,
   248  		},
   249  		{
   250  			name: "reject_ID_excluded",
   251  			vuln: vuln1,
   252  			opt: options.RemediationOptions{
   253  				DevDeps:     true,
   254  				MaxDepth:    -1,
   255  				IgnoreVulns: []string{"VULN-001"},
   256  			},
   257  			want: false,
   258  		},
   259  		{
   260  			name: "accept_matching_multiple",
   261  			vuln: vuln1,
   262  			opt: options.RemediationOptions{
   263  				DevDeps:     false,
   264  				MaxDepth:    3,
   265  				MinSeverity: 5.0,
   266  				IgnoreVulns: []string{"VULN-999"},
   267  			},
   268  			want: true,
   269  		},
   270  		{
   271  			name: "reject_excluded_ID_in_alias",
   272  			vuln: vuln1,
   273  			opt: options.RemediationOptions{
   274  				DevDeps:     true,
   275  				MaxDepth:    -1,
   276  				IgnoreVulns: []string{"OSV-2"},
   277  			},
   278  			want: false,
   279  		},
   280  		{
   281  			name: "check_per-affected_severity",
   282  			vuln: vuln3,
   283  			opt: options.RemediationOptions{
   284  				DevDeps:     true,
   285  				MaxDepth:    -1,
   286  				MinSeverity: 8.0,
   287  			},
   288  			want: false,
   289  		},
   290  	}
   291  
   292  	for _, tt := range tests {
   293  		t.Run(tt.name, func(t *testing.T) {
   294  			if got := remediation.MatchVuln(tt.opt, tt.vuln); got != tt.want {
   295  				t.Errorf("MatchVuln() = %v, want %v", got, tt.want)
   296  			}
   297  		})
   298  	}
   299  }