github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/strategy/relax/relax_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 relax_test
    16  
    17  import (
    18  	"encoding/json"
    19  	"os"
    20  	"testing"
    21  
    22  	"deps.dev/util/resolve/dep"
    23  	"github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	"github.com/google/osv-scalibr/clients/clienttest"
    26  	scalibrfs "github.com/google/osv-scalibr/fs"
    27  	"github.com/google/osv-scalibr/guidedremediation/internal/manifest"
    28  	"github.com/google/osv-scalibr/guidedremediation/internal/manifest/npm"
    29  	"github.com/google/osv-scalibr/guidedremediation/internal/manifest/python"
    30  	"github.com/google/osv-scalibr/guidedremediation/internal/remediation"
    31  	"github.com/google/osv-scalibr/guidedremediation/internal/strategy/relax"
    32  	"github.com/google/osv-scalibr/guidedremediation/internal/vulnenrichertest"
    33  	"github.com/google/osv-scalibr/guidedremediation/options"
    34  	"github.com/google/osv-scalibr/guidedremediation/result"
    35  	"github.com/google/osv-scalibr/guidedremediation/upgrade"
    36  )
    37  
    38  func TestComputePatches(t *testing.T) {
    39  	npmRW, err := npm.GetReadWriter()
    40  	if err != nil {
    41  		t.Fatalf("failed getting npm ReadWriter: %v", err)
    42  	}
    43  	pythonRW, _ := python.GetRequirementsReadWriter()
    44  
    45  	tests := []struct {
    46  		name         string
    47  		universeFile string
    48  		vulnsFile    string
    49  		manifestPath string
    50  		readWriter   manifest.ReadWriter
    51  		opts         options.RemediationOptions
    52  		wantFile     string
    53  	}{
    54  		{
    55  			name:         "npm-simple",
    56  			universeFile: "testdata/npm/universe.yaml",
    57  			vulnsFile:    "testdata/npm/vulnerabilities.json",
    58  			manifestPath: "npm/simple/package.json",
    59  			readWriter:   npmRW,
    60  			opts:         options.DefaultRemediationOptions(),
    61  			wantFile:     "testdata/npm/simple/patches.json",
    62  		},
    63  		{
    64  			name:         "npm-vuln-without-fix",
    65  			universeFile: "testdata/npm/universe.yaml",
    66  			vulnsFile:    "testdata/npm/vulnerabilities.json",
    67  			manifestPath: "npm/vuln-without-fix/package.json",
    68  			readWriter:   npmRW,
    69  			opts:         options.DefaultRemediationOptions(),
    70  			wantFile:     "testdata/npm/vuln-without-fix/patches.json",
    71  		},
    72  		{
    73  			name:         "npm-diamond",
    74  			universeFile: "testdata/npm/universe.yaml",
    75  			vulnsFile:    "testdata/npm/vulnerabilities.json",
    76  			manifestPath: "npm/diamond/package.json",
    77  			readWriter:   npmRW,
    78  			opts:         options.DefaultRemediationOptions(),
    79  			wantFile:     "testdata/npm/diamond/patches.json",
    80  		},
    81  		{
    82  			name:         "npm-removed-vuln-dep",
    83  			universeFile: "testdata/npm/universe.yaml",
    84  			vulnsFile:    "testdata/npm/vulnerabilities.json",
    85  			manifestPath: "npm/removed-vuln/package.json",
    86  			readWriter:   npmRW,
    87  			opts:         options.DefaultRemediationOptions(),
    88  			wantFile:     "testdata/npm/removed-vuln/patches.json",
    89  		},
    90  		{
    91  			name:         "npm-introduced-vuln",
    92  			universeFile: "testdata/npm/universe.yaml",
    93  			vulnsFile:    "testdata/npm/vulnerabilities.json",
    94  			manifestPath: "npm/introduce-vuln/package.json",
    95  			readWriter:   npmRW,
    96  			opts:         options.DefaultRemediationOptions(),
    97  			wantFile:     "testdata/npm/introduce-vuln/patches.json",
    98  		},
    99  		{
   100  			name:         "npm-non-constraining-dep",
   101  			universeFile: "testdata/npm/universe.yaml",
   102  			vulnsFile:    "testdata/npm/vulnerabilities.json",
   103  			manifestPath: "npm/non-constraining/package.json",
   104  			readWriter:   npmRW,
   105  			opts:         options.DefaultRemediationOptions(),
   106  			wantFile:     "testdata/npm/non-constraining/patches.json",
   107  		},
   108  		{
   109  			name:         "npm-deepen-to-remediate",
   110  			universeFile: "testdata/npm/universe.yaml",
   111  			vulnsFile:    "testdata/npm/vulnerabilities.json",
   112  			manifestPath: "npm/deepen/package.json",
   113  			readWriter:   npmRW,
   114  			opts: options.RemediationOptions{
   115  				MaxDepth:      3,
   116  				UpgradeConfig: upgrade.NewConfig(),
   117  			},
   118  			wantFile: "testdata/npm/deepen/patches.json",
   119  		},
   120  		{
   121  			name:         "python-simple",
   122  			universeFile: "testdata/python/universe.yaml",
   123  			vulnsFile:    "testdata/python/vulnerabilities.json",
   124  			manifestPath: "python/simple/requirements.txt",
   125  			readWriter:   pythonRW,
   126  			opts:         options.DefaultRemediationOptions(),
   127  			wantFile:     "testdata/python/simple/patches.json",
   128  		},
   129  		{
   130  			name:         "python-no-fix",
   131  			universeFile: "testdata/python/universe.yaml",
   132  			vulnsFile:    "testdata/python/vulnerabilities.json",
   133  			manifestPath: "python/no-fix/requirements.txt",
   134  			readWriter:   pythonRW,
   135  			opts:         options.DefaultRemediationOptions(),
   136  			wantFile:     "testdata/python/no-fix/patches.json",
   137  		},
   138  		{
   139  			name:         "python-diamond",
   140  			universeFile: "testdata/python/universe.yaml",
   141  			vulnsFile:    "testdata/python/vulnerabilities.json",
   142  			manifestPath: "python/diamond/requirements.txt",
   143  			readWriter:   pythonRW,
   144  			opts:         options.DefaultRemediationOptions(),
   145  			wantFile:     "testdata/python/diamond/patches.json",
   146  		},
   147  		{
   148  			name:         "python-removed-dependency",
   149  			universeFile: "testdata/python/universe.yaml",
   150  			vulnsFile:    "testdata/python/vulnerabilities.json",
   151  			manifestPath: "python/removed/requirements.txt",
   152  			readWriter:   pythonRW,
   153  			opts:         options.DefaultRemediationOptions(),
   154  			wantFile:     "testdata/python/removed/patches.json",
   155  		},
   156  		{
   157  			name:         "python-introduce-new-vuln",
   158  			universeFile: "testdata/python/universe.yaml",
   159  			vulnsFile:    "testdata/python/vulnerabilities.json",
   160  			manifestPath: "python/introduce/requirements.txt",
   161  			readWriter:   pythonRW,
   162  			opts:         options.DefaultRemediationOptions(),
   163  			wantFile:     "testdata/python/introduce/patches.json",
   164  		},
   165  		{
   166  			name:         "python-non-constraining-dependency",
   167  			universeFile: "testdata/python/universe.yaml",
   168  			vulnsFile:    "testdata/python/vulnerabilities.json",
   169  			manifestPath: "python/non-constraining/requirements.txt",
   170  			readWriter:   pythonRW,
   171  			opts:         options.DefaultRemediationOptions(),
   172  			wantFile:     "testdata/python/non-constraining/patches.json",
   173  		},
   174  		{
   175  			name:         "python-deepen",
   176  			universeFile: "testdata/python/universe.yaml",
   177  			vulnsFile:    "testdata/python/vulnerabilities.json",
   178  			manifestPath: "python/deepen/requirements.txt",
   179  			readWriter:   pythonRW,
   180  			opts:         options.DefaultRemediationOptions(),
   181  			wantFile:     "testdata/python/deepen/patches.json",
   182  		},
   183  		{
   184  			name:         "python-max-depth",
   185  			universeFile: "testdata/python/universe.yaml",
   186  			vulnsFile:    "testdata/python/vulnerabilities.json",
   187  			manifestPath: "python/max-depth/requirements.txt",
   188  			readWriter:   pythonRW,
   189  			opts: options.RemediationOptions{
   190  				MaxDepth:      3,
   191  				UpgradeConfig: upgrade.NewConfig(),
   192  			},
   193  			wantFile: "testdata/python/max-depth/patches.json",
   194  		},
   195  	}
   196  
   197  	for _, tt := range tests {
   198  		t.Run(tt.name, func(t *testing.T) {
   199  			wantFile, err := os.Open(tt.wantFile)
   200  			if err != nil {
   201  				t.Fatalf("failed opening wantFile: %v", err)
   202  			}
   203  			defer wantFile.Close()
   204  			var want []result.Patch
   205  			if err := json.NewDecoder(wantFile).Decode(&want); err != nil {
   206  				t.Fatalf("failed decoding wantFile: %v", err)
   207  			}
   208  
   209  			fsys := scalibrfs.DirFS("./testdata")
   210  			m, err := tt.readWriter.Read(tt.manifestPath, fsys)
   211  			if err != nil {
   212  				t.Fatalf("failed reading manifest: %v", err)
   213  			}
   214  
   215  			cl := clienttest.NewMockResolutionClient(t, tt.universeFile)
   216  			ve := vulnenrichertest.NewMockVulnerabilityEnricher(t, tt.vulnsFile)
   217  			resolved, err := remediation.ResolveManifest(t.Context(), cl, ve, m, &tt.opts)
   218  			if err != nil {
   219  				t.Fatalf("failed resolving manifest: %v", err)
   220  			}
   221  			gotFull, err := relax.ComputePatches(t.Context(), cl, ve, resolved, &tt.opts)
   222  			if err != nil {
   223  				t.Fatalf("failed computing patches: %v", err)
   224  			}
   225  			got := gotFull.Patches
   226  
   227  			// Type is not in exported to json, so just ignore it.
   228  			if diff := cmp.Diff(want, got, cmpopts.EquateEmpty(), cmpopts.IgnoreTypes(dep.Type{})); diff != "" {
   229  				t.Errorf("ComputePatches: unexpected diff (-want +got):\n%s", diff)
   230  			}
   231  		})
   232  	}
   233  }