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  }