github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/java/internal/maven/resolver_test.go (about) 1 package maven 2 3 import ( 4 "context" 5 "path/filepath" 6 "strings" 7 "testing" 8 9 "github.com/stretchr/testify/require" 10 11 "github.com/anchore/syft/internal" 12 "github.com/anchore/syft/syft/internal/fileresolver" 13 maventest "github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven/test" 14 ) 15 16 func Test_resolveProperty(t *testing.T) { 17 tests := []struct { 18 name string 19 property string 20 pom Project 21 expected string 22 }{ 23 { 24 name: "property", 25 property: "${version.number}", 26 pom: Project{ 27 Properties: &Properties{ 28 Entries: map[string]string{ 29 "version.number": "12.5.0", 30 }, 31 }, 32 }, 33 expected: "12.5.0", 34 }, 35 { 36 name: "groupId", 37 property: "${project.groupId}", 38 pom: Project{ 39 GroupID: ptr("org.some.group"), 40 }, 41 expected: "org.some.group", 42 }, 43 { 44 name: "parent groupId", 45 property: "${project.parent.groupId}", 46 pom: Project{ 47 Parent: &Parent{ 48 GroupID: ptr("org.some.parent"), 49 }, 50 }, 51 expected: "org.some.parent", 52 }, 53 { 54 name: "nil pointer halts search", 55 property: "${project.parent.groupId}", 56 pom: Project{ 57 Parent: nil, 58 }, 59 expected: "", 60 }, 61 { 62 name: "nil string pointer halts search", 63 property: "${project.parent.groupId}", 64 pom: Project{ 65 Parent: &Parent{ 66 GroupID: nil, 67 }, 68 }, 69 expected: "", 70 }, 71 { 72 name: "double dereference", 73 property: "${springboot.version}", 74 pom: Project{ 75 Parent: &Parent{ 76 Version: ptr("1.2.3"), 77 }, 78 Properties: &Properties{ 79 Entries: map[string]string{ 80 "springboot.version": "${project.parent.version}", 81 }, 82 }, 83 }, 84 expected: "1.2.3", 85 }, 86 { 87 name: "map missing stops double dereference", 88 property: "${springboot.version}", 89 pom: Project{ 90 Parent: &Parent{ 91 Version: ptr("1.2.3"), 92 }, 93 }, 94 expected: "", 95 }, 96 { 97 name: "resolution halts even if it resolves to a variable", 98 property: "${springboot.version}", 99 pom: Project{ 100 Parent: &Parent{ 101 Version: ptr("${undefined.version}"), 102 }, 103 Properties: &Properties{ 104 Entries: map[string]string{ 105 "springboot.version": "${project.parent.version}", 106 }, 107 }, 108 }, 109 expected: "", 110 }, 111 { 112 name: "resolution halts even if cyclic", 113 property: "${springboot.version}", 114 pom: Project{ 115 Properties: &Properties{ 116 Entries: map[string]string{ 117 "springboot.version": "${springboot.version}", 118 }, 119 }, 120 }, 121 expected: "", 122 }, 123 { 124 name: "resolution halts even if cyclic more steps", 125 property: "${cyclic.version}", 126 pom: Project{ 127 Properties: &Properties{ 128 Entries: map[string]string{ 129 "other.version": "${cyclic.version}", 130 "springboot.version": "${other.version}", 131 "cyclic.version": "${springboot.version}", 132 }, 133 }, 134 }, 135 expected: "", 136 }, 137 { 138 name: "resolution halts even if cyclic involving parent", 139 property: "${cyclic.version}", 140 pom: Project{ 141 Parent: &Parent{ 142 Version: ptr("${cyclic.version}"), 143 }, 144 Properties: &Properties{ 145 Entries: map[string]string{ 146 "other.version": "${parent.version}", 147 "springboot.version": "${other.version}", 148 "cyclic.version": "${springboot.version}", 149 }, 150 }, 151 }, 152 expected: "", 153 }, 154 } 155 156 for _, test := range tests { 157 t.Run(test.name, func(t *testing.T) { 158 r := NewResolver(nil, DefaultConfig()) 159 resolved := r.ResolveProperty(context.Background(), &test.pom, ptr(test.property)) 160 require.Equal(t, test.expected, resolved) 161 }) 162 } 163 } 164 165 func Test_mavenResolverLocal(t *testing.T) { 166 dir, err := filepath.Abs("test-fixtures/maven-repo") 167 require.NoError(t, err) 168 169 tests := []struct { 170 name string 171 groupID string 172 artifactID string 173 version string 174 maxDepth int 175 expression string 176 expected string 177 wantErr require.ErrorAssertionFunc 178 }{ 179 { 180 name: "artifact id with variable from 2nd parent", 181 groupID: "my.org", 182 artifactID: "child-one", 183 version: "1.3.6", 184 expression: "${project.one}", 185 expected: "1", 186 }, 187 { 188 name: "depth limited large enough", 189 groupID: "my.org", 190 artifactID: "child-one", 191 version: "1.3.6", 192 expression: "${project.one}", 193 expected: "1", 194 maxDepth: 2, 195 }, 196 { 197 name: "depth limited should not resolve", 198 groupID: "my.org", 199 artifactID: "child-one", 200 version: "1.3.6", 201 expression: "${project.one}", 202 expected: "", 203 maxDepth: 1, 204 }, 205 } 206 207 for _, test := range tests { 208 t.Run(test.name, func(t *testing.T) { 209 ctx := context.Background() 210 r := NewResolver(nil, Config{ 211 UseNetwork: false, 212 UseLocalRepository: true, 213 LocalRepositoryDir: dir, 214 MaxParentRecursiveDepth: test.maxDepth, 215 }) 216 pom, err := r.FindPom(ctx, test.groupID, test.artifactID, test.version) 217 if test.wantErr != nil { 218 test.wantErr(t, err) 219 } else { 220 require.NoError(t, err) 221 } 222 got := r.ResolveProperty(context.Background(), pom, &test.expression) 223 require.Equal(t, test.expected, got) 224 }) 225 } 226 } 227 228 func Test_mavenResolverRemote(t *testing.T) { 229 url := maventest.MockRepo(t, "test-fixtures/maven-repo") 230 231 tests := []struct { 232 groupID string 233 artifactID string 234 version string 235 expression string 236 expected string 237 wantErr require.ErrorAssertionFunc 238 }{ 239 { 240 groupID: "my.org", 241 artifactID: "child-one", 242 version: "1.3.6", 243 expression: "${project.one}", 244 expected: "1", 245 }, 246 { 247 groupID: "my.org", // this particular package has a circular reference 248 artifactID: "circular", 249 version: "1.2.3", 250 expression: "${unresolved}", 251 expected: "", 252 }, 253 } 254 255 for _, test := range tests { 256 t.Run(test.artifactID, func(t *testing.T) { 257 ctx := context.Background() 258 r := NewResolver(nil, Config{ 259 UseNetwork: true, 260 UseLocalRepository: false, 261 Repositories: strings.Split(url, ","), 262 }) 263 pom, err := r.FindPom(ctx, test.groupID, test.artifactID, test.version) 264 if test.wantErr != nil { 265 test.wantErr(t, err) 266 } else { 267 require.NoError(t, err) 268 } 269 got := r.ResolveProperty(context.Background(), pom, &test.expression) 270 require.Equal(t, test.expected, got) 271 }) 272 } 273 } 274 275 func Test_relativePathParent(t *testing.T) { 276 resolver, err := fileresolver.NewFromDirectory("test-fixtures/local", "") 277 require.NoError(t, err) 278 279 ctx := context.Background() 280 281 tests := []struct { 282 name string 283 pom string 284 validate func(t *testing.T, r *Resolver, pom *Project) 285 }{ 286 { 287 name: "basic", 288 pom: "child-1/pom.xml", 289 validate: func(t *testing.T, r *Resolver, pom *Project) { 290 parent, err := r.resolveParent(ctx, pom) 291 require.NoError(t, err) 292 require.Contains(t, r.pomLocations, parent) 293 294 parent, err = r.resolveParent(ctx, parent) 295 require.NoError(t, err) 296 require.Contains(t, r.pomLocations, parent) 297 298 got := r.ResolveProperty(ctx, pom, ptr("${commons-exec_subversion}")) 299 require.Equal(t, "3", got) 300 }, 301 }, 302 { 303 name: "parent property", 304 pom: "child-2/pom.xml", 305 validate: func(t *testing.T, r *Resolver, pom *Project) { 306 id := r.ResolveID(ctx, pom) 307 // child.parent.version = ${revision} 308 // parent.revision = 3.3.3 309 require.Equal(t, id.Version, "3.3.3") 310 }, 311 }, 312 { 313 name: "invalid parent", 314 pom: "child-3/pom.xml", 315 validate: func(t *testing.T, r *Resolver, pom *Project) { 316 require.NotNil(t, pom) 317 id := r.ResolveID(ctx, pom) 318 // version should not be resolved to anything 319 require.Equal(t, "", id.Version) 320 }, 321 }, 322 { 323 name: "circular resolving ID variables", 324 pom: "circular-1/pom.xml", 325 validate: func(t *testing.T, r *Resolver, pom *Project) { 326 require.NotNil(t, pom) 327 id := r.ResolveID(ctx, pom) 328 // version should be resolved, but not artifactId 329 require.Equal(t, "1.2.3", id.Version) 330 require.Equal(t, "", id.ArtifactID) 331 }, 332 }, 333 { 334 name: "circular parent only", 335 pom: "circular-2/pom.xml", 336 validate: func(t *testing.T, r *Resolver, pom *Project) { 337 require.NotNil(t, pom) 338 id := r.ResolveID(ctx, pom) 339 require.Equal(t, "", id.Version) 340 require.Equal(t, "something", id.ArtifactID) 341 }, 342 }, 343 } 344 345 for _, test := range tests { 346 t.Run(test.name, func(t *testing.T) { 347 r := NewResolver(resolver, DefaultConfig()) 348 locs, err := resolver.FilesByPath(test.pom) 349 require.NoError(t, err) 350 require.Len(t, locs, 1) 351 352 loc := locs[0] 353 contents, err := resolver.FileContentsByLocation(loc) 354 require.NoError(t, err) 355 defer internal.CloseAndLogError(contents, loc.RealPath) 356 357 pom, err := ParsePomXML(contents) 358 require.NoError(t, err) 359 360 r.pomLocations[pom] = loc 361 362 test.validate(t, r, pom) 363 }) 364 } 365 } 366 367 // ptr returns a pointer to the given value 368 func ptr[T any](value T) *T { 369 return &value 370 }