Conversation
orto17
commented
Feb 18, 2026
- All tests passed. If this feature is not already covered by the tests, I added new tests.
- This pull request is on the dev branch.
- I used gofmt for formatting the code before submitting the pull request.
- Update documentation about new features / new supported technologies
attiasas
left a comment
There was a problem hiding this comment.
Nice!, take a look at my comments.
- consider fixing linter issues
- consider adding more test cases to expand coverage
| ) | ||
|
|
||
| func TestMavenUpdateRegularDependency(t *testing.T) { | ||
| testProjectPath := filepath.Join("..", "testdata", "packagehandlers") |
There was a problem hiding this comment.
| testProjectPath := filepath.Join("..", "testdata", "packagehandlers") | |
| testProjectPath := filepath.Join("..", "testdata", "packageupdaters") |
test fails, you should pass the following.
I also suggest adding sub folder like maven and optionally even more under maven like regular to be prepared for more test cases in future
| if project.Parent.GroupId == groupId && project.Parent.ArtifactId == artifactId { | ||
| pattern := regexp.MustCompile(`(?s)(<parent>\s*<groupId>` + regexp.QuoteMeta(groupId) + `</groupId>\s*<artifactId>` + regexp.QuoteMeta(artifactId) + `</artifactId>\s*<version>)[^<]+(</version>)`) | ||
| newContent := pattern.ReplaceAll(content, []byte("${1}"+fixedVersion+"${2}")) | ||
| if !bytes.Equal(content, newContent) { | ||
| log.Debug("Updated parent", toDependencyName(groupId, artifactId), "to", fixedVersion) | ||
| return true, newContent |
There was a problem hiding this comment.
make sure this will not fail on pom with vars. $ in a property may cause interaption to the replacer.
try and add test case for a pom similar to:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<!-- ===================== -->
<!-- Version Variables -->
<!-- ===================== -->
<properties>
<java.version>17</java.version>
<spring.boot.version>3.2.5</spring.boot.version>
<junit.version>5.10.2</junit.version>
<lombok.version>1.18.32</lombok.version>
</properties>
<!-- ===================== -->
<!-- Dependency Management -->
<!-- ===================== -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- ===================== -->
<!-- Dependencies -->
<!-- ===================== -->
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- ===================== -->
<!-- Build Plugins -->
<!-- ===================== -->
<build>
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
| var pomPaths []string | ||
| for _, component := range vulnDetails.Components { | ||
| if component.PreferredLocation != nil && component.PreferredLocation.File != "" { | ||
| pomPaths = append(pomPaths, component.PreferredLocation.File) |
There was a problem hiding this comment.
this may cause duplication, maybe use pomPaths := datastructures.MakeSet[string]()?
| pomPaths := m.getVulnerabilityOccurrences(vulnDetails) | ||
| if len(pomPaths) == 0 { | ||
| return fmt.Errorf("no pom.xml locations found for %s - Components array is empty or missing Location data", vulnDetails.ImpactedDependencyName) | ||
| } |
There was a problem hiding this comment.
| pomPaths := m.getVulnerabilityOccurrences(vulnDetails) | |
| if len(pomPaths) == 0 { | |
| return fmt.Errorf("no pom.xml locations found for %s - Components array is empty or missing Location data", vulnDetails.ImpactedDependencyName) | |
| } | |
| pomPaths := m.getVulnerabilityOccurrences(vulnDetails) | |
| if len(pomPaths) == 0 { | |
| return fmt.Errorf("no pom.xml locations found for %s - Components array is empty or missing Location data", vulnDetails.ImpactedDependencyName) | |
| } | |
| llog.Verbose(fmt.Sprintf("Found vulnerability %s occurrences for component %s in %s", vulnDetails.IssueId, vulnDetails.ImpactedDependencyVersion, strings.Join(pomPaths, ", "))) |
Consider adding logs
| "github.com/jfrog/frogbot/v2/utils" | ||
| ) | ||
|
|
||
| func TestMavenUpdateRegularDependency(t *testing.T) { |
There was a problem hiding this comment.
please unify TestMavenUpdateRegularDependency, TestMavenUpdateDependencyManagement, TestMavenUpdatePropertyVersion and TestMavenUpdateParentPOM if possible, the code seems pretty similar to all of them.
Make usre to give a clear name for each test case in the t.Run
| assert.NotContains(t, string(modifiedPom), "<version>2.5.0</version>") | ||
| } | ||
|
|
||
| func TestMavenDependencyNotFound(t *testing.T) { |
There was a problem hiding this comment.
please unify with TestMavenIndirectDependencyNotSupported for one test that checks the failing fixes
| _, clearMavenDepTreeRun, err = mph.CreateTempDirWithSettingsXmlIfNeeded() | ||
| if err != nil { | ||
| return | ||
| func (m *MavenPackageUpdater) getVulnerabilityOccurrences(vulnDetails *utils.VulnerabilityDetails) []string { |
There was a problem hiding this comment.
remove this function and dont use preferredLocation in general (this is intended for the CLI not for Frogbot).
please use GetVulnerabilityLocations to get all occurences of descriptors to fix, and then iterate them all and fix each of them
| var errors []string | ||
| for _, pomPath := range pomPaths { | ||
| if err := m.updatePomFile(pomPath, groupId, artifactId, vulnDetails.SuggestedFixedVersion); err != nil { | ||
| errors = append(errors, fmt.Sprintf("%s: %v", pomPath, err)) |
There was a problem hiding this comment.
| errors = append(errors, fmt.Sprintf("%s: %v", pomPath, err)) | |
| failedFixErrorMsg := fmt.Errorf("failed to fix '%s' in descriptor '%s': %w", vulnDetails.ImpactedDependencyName, descriptorPath, fixErr) | |
| err = errors.Join(err, failedFixErrorMsg) |
There was a problem hiding this comment.
also we can add log.Warn(failedFixErrorMsg) to match what we did in Golang and NPM
There was a problem hiding this comment.
create an array called 'failingDescriptors' and accumulate the names of the descriptors that failed so we can indicate them all in the final error - see my error suggestion
| IsMavenDepTreeInstalled: true, | ||
| var errors []string | ||
| for _, pomPath := range pomPaths { | ||
| if err := m.updatePomFile(pomPath, groupId, artifactId, vulnDetails.SuggestedFixedVersion); err != nil { |
There was a problem hiding this comment.
| if err := m.updatePomFile(pomPath, groupId, artifactId, vulnDetails.SuggestedFixedVersion); err != nil { | |
| if fixErr := m.updatePomFile(pomPath, groupId, artifactId, vulnDetails.SuggestedFixedVersion); fixErr != nil { |
| depTreeParams := &java.DepTreeParams{ | ||
| Server: scanDetails.ServerDetails, | ||
| IsMavenDepTreeInstalled: true, | ||
| var errors []string |
| pomPaths []pomPath | ||
| // mavenDepTreeManager handles the installation and execution of the maven-dep-tree to obtain all the project poms and running mvn commands | ||
| *java.MavenDepTreeManager | ||
| if len(errors) > 0 { |
There was a problem hiding this comment.
| if len(errors) > 0 { | |
| if err != nil |
| // mavenDepTreeManager handles the installation and execution of the maven-dep-tree to obtain all the project poms and running mvn commands | ||
| *java.MavenDepTreeManager | ||
| if len(errors) > 0 { | ||
| return fmt.Errorf("failed to update pom.xml files:\n%s", strings.Join(errors, "\n")) |
There was a problem hiding this comment.
| return fmt.Errorf("failed to update pom.xml files:\n%s", strings.Join(errors, "\n")) | |
| return fmt.Errorf("encountered errors while fixing '%s' vulnerability in descriptors [%s]: %w", vulnDetails.ImpactedDependencyName, strings.Join(failingDescriptors, ", "), err) |
| content, err := os.ReadFile(pomPath) | ||
| if err != nil { | ||
| return err | ||
| return fmt.Errorf("failed to update Pom file: failed to read %s: %w", pomPath, err) |
There was a problem hiding this comment.
| return fmt.Errorf("failed to update Pom file: failed to read %s: %w", pomPath, err) | |
| return fmt.Errorf("failed to read %s: %w", pomPath, err) |
we dont need to state here 'failed to update pom' this will be a duplication with the wrapping error
| mph.pomDependencies = make(map[string]pomDependencyDetails) | ||
| var project mavenProject | ||
| if err = xml.Unmarshal(content, &project); err != nil { | ||
| return fmt.Errorf("failed to update Pom file: failed to parse %s: %w", pomPath, err) |
There was a problem hiding this comment.
| return fmt.Errorf("failed to update Pom file: failed to parse %s: %w", pomPath, err) | |
| return fmt.Errorf("failed to parse %s: %w", pomPath, err) |
| return err | ||
|
|
||
| var updated bool | ||
| newContent := content |
There was a problem hiding this comment.
what is this used for? this is not a copy of the content but rather a copy of the pointer, so we change the actual 'content' anyway. Also - there is no revert to this backup in case of a failure from what I see.
| var updated bool | ||
| newContent := content | ||
|
|
||
| if updated, newContent = m.updateInParent(&project, groupId, artifactId, fixedVersion, newContent); updated { |
There was a problem hiding this comment.
if removing this 'newContent := content' line make sure to pass 'content' here in the parameters
| } | ||
|
|
||
| err = mph.getProjectPoms() | ||
| func (m *MavenPackageUpdater) updatePomFile(pomPath, groupId, artifactId, fixedVersion string) error { |
There was a problem hiding this comment.
There is something wrong with the fixing process in general. for each vulnerability we might need to fix it in 3 different places (parent, manegement and regular).
In order to keep the pom.xml valid we have to make sure to update in ALL necessary locations or in none of them.
I suggest the following:
Attempt to make all 3 fixes on the content WITHOUT writing to the file after each time. if all fixes that are needed succeeded we write to the file (and we also save 2 writes to the file). if any of the needed failed - we return an error and we dont even have to restore a backup since we only write to the file at the end
| pattern := regexp.MustCompile(`(?s)(<parent>\s*<groupId>` + regexp.QuoteMeta(groupId) + `</groupId>\s*<artifactId>` + regexp.QuoteMeta(artifactId) + `</artifactId>\s*<version>)[^<]+(</version>)`) | ||
| newContent := pattern.ReplaceAll(content, []byte("${1}"+fixedVersion+"${2}")) | ||
| if !bytes.Equal(content, newContent) { | ||
| log.Debug("Updated parent", toDependencyName(groupId, artifactId), "to", fixedVersion) |
There was a problem hiding this comment.
redundant log for my opinoin. lets log the final result if we managed to update the dep in all places not in every section. for big projects this will create a massive log
| pattern := regexp.MustCompile(`(?s)(<groupId>` + regexp.QuoteMeta(groupId) + `</groupId>\s*<artifactId>` + regexp.QuoteMeta(artifactId) + `</artifactId>\s*<version>)[^<]+(</version>)`) | ||
| newContent := pattern.ReplaceAll(content, []byte("${1}"+fixedVersion+"${2}")) | ||
| if !bytes.Equal(content, newContent) { | ||
| log.Debug("Updated dependency", toDependencyName(groupId, artifactId), "to", fixedVersion) |
There was a problem hiding this comment.
redundant log for my opinoin. lets log the final result if we managed to update the dep in all places not in every section. for big projects this will create a massive log
| pattern := regexp.MustCompile(`(<` + regexp.QuoteMeta(propertyName) + `>)[^<]+(</` + regexp.QuoteMeta(propertyName) + `>)`) | ||
| newContent := pattern.ReplaceAll(content, []byte("${1}"+newValue+"${2}")) | ||
| if !bytes.Equal(content, newContent) { | ||
| log.Debug("Updated property", propertyName, "to", newValue) |
There was a problem hiding this comment.
redundant log for my opinoin. lets log the final result if we managed to update the dep in all places not in every section. for big projects this will create a massive log
