github.com/jfrog/frogbot@v1.1.1-0.20231221090046-821a26f50338/packagehandlers/packagehandlers_test.go (about)

     1  package packagehandlers
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/jfrog/build-info-go/tests"
     6  	biutils "github.com/jfrog/build-info-go/utils"
     7  	"github.com/jfrog/frogbot/utils"
     8  	"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
     9  	"github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/sca/java"
    10  	"github.com/jfrog/jfrog-cli-core/v2/xray/formats"
    11  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    12  	"github.com/stretchr/testify/assert"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  type dependencyFixTest struct {
    22  	vulnDetails           *utils.VulnerabilityDetails
    23  	scanDetails           *utils.ScanDetails
    24  	fixSupported          bool
    25  	specificTechVersion   string
    26  	uniqueChecksExtraArgs []string
    27  	testDirName           string
    28  }
    29  
    30  const (
    31  	requirementsFile    = "oslo.config>=1.12.1,<1.13\noslo.utils<5.0,>=4.0.0\nparamiko==2.7.2\npasslib<=1.7.4\nprance>=0.9.0\nprompt-toolkit~=1.0.15\npyinotify>0.9.6\nPyJWT>1.7.1\nurllib3 > 1.1.9, < 1.5.*"
    32  	GoPackageDescriptor = "go.mod"
    33  )
    34  
    35  type pipPackageRegexTest struct {
    36  	packageName         string
    37  	expectedRequirement string
    38  }
    39  
    40  func TestUpdateDependency(t *testing.T) {
    41  	testCases := [][]dependencyFixTest{
    42  		// Go test cases
    43  		{
    44  			{
    45  				vulnDetails: &utils.VulnerabilityDetails{
    46  					SuggestedFixedVersion:       "0.0.0-20201216223049-8b5274cf687f",
    47  					IsDirectDependency:          false,
    48  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "golang.org/x/crypto"}},
    49  				},
    50  				fixSupported:          true,
    51  				uniqueChecksExtraArgs: []string{GoPackageDescriptor},
    52  			},
    53  			{
    54  				vulnDetails: &utils.VulnerabilityDetails{
    55  					SuggestedFixedVersion:       "1.7.7",
    56  					IsDirectDependency:          true,
    57  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "github.com/gin-gonic/gin"}},
    58  				},
    59  				fixSupported:          true,
    60  				uniqueChecksExtraArgs: []string{GoPackageDescriptor},
    61  			},
    62  			{
    63  				vulnDetails: &utils.VulnerabilityDetails{
    64  					SuggestedFixedVersion:       "1.3.0",
    65  					IsDirectDependency:          true,
    66  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "github.com/google/uuid"}},
    67  				},
    68  				fixSupported:          true,
    69  				uniqueChecksExtraArgs: []string{GoPackageDescriptor},
    70  			},
    71  		},
    72  
    73  		// Python test cases (includes pip, pipenv, poetry)
    74  		{
    75  			{
    76  				vulnDetails: &utils.VulnerabilityDetails{
    77  					SuggestedFixedVersion:       "1.25.9",
    78  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "urllib3"}},
    79  				},
    80  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "requirements.txt"}},
    81  				fixSupported: false,
    82  			},
    83  			{
    84  				vulnDetails: &utils.VulnerabilityDetails{
    85  					SuggestedFixedVersion:       "1.25.9",
    86  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "urllib3"}},
    87  				},
    88  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "pyproejct.toml"}},
    89  				fixSupported: false,
    90  			},
    91  			{
    92  				vulnDetails: &utils.VulnerabilityDetails{
    93  					SuggestedFixedVersion:       "1.25.9",
    94  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pipenv, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "urllib3"}},
    95  				},
    96  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "Pipfile"}},
    97  				fixSupported: false,
    98  			},
    99  			{
   100  				vulnDetails: &utils.VulnerabilityDetails{
   101  					SuggestedFixedVersion:       "2.4.0",
   102  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "pyjwt"}},
   103  					IsDirectDependency:          true,
   104  				},
   105  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "requirements.txt"}},
   106  				fixSupported: true,
   107  			},
   108  			{
   109  				vulnDetails: &utils.VulnerabilityDetails{
   110  					SuggestedFixedVersion:       "2.4.0",
   111  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "Pyjwt"}},
   112  					IsDirectDependency:          true},
   113  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "requirements.txt"}},
   114  				fixSupported: true,
   115  			},
   116  			{
   117  				vulnDetails: &utils.VulnerabilityDetails{
   118  					SuggestedFixedVersion:       "2.4.0",
   119  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "pyjwt"}},
   120  					IsDirectDependency:          true},
   121  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "setup.py"}},
   122  				fixSupported: true,
   123  			},
   124  			{
   125  				vulnDetails: &utils.VulnerabilityDetails{
   126  					SuggestedFixedVersion:       "2.4.0",
   127  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "pyjwt"}},
   128  					IsDirectDependency:          true},
   129  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "pyproject.toml"}},
   130  				fixSupported: true,
   131  			},
   132  		},
   133  
   134  		// Npm test cases
   135  		{
   136  			{
   137  				vulnDetails: &utils.VulnerabilityDetails{
   138  					SuggestedFixedVersion:       "0.8.4",
   139  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "mpath"}},
   140  				},
   141  				fixSupported: false,
   142  			},
   143  			{
   144  				vulnDetails: &utils.VulnerabilityDetails{
   145  					SuggestedFixedVersion:       "3.0.2",
   146  					IsDirectDependency:          true,
   147  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimatch"}},
   148  				},
   149  				fixSupported: true,
   150  			},
   151  		},
   152  
   153  		// Yarn test cases
   154  		{
   155  			{
   156  				// This test case directs to non-existing directory. It only checks if the dependency update is blocked if the vulnerable dependency is not a direct dependency
   157  				vulnDetails: &utils.VulnerabilityDetails{
   158  					SuggestedFixedVersion:       "1.2.6",
   159  					IsDirectDependency:          false,
   160  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimist"}},
   161  				},
   162  				fixSupported: false,
   163  			},
   164  			{
   165  				vulnDetails: &utils.VulnerabilityDetails{
   166  					SuggestedFixedVersion:       "1.2.6",
   167  					IsDirectDependency:          true,
   168  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimist"}},
   169  				},
   170  				fixSupported:        true,
   171  				specificTechVersion: "1",
   172  			},
   173  			{
   174  				vulnDetails: &utils.VulnerabilityDetails{
   175  					SuggestedFixedVersion:       "1.2.6",
   176  					IsDirectDependency:          true,
   177  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimist"}},
   178  				},
   179  				fixSupported:        true,
   180  				specificTechVersion: "2",
   181  			},
   182  		},
   183  
   184  		// Maven test cases
   185  		{
   186  			{
   187  				vulnDetails: &utils.VulnerabilityDetails{
   188  					SuggestedFixedVersion:       "2.7",
   189  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Maven, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-io:commons-io"}},
   190  					IsDirectDependency:          true},
   191  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{DepsRepo: ""}, ServerDetails: nil},
   192  				fixSupported: true,
   193  			},
   194  			{
   195  				vulnDetails: &utils.VulnerabilityDetails{
   196  					SuggestedFixedVersion:       "4.3.20",
   197  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Maven, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "org.springframework:spring-core"}},
   198  					IsDirectDependency:          false},
   199  				scanDetails:  &utils.ScanDetails{Project: &utils.Project{DepsRepo: ""}, ServerDetails: nil},
   200  				fixSupported: false,
   201  			},
   202  		},
   203  
   204  		// NuGet test cases
   205  		{
   206  			{
   207  				// This test case directs to non-existing directory. It only checks if the dependency update is blocked if the vulnerable dependency is not a direct dependency
   208  				vulnDetails: &utils.VulnerabilityDetails{
   209  					SuggestedFixedVersion:       "1.1.1",
   210  					IsDirectDependency:          false,
   211  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "snappier", ImpactedDependencyVersion: "1.1.0"}},
   212  				},
   213  				fixSupported: false,
   214  				testDirName:  "dotnet",
   215  			},
   216  			{
   217  				vulnDetails: &utils.VulnerabilityDetails{
   218  					SuggestedFixedVersion:       "1.1.1",
   219  					IsDirectDependency:          true,
   220  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "snappier", ImpactedDependencyVersion: "1.1.0"}},
   221  				},
   222  				fixSupported: true,
   223  				testDirName:  "dotnet",
   224  			},
   225  		},
   226  
   227  		// Gradle test cases
   228  		{
   229  			{
   230  				vulnDetails: &utils.VulnerabilityDetails{
   231  					SuggestedFixedVersion:       "4.13.1",
   232  					IsDirectDependency:          false,
   233  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "3.2"}},
   234  				},
   235  				fixSupported: false,
   236  			},
   237  			{ // Unsupported fix: dynamic version
   238  				vulnDetails: &utils.VulnerabilityDetails{
   239  					SuggestedFixedVersion:       "3.2.2",
   240  					IsDirectDependency:          true,
   241  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "3.+"}},
   242  				},
   243  				fixSupported: false,
   244  			},
   245  			{ // Unsupported fix: latest version
   246  				vulnDetails: &utils.VulnerabilityDetails{
   247  					SuggestedFixedVersion:       "3.2.2",
   248  					IsDirectDependency:          true,
   249  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "latest.release"}},
   250  				},
   251  				fixSupported: false,
   252  			},
   253  			{ // Unsupported fix: range version
   254  				vulnDetails: &utils.VulnerabilityDetails{
   255  					SuggestedFixedVersion:       "3.2.2",
   256  					IsDirectDependency:          true,
   257  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "[3.0, 3.5.6)"}},
   258  				},
   259  				fixSupported: false,
   260  			},
   261  			{
   262  				vulnDetails: &utils.VulnerabilityDetails{
   263  					SuggestedFixedVersion:       "4.13.1",
   264  					IsDirectDependency:          true,
   265  					VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "junit:junit", ImpactedDependencyVersion: "4.7"}},
   266  				},
   267  				fixSupported: true,
   268  			},
   269  		},
   270  	}
   271  
   272  	for _, testBatch := range testCases {
   273  		for _, test := range testBatch {
   274  			packageHandler := GetCompatiblePackageHandler(test.vulnDetails, test.scanDetails)
   275  			t.Run(fmt.Sprintf("%s:%s direct:%s", test.vulnDetails.Technology.String()+test.specificTechVersion, test.vulnDetails.ImpactedDependencyName, strconv.FormatBool(test.vulnDetails.IsDirectDependency)),
   276  				func(t *testing.T) {
   277  					testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency)
   278  					testDirName := test.vulnDetails.Technology.String()
   279  					if test.testDirName != "" {
   280  						testDirName = test.testDirName
   281  					}
   282  					cleanup := createTempDirAndChdir(t, testDataDir, testDirName+test.specificTechVersion)
   283  					defer cleanup()
   284  					err := packageHandler.UpdateDependency(test.vulnDetails)
   285  					if test.fixSupported {
   286  						assert.NoError(t, err)
   287  						uniquePackageManagerChecks(t, test)
   288  					} else {
   289  						assert.Error(t, err)
   290  						assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error")
   291  					}
   292  				})
   293  		}
   294  	}
   295  
   296  }
   297  
   298  func TestPipPackageRegex(t *testing.T) {
   299  	var pipPackagesRegexTests = []pipPackageRegexTest{
   300  		{"oslo.config", "oslo.config>=1.12.1,<1.13"},
   301  		{"oslo.utils", "oslo.utils<5.0,>=4.0.0"},
   302  		{"paramiko", "paramiko==2.7.2"},
   303  		{"passlib", "passlib<=1.7.4"},
   304  		{"PassLib", "passlib<=1.7.4"},
   305  		{"prance", "prance>=0.9.0"},
   306  		{"prompt-toolkit", "prompt-toolkit~=1.0.15"},
   307  		{"pyinotify", "pyinotify>0.9.6"},
   308  		{"pyjwt", "pyjwt>1.7.1"},
   309  		{"PyJWT", "pyjwt>1.7.1"},
   310  		{"urllib3", "urllib3 > 1.1.9, < 1.5.*"},
   311  	}
   312  	for _, pack := range pipPackagesRegexTests {
   313  		re := regexp.MustCompile(PythonPackageRegexPrefix + "(" + pack.packageName + "|" + strings.ToLower(pack.packageName) + ")" + PythonPackageRegexSuffix)
   314  		found := re.FindString(requirementsFile)
   315  		assert.Equal(t, pack.expectedRequirement, strings.ToLower(found))
   316  	}
   317  }
   318  
   319  // Maven utils functions
   320  func TestGetDependenciesFromPomXmlSingleDependency(t *testing.T) {
   321  	testCases := []string{`<dependency>
   322  	<groupId>org.apache.commons</groupId>
   323  	<artifactId>commons-email</artifactId>
   324  	<version>1.1</version>
   325  	<scope>compile</scope>
   326  </dependency>`,
   327  		`<dependency>
   328  	<groupId> 	org.apache.commons</groupId>
   329  	<artifactId>commons-email	 </artifactId>
   330  	<version>  1.1  </version>
   331  	<scope> compile		</scope>
   332  </dependency>`,
   333  	}
   334  
   335  	for _, testCase := range testCases {
   336  		result, err := getMavenDependencies([]byte(testCase))
   337  		assert.NoError(t, err)
   338  
   339  		assert.Len(t, result, 1)
   340  		assert.Equal(t, "org.apache.commons", result[0].GroupId)
   341  		assert.Equal(t, "commons-email", result[0].ArtifactId)
   342  		assert.Equal(t, "1.1", result[0].Version)
   343  	}
   344  }
   345  
   346  func TestGetDependenciesFromPomXmlMultiDependency(t *testing.T) {
   347  	testCases := []string{`
   348  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   349          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
   350  <dependencies>
   351         <dependency>
   352             <groupId>org.apache.commons</groupId>
   353             <artifactId>commons-email</artifactId>
   354             <version>1.1</version>
   355             <scope>compile</scope>
   356         </dependency>
   357         <dependency>
   358             <groupId>org.codehaus.plexus</groupId>
   359             <artifactId>plexus-utils</artifactId>
   360             <version>1.5.1</version>
   361         </dependency>
   362  	</dependencies>
   363  </project>`,
   364  	}
   365  
   366  	for _, testCase := range testCases {
   367  		result, err := getMavenDependencies([]byte(testCase))
   368  		assert.NoError(t, err)
   369  
   370  		assert.Len(t, result, 2)
   371  		assert.Equal(t, "org.apache.commons", result[0].GroupId)
   372  		assert.Equal(t, "commons-email", result[0].ArtifactId)
   373  		assert.Equal(t, "1.1", result[0].Version)
   374  
   375  		assert.Equal(t, "org.codehaus.plexus", result[1].GroupId)
   376  		assert.Equal(t, "plexus-utils", result[1].ArtifactId)
   377  		assert.Equal(t, "1.5.1", result[1].Version)
   378  	}
   379  }
   380  
   381  func TestGetPluginsFromPomXml(t *testing.T) {
   382  	testCase :=
   383  		`<project>
   384  			<build>
   385         <plugins>
   386             <plugin>
   387                 <groupId>org.apache.maven.plugins</groupId>
   388                 <artifactId>maven-source-plugin</artifactId>
   389             </plugin>
   390             <plugin>
   391                 <groupId>com.github.spotbugs</groupId>
   392                 <artifactId>spotbugs-maven-plugin</artifactId>
   393                 <version>4.5.3.0</version>
   394                 <configuration>
   395                     <excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile>
   396                     <plugins>
   397                         <plugin>
   398                             <groupId>com.h3xstream.findsecbugs</groupId>
   399                             <artifactId>findsecbugs-plugin</artifactId>
   400                             <version>1.12.0</version>
   401                         </plugin>
   402                     </plugins>
   403                 </configuration>
   404             </plugin>
   405             <plugin>
   406                 <groupId>org.apache.maven.plugins</groupId>
   407                 <artifactId>maven-surefire-plugin</artifactId>
   408                 <version>2.22.1</version>
   409                 <configuration>
   410                     <systemPropertyVariables>
   411                         <!--This will disable JenkinsRule timeout-->
   412                         <maven.surefire.debug>true</maven.surefire.debug>
   413                     </systemPropertyVariables>
   414                     <excludes>
   415                         <exclude>**/InjectedTest.java</exclude>
   416                         <exclude>**/*ITest.java</exclude>
   417                     </excludes>
   418                 </configuration>
   419             </plugin>
   420         </plugins>
   421     </build>
   422  	</project>
   423  		`
   424  	plugins, err := getMavenDependencies([]byte(testCase))
   425  	assert.NoError(t, err)
   426  	assert.Equal(t, "org.apache.maven.plugins", plugins[0].GroupId)
   427  	assert.Equal(t, "maven-source-plugin", plugins[0].ArtifactId)
   428  	assert.Equal(t, "com.github.spotbugs", plugins[1].GroupId)
   429  	assert.Equal(t, "spotbugs-maven-plugin", plugins[1].ArtifactId)
   430  	assert.Equal(t, "4.5.3.0", plugins[1].Version)
   431  	assert.Equal(t, "com.h3xstream.findsecbugs", plugins[2].GroupId)
   432  	assert.Equal(t, "findsecbugs-plugin", plugins[2].ArtifactId)
   433  	assert.Equal(t, "1.12.0", plugins[2].Version)
   434  	assert.Equal(t, "org.apache.maven.plugins", plugins[3].GroupId)
   435  	assert.Equal(t, "maven-surefire-plugin", plugins[3].ArtifactId)
   436  	assert.Equal(t, "2.22.1", plugins[3].Version)
   437  }
   438  
   439  func TestGetDependenciesFromDependencyManagement(t *testing.T) {
   440  	testCase := `
   441  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   442          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
   443     <dependencyManagement>
   444         <dependencies>
   445             <dependency>
   446                 <groupId>io.jenkins.tools.bom</groupId>
   447                 <artifactId>bom-2.346.x</artifactId>
   448                 <version>1607.va_c1576527071</version>
   449                 <scope>import</scope>
   450                 <type>pom</type>
   451             </dependency>
   452             <dependency>
   453                 <groupId>com.fasterxml.jackson.core</groupId>
   454                 <artifactId>jackson-core</artifactId>
   455                 <version>2.13.4</version>
   456             </dependency>
   457             <dependency>
   458                 <groupId>com.fasterxml.jackson.core</groupId>
   459                 <artifactId>jackson-databind</artifactId>
   460                 <version>2.13.4.2</version>
   461             </dependency>
   462             <dependency>
   463                 <groupId>com.fasterxml.jackson.core</groupId>
   464                 <artifactId>jackson-annotations</artifactId>
   465                 <version>2.13.4</version>
   466             </dependency>
   467             <dependency>
   468                 <groupId>org.apache.httpcomponents</groupId>
   469                 <artifactId>httpcore</artifactId>
   470                 <version>4.4.15</version>
   471             </dependency>
   472             <dependency>
   473                 <groupId>org.jenkins-ci.plugins.workflow</groupId>
   474                 <artifactId>workflow-durable-task-step</artifactId>
   475                 <version>1190.vc93d7d457042</version>
   476                 <scope>test</scope>
   477             </dependency>
   478         </dependencies>
   479     </dependencyManagement>
   480  </project>
   481  `
   482  	dependencies, err := getMavenDependencies([]byte(testCase))
   483  	assert.NoError(t, err)
   484  	assert.Len(t, dependencies, 6)
   485  	for _, dependency := range dependencies {
   486  		assert.True(t, dependency.foundInDependencyManagement)
   487  	}
   488  }
   489  
   490  func TestGetProjectPoms(t *testing.T) {
   491  	mvnHandler := &MavenPackageHandler{MavenDepTreeManager: java.NewMavenDepTreeManager(&java.DepTreeParams{}, java.Projects, false)}
   492  	currDir, err := os.Getwd()
   493  	assert.NoError(t, err)
   494  	tmpDir, err := os.MkdirTemp("", "")
   495  	assert.NoError(t, err)
   496  	assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "maven"), tmpDir, true, nil))
   497  	assert.NoError(t, os.Chdir(tmpDir))
   498  	defer func() {
   499  		assert.NoError(t, os.Chdir(currDir))
   500  	}()
   501  	assert.NoError(t, mvnHandler.getProjectPoms())
   502  	assert.Len(t, mvnHandler.pomPaths, 2)
   503  }
   504  
   505  // General Utils functions
   506  func TestFixVersionInfo_UpdateFixVersionIfMax(t *testing.T) {
   507  	type testCase struct {
   508  		fixVersionInfo utils.VulnerabilityDetails
   509  		newFixVersion  string
   510  		expectedOutput string
   511  	}
   512  
   513  	testCases := []testCase{
   514  		{fixVersionInfo: utils.VulnerabilityDetails{SuggestedFixedVersion: "1.2.3", IsDirectDependency: true}, newFixVersion: "1.2.4", expectedOutput: "1.2.4"},
   515  		{fixVersionInfo: utils.VulnerabilityDetails{SuggestedFixedVersion: "1.2.3", IsDirectDependency: true}, newFixVersion: "1.0.4", expectedOutput: "1.2.3"},
   516  	}
   517  
   518  	for _, tc := range testCases {
   519  		t.Run(tc.expectedOutput, func(t *testing.T) {
   520  			tc.fixVersionInfo.UpdateFixVersionIfMax(tc.newFixVersion)
   521  			assert.Equal(t, tc.expectedOutput, tc.fixVersionInfo.SuggestedFixedVersion)
   522  		})
   523  	}
   524  }
   525  
   526  func TestUpdatePackageVersion(t *testing.T) {
   527  	testProjectPath := filepath.Join("..", "testdata", "packagehandlers")
   528  	currDir, err := os.Getwd()
   529  	assert.NoError(t, err)
   530  	tmpDir, err := os.MkdirTemp("", "")
   531  	assert.NoError(t, err)
   532  	assert.NoError(t, biutils.CopyDir(testProjectPath, tmpDir, true, nil))
   533  	assert.NoError(t, os.Chdir(tmpDir))
   534  	defer func() {
   535  		assert.NoError(t, os.Chdir(currDir))
   536  	}()
   537  	testCases := []struct {
   538  		impactedPackage             string
   539  		fixedVersion                string
   540  		foundInDependencyManagement bool
   541  	}{
   542  		{impactedPackage: "org.jfrog.filespecs:file-specs-java", fixedVersion: "1.1.2"},
   543  		{impactedPackage: "com.fasterxml.jackson.core:jackson-core", fixedVersion: "2.15.0", foundInDependencyManagement: true},
   544  		{impactedPackage: "org.apache.httpcomponents:httpcore", fixedVersion: "4.4.16", foundInDependencyManagement: true},
   545  	}
   546  	mvnHandler := &MavenPackageHandler{MavenDepTreeManager: &java.MavenDepTreeManager{}}
   547  	for _, test := range testCases {
   548  		assert.NoError(t, mvnHandler.updatePackageVersion(test.impactedPackage, test.fixedVersion, test.foundInDependencyManagement))
   549  	}
   550  	modifiedPom, err := os.ReadFile("pom.xml")
   551  	assert.NoError(t, err)
   552  	for _, test := range testCases {
   553  		assert.Contains(t, fmt.Sprintf("<version>%s</version>", string(modifiedPom)), test.fixedVersion)
   554  	}
   555  
   556  	// Test non-existing version error
   557  	assert.ErrorContains(t,
   558  		mvnHandler.updatePackageVersion("org.apache.httpcomponents:httpcore", "non.existing.version", true),
   559  		fmt.Sprintf(MavenVersionNotAvailableErrorFormat, "non.existing.version"))
   560  }
   561  
   562  func TestUpdatePropertiesVersion(t *testing.T) {
   563  	testProjectPath := filepath.Join("..", "testdata", "packagehandlers")
   564  	currDir, err := os.Getwd()
   565  	assert.NoError(t, err)
   566  	tmpDir, err := os.MkdirTemp("", "")
   567  	assert.NoError(t, err)
   568  	assert.NoError(t, biutils.CopyDir(testProjectPath, tmpDir, true, nil))
   569  	assert.NoError(t, os.Chdir(tmpDir))
   570  	defer func() {
   571  		assert.NoError(t, os.Chdir(currDir))
   572  	}()
   573  	mvnHandler := &MavenPackageHandler{MavenDepTreeManager: &java.MavenDepTreeManager{}}
   574  	assert.NoError(t, mvnHandler.updateProperties(&pomDependencyDetails{properties: []string{"buildinfo.version"}}, "2.39.9"))
   575  	modifiedPom, err := os.ReadFile("pom.xml")
   576  	assert.NoError(t, err)
   577  	assert.Contains(t, string(modifiedPom), "2.39.9")
   578  }
   579  
   580  func getTestDataDir(t *testing.T, directDependency bool) string {
   581  	var projectDir string
   582  	if directDependency {
   583  		projectDir = "projects"
   584  	} else {
   585  		projectDir = "indirect-projects"
   586  	}
   587  	testdataDir, err := filepath.Abs(filepath.Join("..", "testdata", projectDir))
   588  	assert.NoError(t, err)
   589  	return testdataDir
   590  }
   591  
   592  func createTempDirAndChdir(t *testing.T, testdataDir string, tech string) func() {
   593  	// Create temp technology project
   594  	projectPath := filepath.Join(testdataDir, tech)
   595  	tmpProjectPath, cleanup := tests.CreateTestProject(t, projectPath)
   596  	currDir, err := os.Getwd()
   597  	assert.NoError(t, err)
   598  	assert.NoError(t, os.Chdir(tmpProjectPath))
   599  	if tech == "go" {
   600  		err = removeTxtSuffix("go.mod.txt")
   601  		assert.NoError(t, err)
   602  		err = removeTxtSuffix("go.sum.txt")
   603  		assert.NoError(t, err)
   604  		err = removeTxtSuffix("main.go.txt")
   605  		assert.NoError(t, err)
   606  	}
   607  	return func() {
   608  		cleanup()
   609  		assert.NoError(t, os.Chdir(currDir))
   610  	}
   611  }
   612  
   613  func removeTxtSuffix(txtFileName string) error {
   614  	// go.sum.txt  >> go.sum
   615  	return fileutils.MoveFile(txtFileName, strings.TrimSuffix(txtFileName, ".txt"))
   616  }
   617  
   618  func assertFixVersionInPackageDescriptor(t *testing.T, test dependencyFixTest, packageDescriptor string) {
   619  	file, err := os.ReadFile(packageDescriptor)
   620  	assert.NoError(t, err)
   621  
   622  	assert.Contains(t, string(file), test.vulnDetails.SuggestedFixedVersion)
   623  	// Verify that case-sensitive packages in python are lowered
   624  	assert.Contains(t, string(file), strings.ToLower(test.vulnDetails.ImpactedDependencyName))
   625  
   626  }
   627  
   628  // This function is intended to add unique checks for specific package managers
   629  func uniquePackageManagerChecks(t *testing.T, test dependencyFixTest) {
   630  	technology := test.vulnDetails.Technology
   631  	extraArgs := test.uniqueChecksExtraArgs
   632  	switch technology {
   633  	case coreutils.Go:
   634  		packageDescriptor := extraArgs[0]
   635  		assertFixVersionInPackageDescriptor(t, test, packageDescriptor)
   636  	case coreutils.Gradle:
   637  		descriptorFilesPaths, err := getDescriptorFilesPaths()
   638  		assert.NoError(t, err)
   639  		assert.Equal(t, len(descriptorFilesPaths), 2, "incorrect number of descriptor files found")
   640  		for _, packageDescriptor := range descriptorFilesPaths {
   641  			assertFixVersionInPackageDescriptor(t, test, packageDescriptor)
   642  		}
   643  	default:
   644  	}
   645  }
   646  
   647  func TestNugetFixVulnerabilityIfExists(t *testing.T) {
   648  	var testcases = []struct {
   649  		vulnerabilityDetails *utils.VulnerabilityDetails
   650  	}{
   651  		// Basic check
   652  		{
   653  			vulnerabilityDetails: &utils.VulnerabilityDetails{
   654  				SuggestedFixedVersion:       "1.1.1",
   655  				IsDirectDependency:          true,
   656  				VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "snappier", ImpactedDependencyVersion: "1.1.0"}}},
   657  		},
   658  		// This testcase checks a fix with a vulnerability that has '.' in the dependency's group and name + more complex version, including letters, to check that the regexp captures them correctly
   659  		{
   660  			vulnerabilityDetails: &utils.VulnerabilityDetails{
   661  				SuggestedFixedVersion:       "7.0.11",
   662  				IsDirectDependency:          true,
   663  				VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "Microsoft.Bcl.AsyncInterfaces", ImpactedDependencyVersion: "8.0.0-rc.1.23419.4"}}},
   664  		},
   665  	}
   666  	testRootDir, err := os.Getwd()
   667  	assert.NoError(t, err)
   668  
   669  	tmpDir, err := os.MkdirTemp("", "")
   670  	assert.NoError(t, err)
   671  	assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "dotnet"), tmpDir, true, nil))
   672  	assert.NoError(t, os.Chdir(tmpDir))
   673  	defer func() {
   674  		assert.NoError(t, os.Chdir(testRootDir))
   675  	}()
   676  
   677  	assetFiles, err := getAssetsFilesPaths()
   678  	assert.NoError(t, err)
   679  	testedAssetFile := assetFiles[0]
   680  
   681  	nph := &NugetPackageHandler{}
   682  
   683  	for _, testcase := range testcases {
   684  		vulnRegexpCompiler := getVulnerabilityRegexCompiler(testcase.vulnerabilityDetails.ImpactedDependencyName, testcase.vulnerabilityDetails.ImpactedDependencyVersion)
   685  		var isFileChanged bool
   686  		isFileChanged, err = nph.fixVulnerabilityIfExists(testcase.vulnerabilityDetails, testedAssetFile, vulnRegexpCompiler, tmpDir)
   687  		assert.NoError(t, err)
   688  		assert.True(t, isFileChanged)
   689  	}
   690  
   691  	var fixedFileContent []byte
   692  	fixedFileContent, err = os.ReadFile(testedAssetFile)
   693  	fixedFileContentString := string(fixedFileContent)
   694  
   695  	assert.NoError(t, err)
   696  	assert.NotContains(t, fixedFileContentString, "<PackageReference Include=\"snappier\" Version=\"1.1.0\" />")
   697  	assert.Contains(t, fixedFileContentString, "<PackageReference Include=\"snappier\" Version=\"1.1.1\" />")
   698  	assert.NotContains(t, fixedFileContentString, "<PackageReference Include=\"Microsoft.Bcl.AsyncInterfaces\" Version=\"8.0.0-rc.1.23419.4\" />")
   699  	assert.Contains(t, fixedFileContentString, "<PackageReference Include=\"Microsoft.Bcl.AsyncInterfaces\" Version=\"7.0.11\" />")
   700  }
   701  
   702  func TestGetFixedPackage(t *testing.T) {
   703  	var testcases = []struct {
   704  		impactedPackage       string
   705  		versionOperator       string
   706  		suggestedFixedVersion string
   707  		expectedOutput        []string
   708  	}{
   709  		{
   710  			impactedPackage:       "snappier",
   711  			versionOperator:       " -v ",
   712  			suggestedFixedVersion: "1.1.1",
   713  			expectedOutput:        []string{"snappier", "-v", "1.1.1"},
   714  		},
   715  		{
   716  			impactedPackage:       "json",
   717  			versionOperator:       "@",
   718  			suggestedFixedVersion: "10.0.0",
   719  			expectedOutput:        []string{"json@10.0.0"},
   720  		},
   721  	}
   722  
   723  	for _, test := range testcases {
   724  		fixedPackageArgs := getFixedPackage(test.impactedPackage, test.versionOperator, test.suggestedFixedVersion)
   725  		assert.Equal(t, test.expectedOutput, fixedPackageArgs)
   726  	}
   727  }
   728  
   729  func TestGradleGetDescriptorFilesPaths(t *testing.T) {
   730  	currDir, err := os.Getwd()
   731  	assert.NoError(t, err)
   732  	tmpDir, err := os.MkdirTemp("", "")
   733  	assert.NoError(t, err)
   734  	assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "gradle"), tmpDir, true, nil))
   735  	assert.NoError(t, os.Chdir(tmpDir))
   736  	defer func() {
   737  		assert.NoError(t, os.Chdir(currDir))
   738  	}()
   739  	finalPath, err := os.Getwd()
   740  	assert.NoError(t, err)
   741  
   742  	expectedResults := []string{filepath.Join(finalPath, groovyDescriptorFileSuffix), filepath.Join(finalPath, "innerProjectForTest", kotlinDescriptorFileSuffix)}
   743  
   744  	buildFilesPaths, err := getDescriptorFilesPaths()
   745  	assert.NoError(t, err)
   746  	assert.ElementsMatch(t, expectedResults, buildFilesPaths)
   747  }
   748  
   749  func TestGradleFixVulnerabilityIfExists(t *testing.T) {
   750  	var testcases = []struct {
   751  		vulnerabilityDetails *utils.VulnerabilityDetails
   752  	}{
   753  		// Basic check
   754  		{
   755  			vulnerabilityDetails: &utils.VulnerabilityDetails{
   756  				SuggestedFixedVersion:       "4.13.1",
   757  				IsDirectDependency:          true,
   758  				VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "junit:junit", ImpactedDependencyVersion: "4.7"}}},
   759  		},
   760  		// This testcase checks a fix with a vulnerability that has '.' in the dependency's group and name + more complex version, including letters, to check that the regexp captures them correctly
   761  		{
   762  			vulnerabilityDetails: &utils.VulnerabilityDetails{
   763  				SuggestedFixedVersion:       "1.9.9",
   764  				IsDirectDependency:          true,
   765  				VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "my.group:my.dot.name", ImpactedDependencyVersion: "1.0.0-beta.test"}}},
   766  		},
   767  	}
   768  
   769  	currDir, err := os.Getwd()
   770  	assert.NoError(t, err)
   771  
   772  	tmpDir, err := os.MkdirTemp("", "")
   773  	assert.NoError(t, err)
   774  	assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "gradle"), tmpDir, true, nil))
   775  	assert.NoError(t, os.Chdir(tmpDir))
   776  	defer func() {
   777  		assert.NoError(t, os.Chdir(currDir))
   778  	}()
   779  
   780  	descriptorFiles, err := getDescriptorFilesPaths()
   781  	assert.NoError(t, err)
   782  
   783  	gph := GradlePackageHandler{}
   784  
   785  	for _, descriptorFile := range descriptorFiles {
   786  		for _, testcase := range testcases {
   787  			var isFileChanged bool
   788  			isFileChanged, err = gph.fixVulnerabilityIfExists(descriptorFile, testcase.vulnerabilityDetails)
   789  			assert.NoError(t, err)
   790  			assert.True(t, isFileChanged)
   791  		}
   792  		compareFixedFileToComparisonFile(t, descriptorFile)
   793  	}
   794  
   795  }
   796  
   797  func compareFixedFileToComparisonFile(t *testing.T, descriptorFileAbsPath string) {
   798  	var compareFilePath string
   799  	if strings.HasSuffix(descriptorFileAbsPath, groovyDescriptorFileSuffix) {
   800  		curDirPath := strings.TrimSuffix(descriptorFileAbsPath, groovyDescriptorFileSuffix)
   801  		compareFilePath = filepath.Join(curDirPath, "fixedBuildGradleForCompare.txt")
   802  	} else {
   803  		curDirPath := strings.TrimSuffix(descriptorFileAbsPath, kotlinDescriptorFileSuffix)
   804  		compareFilePath = filepath.Join(curDirPath, "fixedBuildGradleKtsForCompare.txt")
   805  	}
   806  
   807  	expectedFileContent, err := os.ReadFile(descriptorFileAbsPath)
   808  	assert.NoError(t, err)
   809  
   810  	fixedFileContent, err := os.ReadFile(compareFilePath)
   811  	assert.NoError(t, err)
   812  
   813  	assert.ElementsMatch(t, expectedFileContent, fixedFileContent)
   814  }
   815  
   816  func TestGradleIsVersionSupportedForFix(t *testing.T) {
   817  	var testcases = []struct {
   818  		impactedVersion string
   819  		expectedResult  bool
   820  	}{
   821  		{
   822  			impactedVersion: "10.+",
   823  			expectedResult:  false,
   824  		},
   825  		{
   826  			impactedVersion: "[10.3, 11.0)",
   827  			expectedResult:  false,
   828  		},
   829  		{
   830  			impactedVersion: "(10.4.2, 11.7.8)",
   831  			expectedResult:  false,
   832  		},
   833  		{
   834  			impactedVersion: "latest.release",
   835  			expectedResult:  false,
   836  		},
   837  		{
   838  			impactedVersion: "5.5",
   839  			expectedResult:  true,
   840  		},
   841  		{
   842  			impactedVersion: "9.0.13-beta",
   843  			expectedResult:  true,
   844  		},
   845  	}
   846  
   847  	for _, testcase := range testcases {
   848  		assert.Equal(t, testcase.expectedResult, isVersionSupportedForFix(testcase.impactedVersion))
   849  	}
   850  }