From c9201c463232b4af0d21829f3a02ab8bcac0d972 Mon Sep 17 00:00:00 2001
From: Thanh Hai Trinh <thanh.hai.trinh@sap.com>
Date: Wed, 29 May 2024 09:31:58 +0200
Subject: [PATCH 1/6] fix(cxOne): get Git branch from commonPipelineEnvironment

---
 cmd/checkmarxOneExecuteScan_generated.go        | 2 +-
 resources/metadata/checkmarxOneExecuteScan.yaml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmd/checkmarxOneExecuteScan_generated.go b/cmd/checkmarxOneExecuteScan_generated.go
index 06d8d653f2..9f94fa8f4b 100644
--- a/cmd/checkmarxOneExecuteScan_generated.go
+++ b/cmd/checkmarxOneExecuteScan_generated.go
@@ -528,7 +528,7 @@ func checkmarxOneExecuteScanMetadata() config.StepData {
 						ResourceRef: []config.ResourceReference{
 							{
 								Name:  "commonPipelineEnvironment",
-								Param: "github/branch",
+								Param: "git/branch",
 							},
 						},
 						Scope:     []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
diff --git a/resources/metadata/checkmarxOneExecuteScan.yaml b/resources/metadata/checkmarxOneExecuteScan.yaml
index 0807f7ff2e..189876034e 100644
--- a/resources/metadata/checkmarxOneExecuteScan.yaml
+++ b/resources/metadata/checkmarxOneExecuteScan.yaml
@@ -131,7 +131,7 @@ spec:
         description: "Set the GitHub repository branch."
         resourceRef:
           - name: commonPipelineEnvironment
-            param: github/branch
+            param: git/branch
         scope:
           - GENERAL
           - PARAMETERS

From c84c95558e841510ddc0f29708e9c54fa76bfcb6 Mon Sep 17 00:00:00 2001
From: Thanh Hai Trinh <thanh.hai.trinh@sap.com>
Date: Thu, 30 May 2024 14:44:11 +0200
Subject: [PATCH 2/6] fix(cxOne): add params to tag a scan and a project

---
 cmd/checkmarxOneExecuteScan.go                | 36 +++++++++++++++++--
 cmd/checkmarxOneExecuteScan_generated.go      | 22 ++++++++++++
 pkg/checkmarxone/checkmarxone.go              | 35 +++++++++++++-----
 .../metadata/checkmarxOneExecuteScan.yaml     | 16 +++++++++
 4 files changed, 99 insertions(+), 10 deletions(-)

diff --git a/cmd/checkmarxOneExecuteScan.go b/cmd/checkmarxOneExecuteScan.go
index 24731bc674..9939b8a56c 100644
--- a/cmd/checkmarxOneExecuteScan.go
+++ b/cmd/checkmarxOneExecuteScan.go
@@ -3,8 +3,10 @@ package cmd
 import (
 	"archive/zip"
 	"context"
+	"encoding/json"
 	"fmt"
 	"io"
+	"maps"
 	"math"
 	"os"
 	"path/filepath"
@@ -112,6 +114,14 @@ func runStep(config checkmarxOneExecuteScanOptions, influx *checkmarxOneExecuteS
 		return fmt.Errorf("failed to set preset: %s", err)
 	}
 
+	// update project's tags
+	if (len(config.ProjectTags)) > 0 {
+		err = cx1sh.UpdateProjectTags()
+		if err != nil {
+			log.Entry().WithError(err).Warnf("failed to tags the project: %s", err)
+		}
+	}
+
 	scans, err := cx1sh.GetLastScans(10)
 	if err != nil {
 		log.Entry().WithError(err).Warnf("failed to get last 10 scans")
@@ -298,6 +308,19 @@ func (c *checkmarxOneExecuteScanHelper) CreateProject() (*checkmarxOne.Project,
 	return &project, nil
 }
 
+func (c *checkmarxOneExecuteScanHelper) UpdateProjectTags() error {
+	tags := make(map[string]string, 0)
+	err := json.Unmarshal([]byte(c.config.ProjectTags), &tags)
+	if err != nil {
+		log.Entry().Infof("Failed to parse the project tags: %v", c.config.ProjectTags)
+		return err
+	}
+	// merge new tags to the existing ones
+	maps.Copy(c.Project.Tags, tags)
+
+	return c.sys.UpdateProject(c.Project)
+}
+
 func (c *checkmarxOneExecuteScanHelper) SetProjectPreset() error {
 	projectConf, err := c.sys.GetProjectConfiguration(c.Project.ProjectID)
 
@@ -431,9 +454,18 @@ func (c *checkmarxOneExecuteScanHelper) CreateScanRequest(incremental bool, uplo
 	log.Entry().Infof("Will run a scan with the following configuration: %v", sastConfigString)
 
 	configs := []checkmarxOne.ScanConfiguration{sastConfig}
-	// add more engines
 
-	scan, err := c.sys.ScanProjectZip(c.Project.ProjectID, uploadLink, branch, configs)
+	// add scan's tags
+	tags := make(map[string]string, 0)
+	if len(c.config.ScanTags) > 0 {
+		err := json.Unmarshal([]byte(c.config.ScanTags), &tags)
+		if err != nil {
+			log.Entry().Infof("Failed to parse the scan tags: %v", c.config.ScanTags)
+		}
+	}
+
+	// add more engines
+	scan, err := c.sys.ScanProjectZip(c.Project.ProjectID, uploadLink, branch, configs, tags)
 
 	if err != nil {
 		return nil, fmt.Errorf("Failed to run scan on project %v: %s", c.Project.Name, err)
diff --git a/cmd/checkmarxOneExecuteScan_generated.go b/cmd/checkmarxOneExecuteScan_generated.go
index 9f94fa8f4b..6e14070dbb 100644
--- a/cmd/checkmarxOneExecuteScan_generated.go
+++ b/cmd/checkmarxOneExecuteScan_generated.go
@@ -39,6 +39,8 @@ type checkmarxOneExecuteScanOptions struct {
 	LanguageMode                         string   `json:"languageMode,omitempty"`
 	ProjectCriticality                   string   `json:"projectCriticality,omitempty"`
 	ProjectName                          string   `json:"projectName,omitempty"`
+	ProjectTags                          string   `json:"projectTags,omitempty"`
+	ScanTags                             string   `json:"scanTags,omitempty"`
 	Branch                               string   `json:"branch,omitempty"`
 	PullRequestName                      string   `json:"pullRequestName,omitempty"`
 	Repository                           string   `json:"repository,omitempty"`
@@ -364,6 +366,8 @@ func addCheckmarxOneExecuteScanFlags(cmd *cobra.Command, stepConfig *checkmarxOn
 	cmd.Flags().StringVar(&stepConfig.LanguageMode, "languageMode", `multi`, "Specifies whether the scan should be run for a 'single' language or 'multi' language, default 'multi'")
 	cmd.Flags().StringVar(&stepConfig.ProjectCriticality, "projectCriticality", `3`, "The criticality of the checkmarxOne project, used during project creation")
 	cmd.Flags().StringVar(&stepConfig.ProjectName, "projectName", os.Getenv("PIPER_projectName"), "The name of the checkmarxOne project to scan into")
+	cmd.Flags().StringVar(&stepConfig.ProjectTags, "projectTags", os.Getenv("PIPER_projectTags"), "Used to tag a project with a JSON string, e.g., {\"key\":\"value\", \"keywithoutvalue\":\"\"}")
+	cmd.Flags().StringVar(&stepConfig.ScanTags, "scanTags", os.Getenv("PIPER_scanTags"), "Used to tag a scan with a JSON string, e.g., {\"key\":\"value\", \"keywithoutvalue\":\"\"}")
 	cmd.Flags().StringVar(&stepConfig.Branch, "branch", os.Getenv("PIPER_branch"), "Used to supply the branch scanned in the repository, or a friendly-name set by the user")
 	cmd.Flags().StringVar(&stepConfig.PullRequestName, "pullRequestName", os.Getenv("PIPER_pullRequestName"), "Used to supply the name for the newly created PR project branch when being used in pull request scenarios. This is supplied by the orchestrator.")
 	cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Set the GitHub repository.")
@@ -615,6 +619,24 @@ func checkmarxOneExecuteScanMetadata() config.StepData {
 						Aliases:     []config.Alias{},
 						Default:     os.Getenv("PIPER_projectName"),
 					},
+					{
+						Name:        "projectTags",
+						ResourceRef: []config.ResourceReference{},
+						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"},
+						Type:        "string",
+						Mandatory:   false,
+						Aliases:     []config.Alias{},
+						Default:     os.Getenv("PIPER_projectTags"),
+					},
+					{
+						Name:        "scanTags",
+						ResourceRef: []config.ResourceReference{},
+						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"},
+						Type:        "string",
+						Mandatory:   false,
+						Aliases:     []config.Alias{},
+						Default:     os.Getenv("PIPER_scanTags"),
+					},
 					{
 						Name:        "branch",
 						ResourceRef: []config.ResourceReference{},
diff --git a/pkg/checkmarxone/checkmarxone.go b/pkg/checkmarxone/checkmarxone.go
index 442c14f308..f28a87a3cc 100644
--- a/pkg/checkmarxone/checkmarxone.go
+++ b/pkg/checkmarxone/checkmarxone.go
@@ -309,9 +309,10 @@ type System interface {
 	GetLastScans(projectID string, limit int) ([]Scan, error)
 	GetLastScansByStatus(projectID string, limit int, status []string) ([]Scan, error)
 
-	ScanProject(projectID, sourceUrl, branch, scanType string, settings []ScanConfiguration) (Scan, error)
-	ScanProjectZip(projectID, sourceUrl, branch string, settings []ScanConfiguration) (Scan, error)
-	ScanProjectGit(projectID, repoUrl, branch string, settings []ScanConfiguration) (Scan, error)
+	ScanProject(projectID, sourceUrl, branch, scanType string, settings []ScanConfiguration, tags map[string]string) (Scan, error)
+	ScanProjectZip(projectID, sourceUrl, branch string, settings []ScanConfiguration, tags map[string]string) (Scan, error)
+	ScanProjectGit(projectID, repoUrl, branch string, settings []ScanConfiguration, tags map[string]string) (Scan, error)
+	UpdateProject(project *Project) error
 
 	UploadProjectSourceCode(projectID string, zipFile string) (string, error)
 	CreateProject(projectName string, groupIDs []string) (Project, error)
@@ -651,6 +652,22 @@ func (sys *SystemInstance) UpdateApplication(app *Application) error {
 	return nil
 }
 
+func (sys *SystemInstance) UpdateProject(project *Project) error {
+	sys.logger.Debugf("Updating project: %v", project.Name)
+	jsonBody, err := json.Marshal(*project)
+	if err != nil {
+		return err
+	}
+
+	_, err = sendRequest(sys, http.MethodPut, fmt.Sprintf("/projects/%v", project.ProjectID), bytes.NewReader(jsonBody), nil, []int{})
+	if err != nil {
+		sys.logger.Errorf("Error while updating project: %s", err)
+		return err
+	}
+
+	return nil
+}
+
 // Updated for Cx1
 func (sys *SystemInstance) GetGroups() ([]Group, error) {
 	sys.logger.Debug("Getting Groups...")
@@ -936,7 +953,7 @@ func (sys *SystemInstance) scanProject(scanConfig map[string]interface{}) (Scan,
 	return scan, err
 }
 
-func (sys *SystemInstance) ScanProjectZip(projectID, sourceUrl, branch string, settings []ScanConfiguration) (Scan, error) {
+func (sys *SystemInstance) ScanProjectZip(projectID, sourceUrl, branch string, settings []ScanConfiguration, tags map[string]string) (Scan, error) {
 	jsonBody := map[string]interface{}{
 		"project": map[string]interface{}{"id": projectID},
 		"type":    "upload",
@@ -945,6 +962,7 @@ func (sys *SystemInstance) ScanProjectZip(projectID, sourceUrl, branch string, s
 			"branch":    branch,
 		},
 		"config": settings,
+		"tags":   tags,
 	}
 
 	scan, err := sys.scanProject(jsonBody)
@@ -954,7 +972,7 @@ func (sys *SystemInstance) ScanProjectZip(projectID, sourceUrl, branch string, s
 	return scan, err
 }
 
-func (sys *SystemInstance) ScanProjectGit(projectID, repoUrl, branch string, settings []ScanConfiguration) (Scan, error) {
+func (sys *SystemInstance) ScanProjectGit(projectID, repoUrl, branch string, settings []ScanConfiguration, tags map[string]string) (Scan, error) {
 	jsonBody := map[string]interface{}{
 		"project": map[string]interface{}{"id": projectID},
 		"type":    "git",
@@ -963,6 +981,7 @@ func (sys *SystemInstance) ScanProjectGit(projectID, repoUrl, branch string, set
 			"branch":  branch,
 		},
 		"config": settings,
+		"tags":   tags,
 	}
 
 	scan, err := sys.scanProject(jsonBody)
@@ -972,11 +991,11 @@ func (sys *SystemInstance) ScanProjectGit(projectID, repoUrl, branch string, set
 	return scan, err
 }
 
-func (sys *SystemInstance) ScanProject(projectID, sourceUrl, branch, scanType string, settings []ScanConfiguration) (Scan, error) {
+func (sys *SystemInstance) ScanProject(projectID, sourceUrl, branch, scanType string, settings []ScanConfiguration, tags map[string]string) (Scan, error) {
 	if scanType == "upload" {
-		return sys.ScanProjectZip(projectID, sourceUrl, branch, settings)
+		return sys.ScanProjectZip(projectID, sourceUrl, branch, settings, tags)
 	} else if scanType == "git" {
-		return sys.ScanProjectGit(projectID, sourceUrl, branch, settings)
+		return sys.ScanProjectGit(projectID, sourceUrl, branch, settings, tags)
 	}
 
 	return Scan{}, errors.New("Invalid scanType provided, must be 'upload' or 'git'")
diff --git a/resources/metadata/checkmarxOneExecuteScan.yaml b/resources/metadata/checkmarxOneExecuteScan.yaml
index 189876034e..ff89ea18b3 100644
--- a/resources/metadata/checkmarxOneExecuteScan.yaml
+++ b/resources/metadata/checkmarxOneExecuteScan.yaml
@@ -202,6 +202,22 @@ spec:
           - PARAMETERS
           - STAGES
           - STEPS
+      - name: projectTags
+        type: string
+        description: Used to tag a project with a JSON string, e.g., {"key":"value", "keywithoutvalue":""}
+        mandatory: false
+        scope:
+          - PARAMETERS
+          - STAGES
+          - STEPS
+      - name: scanTags
+        type: string
+        description: Used to tag a scan with a JSON string, e.g., {"key":"value", "keywithoutvalue":""}
+        mandatory: false
+        scope:
+          - PARAMETERS
+          - STAGES
+          - STEPS
       - name: branch
         type: string
         description: Used to supply the branch scanned in the repository, or a friendly-name set by the user

From f41e03b2ddebb223acf3c8a3901a42674e6b9819 Mon Sep 17 00:00:00 2001
From: Thanh Hai Trinh <thanh.hai.trinh@sap.com>
Date: Thu, 30 May 2024 15:57:38 +0200
Subject: [PATCH 3/6] fix(cxOne): unit test - update project

---
 pkg/checkmarxone/checkmarxone_test.go | 46 +++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/pkg/checkmarxone/checkmarxone_test.go b/pkg/checkmarxone/checkmarxone_test.go
index 63b496698e..6b7ccf499c 100644
--- a/pkg/checkmarxone/checkmarxone_test.go
+++ b/pkg/checkmarxone/checkmarxone_test.go
@@ -2,6 +2,7 @@ package checkmarxOne
 
 import (
 	"bytes"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -294,3 +295,48 @@ func TestGetApplicationByName(t *testing.T) {
 		assert.Contains(t, fmt.Sprint(err), "Provoked technical error")
 	})
 }
+
+func TestUpdateProject(t *testing.T) {
+	logger := log.Entry().WithField("package", "SAP/jenkins-library/pkg/checkmarxOne_test")
+	opts := piperHttp.ClientOptions{}
+
+	requestJson := `{ "id": "702ba12b-ae61-48c0-9b6a-09b17666be32",
+		"name": "test-apr24-piper",
+		"tags": {
+			"\"key1\"": "\"value1\"",
+			"\"keywithoutvalue\"": "\"\""
+		},
+		"groups": [],
+		"criticality": 3,
+		"mainBranch": "",
+		"privatePackage": false
+	}`
+	var project Project
+	_ = json.Unmarshal([]byte(requestJson), &project)
+
+	t.Run("test success", func(t *testing.T) {
+		myTestClient := senderMock{responseBody: ``, httpStatusCode: 204}
+		serverURL := "https://cx1.server.com"
+		sys := SystemInstance{serverURL: serverURL, iamURL: "https://cx1iam.server.com", tenant: "tenant", client: &myTestClient, logger: logger}
+		myTestClient.SetOptions(opts)
+
+		err := sys.UpdateProject(&project)
+		assert.NoError(t, err, "Error occurred but none expected")
+		assert.Equal(t, serverURL+"/api/projects/"+project.ProjectID, myTestClient.urlCalled, "Called url incorrect")
+		assert.Equal(t, "PUT", myTestClient.httpMethod, "HTTP method incorrect")
+		var body Project
+		_ = json.Unmarshal([]byte(myTestClient.requestBody), &body)
+		assert.Equal(t, project, body, "Request body incorrect")
+
+	})
+
+	t.Run("test technical error", func(t *testing.T) {
+		myTestClient := senderMock{httpStatusCode: 403}
+		sys := SystemInstance{serverURL: "https://cx1.server.com", iamURL: "https://cx1iam.server.com", tenant: "tenant", client: &myTestClient, logger: logger}
+		myTestClient.SetOptions(opts)
+		myTestClient.errorExp = true
+
+		err := sys.UpdateProject(&project)
+		assert.Contains(t, fmt.Sprint(err), "Provoked technical error")
+	})
+}

From 22f109e763ef0f09aa4f668ecf73acb3d44de777 Mon Sep 17 00:00:00 2001
From: Thanh Hai Trinh <thanh.hai.trinh@sap.com>
Date: Fri, 31 May 2024 14:46:38 +0200
Subject: [PATCH 4/6] fix(cxOne): unit test - update project tags

---
 cmd/checkmarxOneExecuteScan.go      | 20 +++++----
 cmd/checkmarxOneExecuteScan_test.go | 69 +++++++++++++++++++++++++++--
 2 files changed, 78 insertions(+), 11 deletions(-)

diff --git a/cmd/checkmarxOneExecuteScan.go b/cmd/checkmarxOneExecuteScan.go
index 9939b8a56c..97613ffc84 100644
--- a/cmd/checkmarxOneExecuteScan.go
+++ b/cmd/checkmarxOneExecuteScan.go
@@ -309,16 +309,20 @@ func (c *checkmarxOneExecuteScanHelper) CreateProject() (*checkmarxOne.Project,
 }
 
 func (c *checkmarxOneExecuteScanHelper) UpdateProjectTags() error {
-	tags := make(map[string]string, 0)
-	err := json.Unmarshal([]byte(c.config.ProjectTags), &tags)
-	if err != nil {
-		log.Entry().Infof("Failed to parse the project tags: %v", c.config.ProjectTags)
-		return err
+	if (len(c.config.ProjectTags) > 0 ) {
+		tags := make(map[string]string, 0)
+		err := json.Unmarshal([]byte(c.config.ProjectTags), &tags)
+		if err != nil {
+			log.Entry().Infof("Failed to parse the project tags: %v", c.config.ProjectTags)
+			return err
+		}
+		// merge new tags to the existing ones
+		maps.Copy(c.Project.Tags, tags)
+	
+		return c.sys.UpdateProject(c.Project)
 	}
-	// merge new tags to the existing ones
-	maps.Copy(c.Project.Tags, tags)
 
-	return c.sys.UpdateProject(c.Project)
+	return nil
 }
 
 func (c *checkmarxOneExecuteScanHelper) SetProjectPreset() error {
diff --git a/cmd/checkmarxOneExecuteScan_test.go b/cmd/checkmarxOneExecuteScan_test.go
index 453e8a1780..c9206dbf8d 100644
--- a/cmd/checkmarxOneExecuteScan_test.go
+++ b/cmd/checkmarxOneExecuteScan_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"maps"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -77,15 +78,15 @@ func (sys *checkmarxOneSystemMock) GetLastScansByStatus(projectID string, limit
 	return []checkmarxOne.Scan{}, nil
 }
 
-func (sys *checkmarxOneSystemMock) ScanProject(projectID, sourceUrl, branch, scanType string, settings []checkmarxOne.ScanConfiguration) (checkmarxOne.Scan, error) {
+func (sys *checkmarxOneSystemMock) ScanProject(projectID, sourceUrl, branch, scanType string, settings []checkmarxOne.ScanConfiguration, tags map[string]string) (checkmarxOne.Scan, error) {
 	return checkmarxOne.Scan{}, nil
 }
 
-func (sys *checkmarxOneSystemMock) ScanProjectZip(projectID, sourceUrl, branch string, settings []checkmarxOne.ScanConfiguration) (checkmarxOne.Scan, error) {
+func (sys *checkmarxOneSystemMock) ScanProjectZip(projectID, sourceUrl, branch string, settings []checkmarxOne.ScanConfiguration, tags map[string]string) (checkmarxOne.Scan, error) {
 	return checkmarxOne.Scan{}, nil
 }
 
-func (sys *checkmarxOneSystemMock) ScanProjectGit(projectID, repoUrl, branch string, settings []checkmarxOne.ScanConfiguration) (checkmarxOne.Scan, error) {
+func (sys *checkmarxOneSystemMock) ScanProjectGit(projectID, repoUrl, branch string, settings []checkmarxOne.ScanConfiguration, tags map[string]string) (checkmarxOne.Scan, error) {
 	return checkmarxOne.Scan{}, nil
 }
 
@@ -240,6 +241,10 @@ func (sys *checkmarxOneSystemMock) UpdateProjectConfiguration(projectID string,
 	return nil
 }
 
+func (sys *checkmarxOneSystemMock) UpdateProject(project *checkmarxOne.Project) error {
+	return nil
+}
+
 func (sys *checkmarxOneSystemMock) GetVersion() (checkmarxOne.VersionInfo, error) {
 	return checkmarxOne.VersionInfo{}, nil
 }
@@ -324,3 +329,61 @@ func TestGetGroup(t *testing.T) {
 		assert.Equal(t, group.Name, "Group2")
 	})
 }
+
+func TestUpdateProjectTags(t *testing.T) {
+	t.Parallel()
+
+	sys := &checkmarxOneSystemMock{}
+
+	t.Run("project tags are not provided", func(t *testing.T) {
+		t.Parallel()
+
+		options := checkmarxOneExecuteScanOptions{ProjectName: "ssba", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "CheckmarxDefault" /*GroupName: "NotProvided",*/, VulnerabilityThresholdEnabled: true, GeneratePdfReport: true, APIKey: "testAPIKey", ServerURL: "testURL", IamURL: "testIamURL", Tenant: "testTenant"}
+
+		cx1sh := checkmarxOneExecuteScanHelper{nil, options, sys, nil, nil, nil, nil, nil, nil}
+		err := cx1sh.UpdateProjectTags()
+		assert.NoError(t, err, "Error occurred but none expected")
+	})
+
+	t.Run("project tags are provided correctly", func(t *testing.T) {
+		t.Parallel()
+
+		projectJson := `{ "id": "702ba12b-ae61-48c0-9b6a-09b17666be32",
+			"name": "test-apr24-piper",
+			"tags": {
+				"key1": "value1",
+				"key2": "value2", 
+				"keywithoutvalue1": ""
+			},
+			"groups": [],
+			"criticality": 3,
+			"mainBranch": "",
+			"privatePackage": false
+		}`
+		var project checkmarxOne.Project
+		_ = json.Unmarshal([]byte(projectJson), &project)
+
+		options := checkmarxOneExecuteScanOptions{ProjectName: "ssba", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "CheckmarxDefault" /*GroupName: "NotProvided",*/, VulnerabilityThresholdEnabled: true, GeneratePdfReport: true, APIKey: "testAPIKey", ServerURL: "testURL", IamURL: "testIamURL", Tenant: "testTenant", ProjectTags: `{"key3":"value3", "key2":"value5", "keywithoutvalue2":""}`}
+
+		cx1sh := checkmarxOneExecuteScanHelper{nil, options, sys, nil, nil, &project, nil, nil, nil}
+		err := cx1sh.UpdateProjectTags()
+		assert.NoError(t, err, "Error occurred but none expected")
+
+		oldTagsJson := `{
+			"key1": "value1",
+			"key2": "value2", 
+			"keywithoutvalue1": ""
+		}`
+		oldTags := make(map[string]string, 0)
+		_ = json.Unmarshal([]byte(oldTagsJson), &oldTags)
+
+		newTagsJson := `{"key3":"value3", "key2":"value5", "keywithoutvalue2":""}`
+		newTags := make(map[string]string, 0)
+		_ = json.Unmarshal([]byte(newTagsJson), &newTags)
+
+		// merge new tags to the existing ones
+		maps.Copy(oldTags, newTags)
+
+		assert.Equal(t, project.Tags, oldTags) // project's tags must be merged
+	})
+}

From c6ade60ec5788add0b23c076f52c0d8be72aa8b6 Mon Sep 17 00:00:00 2001
From: Thanh Hai Trinh <thanh.hai.trinh@sap.com>
Date: Fri, 31 May 2024 21:46:52 +0200
Subject: [PATCH 5/6] fix(cxOne): improve logs

---
 cmd/checkmarxOneExecuteScan.go | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/cmd/checkmarxOneExecuteScan.go b/cmd/checkmarxOneExecuteScan.go
index 97613ffc84..6b6dc97b87 100644
--- a/cmd/checkmarxOneExecuteScan.go
+++ b/cmd/checkmarxOneExecuteScan.go
@@ -80,9 +80,11 @@ func runStep(config checkmarxOneExecuteScanOptions, influx *checkmarxOneExecuteS
 		return fmt.Errorf("failed to get project: %s", err)
 	}
 
-	cx1sh.Group, err = cx1sh.GetGroup() // used when creating a project and when generating a SARIF report
-	if err != nil {
-		log.Entry().WithError(err).Warnf("failed to get group")
+	if (len(config.GroupName) > 0) {
+		cx1sh.Group, err = cx1sh.GetGroup() // used when creating a project and when generating a SARIF report
+		if err != nil {
+			log.Entry().WithError(err).Warnf("failed to get group")
+		}
 	}
 
 	if cx1sh.Project == nil {
@@ -318,7 +320,7 @@ func (c *checkmarxOneExecuteScanHelper) UpdateProjectTags() error {
 		}
 		// merge new tags to the existing ones
 		maps.Copy(c.Project.Tags, tags)
-	
+
 		return c.sys.UpdateProject(c.Project)
 	}
 
@@ -464,7 +466,7 @@ func (c *checkmarxOneExecuteScanHelper) CreateScanRequest(incremental bool, uplo
 	if len(c.config.ScanTags) > 0 {
 		err := json.Unmarshal([]byte(c.config.ScanTags), &tags)
 		if err != nil {
-			log.Entry().Infof("Failed to parse the scan tags: %v", c.config.ScanTags)
+			log.Entry().WithError(err).Warnf("Failed to parse the scan tags: %v", c.config.ScanTags)
 		}
 	}
 

From 9cde5afab6ecbec6a91186a775fe4f30b99f901a Mon Sep 17 00:00:00 2001
From: Thanh Hai Trinh <thanh.hai.trinh@sap.com>
Date: Fri, 31 May 2024 21:50:03 +0200
Subject: [PATCH 6/6] fix(cxOne): improve logs

---
 cmd/checkmarxOneExecuteScan.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmd/checkmarxOneExecuteScan.go b/cmd/checkmarxOneExecuteScan.go
index 6b6dc97b87..d809304290 100644
--- a/cmd/checkmarxOneExecuteScan.go
+++ b/cmd/checkmarxOneExecuteScan.go
@@ -80,7 +80,7 @@ func runStep(config checkmarxOneExecuteScanOptions, influx *checkmarxOneExecuteS
 		return fmt.Errorf("failed to get project: %s", err)
 	}
 
-	if (len(config.GroupName) > 0) {
+	if len(config.GroupName) > 0 {
 		cx1sh.Group, err = cx1sh.GetGroup() // used when creating a project and when generating a SARIF report
 		if err != nil {
 			log.Entry().WithError(err).Warnf("failed to get group")
@@ -311,7 +311,7 @@ func (c *checkmarxOneExecuteScanHelper) CreateProject() (*checkmarxOne.Project,
 }
 
 func (c *checkmarxOneExecuteScanHelper) UpdateProjectTags() error {
-	if (len(c.config.ProjectTags) > 0 ) {
+	if len(c.config.ProjectTags) > 0 {
 		tags := make(map[string]string, 0)
 		err := json.Unmarshal([]byte(c.config.ProjectTags), &tags)
 		if err != nil {