github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/resolution/resolve_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 resolution_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"deps.dev/util/maven"
    21  	"deps.dev/util/resolve"
    22  	"deps.dev/util/resolve/dep"
    23  	"deps.dev/util/resolve/schema"
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/google/osv-scalibr/clients/clienttest"
    26  	"github.com/google/osv-scalibr/guidedremediation/internal/manifest"
    27  	mavenmanifest "github.com/google/osv-scalibr/guidedremediation/internal/manifest/maven"
    28  	"github.com/google/osv-scalibr/guidedremediation/internal/resolution"
    29  	"github.com/google/osv-scalibr/guidedremediation/options"
    30  )
    31  
    32  // mockManifest represents a manifest file for testing purposes.
    33  // It implements the manifest.Manifest interface.
    34  type mockManifest struct {
    35  	name              string
    36  	version           string
    37  	system            resolve.System
    38  	requirements      []mockManifestRequirements
    39  	groups            map[manifest.RequirementKey][]string
    40  	localManifests    []mockManifest
    41  	ecosystemSpecific any
    42  }
    43  
    44  type mockManifestRequirements struct {
    45  	name    string
    46  	version string
    47  	typ     dep.Type
    48  }
    49  
    50  func (m mockManifest) FilePath() string {
    51  	return ""
    52  }
    53  
    54  func (m mockManifest) Root() resolve.Version {
    55  	return resolve.Version{
    56  		VersionKey: resolve.VersionKey{
    57  			PackageKey: resolve.PackageKey{
    58  				Name:   m.name,
    59  				System: m.system,
    60  			},
    61  			Version:     m.version,
    62  			VersionType: resolve.Concrete,
    63  		},
    64  	}
    65  }
    66  
    67  func (m mockManifest) System() resolve.System {
    68  	return m.system
    69  }
    70  
    71  func (m mockManifest) Requirements() []resolve.RequirementVersion {
    72  	reqs := make([]resolve.RequirementVersion, len(m.requirements))
    73  	for i, r := range m.requirements {
    74  		reqs[i] = resolve.RequirementVersion{
    75  			VersionKey: resolve.VersionKey{
    76  				PackageKey: resolve.PackageKey{
    77  					Name:   r.name,
    78  					System: m.system,
    79  				},
    80  				Version:     r.version,
    81  				VersionType: resolve.Requirement,
    82  			},
    83  			Type: r.typ.Clone(),
    84  		}
    85  	}
    86  	return reqs
    87  }
    88  
    89  func (m mockManifest) Groups() map[manifest.RequirementKey][]string {
    90  	return m.groups
    91  }
    92  
    93  func (m mockManifest) LocalManifests() []manifest.Manifest {
    94  	ret := make([]manifest.Manifest, len(m.localManifests))
    95  	for i, lm := range m.localManifests {
    96  		ret[i] = lm
    97  	}
    98  	return ret
    99  }
   100  
   101  func (m mockManifest) EcosystemSpecific() any {
   102  	return m.ecosystemSpecific
   103  }
   104  
   105  func (m mockManifest) Clone() manifest.Manifest {
   106  	return m
   107  }
   108  
   109  func (m mockManifest) PatchRequirement(resolve.RequirementVersion) error {
   110  	return nil
   111  }
   112  
   113  func TestResolveNPM(t *testing.T) {
   114  	aliasType := func(knownAs string) dep.Type {
   115  		var typ dep.Type
   116  		typ.AddAttr(dep.KnownAs, knownAs)
   117  		return typ
   118  	}
   119  	// Create a mock manifest with dependencies, including aliases and workspaces.
   120  	m := mockManifest{
   121  		name:    "test",
   122  		version: "1.0.0",
   123  		system:  resolve.NPM,
   124  		requirements: []mockManifestRequirements{
   125  			{
   126  				name:    "pkg",
   127  				version: "^1.0.0",
   128  			},
   129  			{
   130  				// Alias for "pkg"
   131  				name:    "pkg",
   132  				version: "^2.0.0",
   133  				typ:     aliasType("pkg-aliased"),
   134  			},
   135  			{
   136  				// Workspace dependency
   137  				name:    "one:workspace",
   138  				version: "*",
   139  			},
   140  			{
   141  				// Workspace dependency
   142  				name:    "two:workspace",
   143  				version: "*",
   144  			},
   145  		},
   146  		localManifests: []mockManifest{
   147  			{
   148  				name:    "one:workspace",
   149  				version: "1.1.1",
   150  				system:  resolve.NPM,
   151  				requirements: []mockManifestRequirements{
   152  					{
   153  						name:    "two:workspace",
   154  						version: "*",
   155  					},
   156  					{
   157  						name:    "pkg",
   158  						version: "^2.0.0",
   159  					},
   160  				},
   161  			},
   162  			{
   163  				name:    "two:workspace",
   164  				version: "2.2.2",
   165  				system:  resolve.NPM,
   166  				requirements: []mockManifestRequirements{
   167  					{
   168  						name:    "pkg",
   169  						version: "^1.0.0",
   170  					},
   171  				},
   172  			},
   173  		},
   174  	}
   175  	cl := clienttest.NewMockResolutionClient(t, "testdata/universe/npm.yaml")
   176  
   177  	got, err := resolution.Resolve(t.Context(), cl, m, options.ResolutionOptions{})
   178  	if err != nil {
   179  		t.Fatal(err)
   180  	}
   181  	_ = got.Canon()
   182  	got.Duration = 0 // Ignore duration for comparison
   183  
   184  	want, err := schema.ParseResolve(`
   185  test 1.0.0
   186  	p1: Selector | pkg@^1.0.0 1.0.0
   187  		p2: Selector | pkg2@^1.0.0 1.1.1
   188  	KnownAs pkg-aliased Selector | pkg@^2.0.0 2.0.0
   189  		$p2@^1.0.0
   190  	Selector | one:workspace@* 1.1.1
   191  		$ws2@*
   192  		Selector | pkg@^2.0.0 2.0.0
   193  			$p2@^1.0.0
   194  	ws2: Selector | two:workspace@* 2.2.2
   195  		$p1@^1.0.0
   196  `, resolve.NPM)
   197  	if err != nil {
   198  		t.Fatal(err)
   199  	}
   200  	_ = want.Canon()
   201  	want.Duration = 0
   202  
   203  	if diff := cmp.Diff(want, got); diff != "" {
   204  		t.Errorf("Resolve() mismatch (-want +got):\n%s", diff)
   205  	}
   206  }
   207  
   208  func TestResolveMaven(t *testing.T) {
   209  	var managementType dep.Type
   210  	managementType.AddAttr(dep.MavenDependencyOrigin, "management")
   211  	m := mockManifest{
   212  		name:    "test:test",
   213  		version: "1.0.0",
   214  		system:  resolve.Maven,
   215  		requirements: []mockManifestRequirements{
   216  			{
   217  				name:    "group:pkg1",
   218  				version: "1.0",
   219  			},
   220  			{
   221  				// Dependency from dependencyManagement (used)
   222  				name:    "group:pkg2",
   223  				version: "2.0",
   224  				typ:     managementType.Clone(),
   225  			},
   226  			{
   227  				// Dependency from dependencyManagement (unused)
   228  				name:    "group:pkg3",
   229  				version: "3.0",
   230  				typ:     managementType.Clone(),
   231  			},
   232  		},
   233  		ecosystemSpecific: mavenmanifest.ManifestSpecific{
   234  			// Construct the OriginalRequirements that resolvePostProcess checks.
   235  			OriginalRequirements: []mavenmanifest.DependencyWithOrigin{
   236  				{
   237  					Dependency: maven.Dependency{
   238  						GroupID:    "group",
   239  						ArtifactID: "pkg1",
   240  						Version:    "1.0",
   241  					},
   242  					Origin: "",
   243  				},
   244  				{
   245  					Dependency: maven.Dependency{
   246  						GroupID:    "group",
   247  						ArtifactID: "pkg2",
   248  						Version:    "2.0",
   249  					},
   250  					Origin: "management",
   251  				},
   252  				{
   253  					Dependency: maven.Dependency{
   254  						GroupID:    "group",
   255  						ArtifactID: "pkg3",
   256  						Version:    "3.0",
   257  					},
   258  					Origin: "management",
   259  				},
   260  			},
   261  		},
   262  	}
   263  	cl := clienttest.NewMockResolutionClient(t, "testdata/universe/maven.yaml")
   264  
   265  	got, err := resolution.Resolve(t.Context(), cl, m, options.ResolutionOptions{MavenManagement: true})
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	_ = got.Canon()
   270  	got.Duration = 0
   271  
   272  	want, err := schema.ParseResolve(`
   273  test:test 1.0.0
   274  	Selector | group:pkg1@1.0 1.0
   275  		Selector | group:pkg2@2.0 2.0
   276  	MavenDependencyOrigin management | group:pkg3@3.0 3.0
   277  `, resolve.Maven)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	_ = want.Canon()
   282  	want.Duration = 0
   283  
   284  	if diff := cmp.Diff(want, got); diff != "" {
   285  		t.Errorf("Resolve() mismatch (-want +got):\n%s", diff)
   286  	}
   287  }