From adab9c2d813379866ad46b0f9481756fbb2daf8b Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:18:20 +0530 Subject: [PATCH 01/14] Existing project Assignment to Existing Application --- internal/commands/scan_test.go | 43 +++++++++++++++++++ internal/constants/errors/errors.go | 7 +-- internal/services/applications.go | 50 ++++++++++++++++++++++ internal/services/projects.go | 7 ++- internal/wrappers/application-http.go | 36 ++++++++++++++++ internal/wrappers/application.go | 34 +++++++++------ internal/wrappers/mock/application-mock.go | 13 ++++++ test/integration/scan_test.go | 28 ++++++++++++ 8 files changed, 199 insertions(+), 19 deletions(-) diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index ae0c42933..b94597030 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -3623,3 +3623,46 @@ func Test_CreateScanWithIgnorePolicyFlag(t *testing.T) { "scan", "create", "--project-name", "MOCK", "-s", "data/sources.zip", "--branch", "dummy_branch", "--ignore-policy", ) } + +func Test_CreateScanWithExistingProjectAndAssign_Application(t *testing.T) { + file := createOutputFile(t, outputFileName) + defer deleteOutputFile(file) + defer logger.SetOutput(os.Stdout) + + baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", ".", "--branch", "main", "--application-name", mock.ExistingApplication, "--debug"} + execCmdNilAssertion( + t, + baseArgs..., + ) + stdoutString, err := util.ReadFileAsString(file.Name()) + if err != nil { + t.Fatalf("Failed to read log file: %v", err) + } + assert.Equal(t, strings.Contains(stdoutString, "Successfully updated the application"), true, "Expected output: %s", "Successfully updated the application") +} + +func Test_CreateScanWithExistingProjectAndAssign_FailedNoApplication_NameProvided(t *testing.T) { + file := createOutputFile(t, outputFileName) + defer deleteOutputFile(file) + defer logger.SetOutput(os.Stdout) + + baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", ".", "--branch", "main", "--debug"} + execCmdNilAssertion( + t, + baseArgs..., + ) + stdoutString, err := util.ReadFileAsString(file.Name()) + if err != nil { + t.Fatalf("Failed to read log file: %v", err) + } + assert.Equal(t, strings.Contains(stdoutString, "No Application Name provided. Skipping application update"), true, "Expected output: %s", "No Application Name provided. Skipping application update") +} + +func Test_CreateScanWithExistingProjectAndAssign_FailedApplication_DoesNot_Exist(t *testing.T) { + baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", ".", "--branch", "main", "--debug", "--application-name", "NoPermissionApp"} + err := execCmdNotNilAssertion( + t, + baseArgs..., + ) + assert.Error(t, err, "Failed to get Application NoPermissionApp: provided application does not exist or user has no permission to the application", err.Error()) +} diff --git a/internal/constants/errors/errors.go b/internal/constants/errors/errors.go index e565e1cdc..6162fc6c7 100644 --- a/internal/constants/errors/errors.go +++ b/internal/constants/errors/errors.go @@ -29,9 +29,10 @@ const ( FileExtensionIsRequired = "file must have an extension" // Realtime - RealtimeEngineErrFormat = "realtime engine error: %s" - RealtimeEngineNotAvailable = "Realtime engine is not available for this tenant" - RealtimeEngineFilePathRequired = "file path is required for realtime scan" + RealtimeEngineErrFormat = "realtime engine error: %s" + RealtimeEngineNotAvailable = "Realtime engine is not available for this tenant" + RealtimeEngineFilePathRequired = "file path is required for realtime scan" + NoPermissionToUpdateApplication = "you do not have permission to update the application" ) type RealtimeEngineError struct { diff --git a/internal/services/applications.go b/internal/services/applications.go index 744e89670..5085b2917 100644 --- a/internal/services/applications.go +++ b/internal/services/applications.go @@ -2,11 +2,16 @@ package services import ( errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" + "github.com/checkmarx/ast-cli/internal/logger" "github.com/checkmarx/ast-cli/internal/wrappers" "github.com/checkmarx/ast-cli/internal/wrappers/utils" "github.com/pkg/errors" ) +const ( + ApplicationRuleType = "project.name.in" +) + func createApplicationIds(applicationID, existingApplicationIds []string) []string { for _, id := range applicationID { if !utils.Contains(existingApplicationIds, id) { @@ -58,3 +63,48 @@ func verifyApplicationNameExactMatch(applicationName string, resp *wrappers.Appl } return application } + +func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper, projectName string) error { + if applicationName == "" { + logger.PrintfIfVerbose("No application name provided. Skipping application update") + return nil + } + applicationResp, err := GetApplication(applicationName, applicationsWrapper) + if err != nil { + return errors.Wrapf(err, "Failed to get Application:%s", applicationName) + } + if applicationResp == nil { + return errors.Errorf("Application %s not found", applicationName) + } + var applicationModel wrappers.ApplicationConfiguration + var newApplicationRule wrappers.Rule + var applicationID string + + applicationModel.Name = applicationResp.Name + applicationModel.Description = applicationResp.Description + applicationModel.Criticality = applicationResp.Criticality + applicationModel.Type = applicationResp.Type + applicationModel.Tags = applicationResp.Tags + newApplicationRule.Type = ApplicationRuleType + newApplicationRule.Value = projectName + applicationModel.Rules = append(applicationResp.Rules, newApplicationRule) + applicationID = applicationResp.ID + + err = updateApplication(applicationModel, applicationsWrapper, applicationID) + if err != nil { + return err + } + return nil +} + +func updateApplication(applicationModel wrappers.ApplicationConfiguration, applicationWrapper wrappers.ApplicationsWrapper, applicationID string) error { + errorModel, err := applicationWrapper.Update(applicationID, applicationModel) + if errorModel != nil { + err = errors.Errorf(ErrorCodeFormat, "failed to update application", errorModel.Code, errorModel.Message) + } + if errorModel == nil && err == nil { + logger.PrintIfVerbose("Successfully updated the application") + return nil + } + return err +} diff --git a/internal/services/projects.go b/internal/services/projects.go index c39a25863..00b7ad1b8 100644 --- a/internal/services/projects.go +++ b/internal/services/projects.go @@ -40,9 +40,14 @@ func FindProject( } branchName := strings.TrimSpace(viper.GetString(commonParams.BranchKey)) isBranchPrimary, _ = cmd.Flags().GetBool(commonParams.BranchPrimaryFlag) + applicationName, _ := cmd.Flags().GetString(commonParams.ApplicationName) for i := 0; i < len(resp.Projects); i++ { project := resp.Projects[i] if project.Name == projectName { + err = findApplicationAndUpdate(applicationName, applicationWrapper, projectName) + if err != nil { + return "", err + } projectTags, _ := cmd.Flags().GetString(commonParams.ProjectTagList) projectPrivatePackage, _ := cmd.Flags().GetString(commonParams.ProjecPrivatePackageFlag) return updateProject(&project, projectsWrapper, projectTags, projectPrivatePackage, isBranchPrimary, branchName) @@ -51,8 +56,6 @@ func FindProject( projectGroups, _ := cmd.Flags().GetString(commonParams.ProjectGroupList) projectPrivatePackage, _ := cmd.Flags().GetString(commonParams.ProjecPrivatePackageFlag) - - applicationName, _ := cmd.Flags().GetString(commonParams.ApplicationName) applicationID, appErr := getApplicationID(applicationName, applicationWrapper) if appErr != nil { return "", appErr diff --git a/internal/wrappers/application-http.go b/internal/wrappers/application-http.go index 157cb9258..ad13c58f7 100644 --- a/internal/wrappers/application-http.go +++ b/internal/wrappers/application-http.go @@ -1,7 +1,9 @@ package wrappers import ( + "bytes" "encoding/json" + "fmt" "net/http" errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" @@ -20,6 +22,40 @@ func NewApplicationsHTTPWrapper(path string) ApplicationsWrapper { } } +func (a *ApplicationsHTTPWrapper) Update(applicationID string, applicationBody ApplicationConfiguration) (*ErrorModel, error) { + clientTimeout := viper.GetUint(commonParams.ClientTimeoutKey) + jsonBytes, err := json.Marshal(applicationBody) + updatePath := fmt.Sprintf("%s/%s", a.path, applicationID) + if err != nil { + return nil, err + } + resp, err := SendHTTPRequest(http.MethodPut, updatePath, bytes.NewBuffer(jsonBytes), true, clientTimeout) + if err != nil { + return nil, err + } + decoder := json.NewDecoder(resp.Body) + switch resp.StatusCode { + case http.StatusBadRequest: + errorModel := ErrorModel{} + err = decoder.Decode(&errorModel) + if err != nil { + return nil, errors.Errorf("failed to parse application response: %s ", err) + } + return &errorModel, nil + + case http.StatusNoContent: + return nil, nil + + case http.StatusForbidden: + return nil, errors.New(errorConstants.NoPermissionToUpdateApplication) + + case http.StatusUnauthorized: + return nil, errors.New(errorConstants.StatusUnauthorized) + default: + return nil, errors.Errorf("response status code %d", resp.StatusCode) + } +} + func (a *ApplicationsHTTPWrapper) Get(params map[string]string) (*ApplicationsResponseModel, error) { if _, ok := params[limit]; !ok { params[limit] = limitValue diff --git a/internal/wrappers/application.go b/internal/wrappers/application.go index 6bb30d62a..52d8df0ae 100644 --- a/internal/wrappers/application.go +++ b/internal/wrappers/application.go @@ -9,15 +9,25 @@ type ApplicationsResponseModel struct { } type Application struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Criticality int `json:"criticality"` - Rules []Rule `json:"rules"` - ProjectIds []string `json:"projectIds"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - Tags Tags `json:"tags"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Criticality int `json:"criticality"` + Rules []Rule `json:"rules"` + ProjectIds []string `json:"projectIds"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Tags map[string]string `json:"tags"` + Type string `json:"type"` +} + +type ApplicationConfiguration struct { + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + Criticality int `json:"criticality"` + Rules []Rule `json:"rules"` + Tags map[string]string `json:"tags"` } type Rule struct { @@ -26,11 +36,7 @@ type Rule struct { Value string `json:"value"` } -type Tags struct { - Test string `json:"test"` - Priority string `json:"priority"` -} - type ApplicationsWrapper interface { Get(params map[string]string) (*ApplicationsResponseModel, error) + Update(applicationID string, applicationBody ApplicationConfiguration) (*ErrorModel, error) } diff --git a/internal/wrappers/mock/application-mock.go b/internal/wrappers/mock/application-mock.go index fdb9e01a9..937399cf1 100644 --- a/internal/wrappers/mock/application-mock.go +++ b/internal/wrappers/mock/application-mock.go @@ -1,6 +1,7 @@ package mock import ( + "fmt" "time" errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" @@ -52,3 +53,15 @@ func (a ApplicationsMockWrapper) Get(params map[string]string) (*wrappers.Applic return response, nil } + +func (a ApplicationsMockWrapper) Update(applicationID string, applicationBody wrappers.ApplicationConfiguration) (*wrappers.ErrorModel, error) { + fmt.Println("called Update project") + if applicationID == FakeForbidden403 { + return nil, errors.Errorf(errorConstants.NoPermissionToUpdateApplication) + } + if applicationID == FakeUnauthorized401 { + return nil, errors.Errorf(errorConstants.StatusUnauthorized) + } + + return nil, nil +} diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index ddcba2e17..ffec59b8c 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -2706,3 +2706,31 @@ func TestCreateScanFilterGitIgnoreFile_GitIgnoreExist(t *testing.T) { err, _ := executeCommand(t, args...) assert.NilError(t, err, "Scan creation with gitignore filter should pass without error") } + +func TestCreateScanWithExistingProjectAnd_AssignApplication(t *testing.T) { + _, projectName := createNewProject(t, nil, nil, GenerateRandomProjectNameForScan()) + + args := []string{ + "scan", "create", + flag(params.ProjectName), projectName, + flag(params.BranchFlag), "dummy_branch", + flag(params.SourcesFlag), "data/sources-gitignore.zip", + flag(params.ApplicationName), "cli-application", + } + err, _ := executeCommand(t, args...) + assert.NilError(t, err, "Project should be assigned to application") +} + +func TestCreateScanWithExistingProjectAnd_ApplicationNotFoundFailed(t *testing.T) { + _, projectName := createNewProject(t, nil, nil, GenerateRandomProjectNameForScan()) + + args := []string{ + "scan", "create", + flag(params.ProjectName), projectName, + flag(params.BranchFlag), "dummy_branch", + flag(params.SourcesFlag), "data/sources-gitignore.zip", + flag(params.ApplicationName), "mock", + } + err, _ := executeCommand(t, args...) + assert.ErrorContains(t, err, "Application mock not found") +} From 4e82726644bb5d82b61ad56bb78b44e4fac668b5 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:59:38 +0530 Subject: [PATCH 02/14] lint issues fixed --- internal/services/applications.go | 7 ++++--- internal/wrappers/application-http.go | 6 +++++- internal/wrappers/application.go | 2 +- internal/wrappers/mock/application-mock.go | 4 +--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/services/applications.go b/internal/services/applications.go index 5085b2917..4c305ed6d 100644 --- a/internal/services/applications.go +++ b/internal/services/applications.go @@ -87,17 +87,18 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe applicationModel.Tags = applicationResp.Tags newApplicationRule.Type = ApplicationRuleType newApplicationRule.Value = projectName - applicationModel.Rules = append(applicationResp.Rules, newApplicationRule) + combinedRules := append(applicationResp.Rules, newApplicationRule) + applicationModel.Rules = combinedRules applicationID = applicationResp.ID - err = updateApplication(applicationModel, applicationsWrapper, applicationID) + err = updateApplication(&applicationModel, applicationsWrapper, applicationID) if err != nil { return err } return nil } -func updateApplication(applicationModel wrappers.ApplicationConfiguration, applicationWrapper wrappers.ApplicationsWrapper, applicationID string) error { +func updateApplication(applicationModel *wrappers.ApplicationConfiguration, applicationWrapper wrappers.ApplicationsWrapper, applicationID string) error { errorModel, err := applicationWrapper.Update(applicationID, applicationModel) if errorModel != nil { err = errors.Errorf(ErrorCodeFormat, "failed to update application", errorModel.Code, errorModel.Message) diff --git a/internal/wrappers/application-http.go b/internal/wrappers/application-http.go index ad13c58f7..0cebab4c4 100644 --- a/internal/wrappers/application-http.go +++ b/internal/wrappers/application-http.go @@ -22,7 +22,7 @@ func NewApplicationsHTTPWrapper(path string) ApplicationsWrapper { } } -func (a *ApplicationsHTTPWrapper) Update(applicationID string, applicationBody ApplicationConfiguration) (*ErrorModel, error) { +func (a *ApplicationsHTTPWrapper) Update(applicationID string, applicationBody *ApplicationConfiguration) (*ErrorModel, error) { clientTimeout := viper.GetUint(commonParams.ClientTimeoutKey) jsonBytes, err := json.Marshal(applicationBody) updatePath := fmt.Sprintf("%s/%s", a.path, applicationID) @@ -34,6 +34,10 @@ func (a *ApplicationsHTTPWrapper) Update(applicationID string, applicationBody A return nil, err } decoder := json.NewDecoder(resp.Body) + defer func() { + _ = resp.Body.Close() + }() + switch resp.StatusCode { case http.StatusBadRequest: errorModel := ErrorModel{} diff --git a/internal/wrappers/application.go b/internal/wrappers/application.go index 52d8df0ae..5def20cc9 100644 --- a/internal/wrappers/application.go +++ b/internal/wrappers/application.go @@ -38,5 +38,5 @@ type Rule struct { type ApplicationsWrapper interface { Get(params map[string]string) (*ApplicationsResponseModel, error) - Update(applicationID string, applicationBody ApplicationConfiguration) (*ErrorModel, error) + Update(applicationID string, applicationBody *ApplicationConfiguration) (*ErrorModel, error) } diff --git a/internal/wrappers/mock/application-mock.go b/internal/wrappers/mock/application-mock.go index 937399cf1..a565c5762 100644 --- a/internal/wrappers/mock/application-mock.go +++ b/internal/wrappers/mock/application-mock.go @@ -50,11 +50,10 @@ func (a ApplicationsMockWrapper) Get(params map[string]string) (*wrappers.Applic response.TotalCount = 0 response.Applications = []wrappers.Application{} } - return response, nil } -func (a ApplicationsMockWrapper) Update(applicationID string, applicationBody wrappers.ApplicationConfiguration) (*wrappers.ErrorModel, error) { +func (a ApplicationsMockWrapper) Update(applicationID string, applicationBody *wrappers.ApplicationConfiguration) (*wrappers.ErrorModel, error) { fmt.Println("called Update project") if applicationID == FakeForbidden403 { return nil, errors.Errorf(errorConstants.NoPermissionToUpdateApplication) @@ -62,6 +61,5 @@ func (a ApplicationsMockWrapper) Update(applicationID string, applicationBody wr if applicationID == FakeUnauthorized401 { return nil, errors.Errorf(errorConstants.StatusUnauthorized) } - return nil, nil } From 947de4598588091b27052030f3bed2d85cc3b7bc Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:29:23 +0530 Subject: [PATCH 03/14] append lint fixed --- internal/services/applications.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/applications.go b/internal/services/applications.go index 4c305ed6d..681b547af 100644 --- a/internal/services/applications.go +++ b/internal/services/applications.go @@ -87,8 +87,8 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe applicationModel.Tags = applicationResp.Tags newApplicationRule.Type = ApplicationRuleType newApplicationRule.Value = projectName - combinedRules := append(applicationResp.Rules, newApplicationRule) - applicationModel.Rules = combinedRules + applicationModel.Rules = append(applicationModel.Rules, applicationResp.Rules...) + applicationModel.Rules = append(applicationModel.Rules, newApplicationRule) applicationID = applicationResp.ID err = updateApplication(&applicationModel, applicationsWrapper, applicationID) From 82e2e320d9af7cecadb10ec59410abd0525a42da Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 17:33:39 +0530 Subject: [PATCH 04/14] fixed assertion --- internal/commands/scan_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index b94597030..e44669dcc 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -3643,7 +3643,7 @@ func Test_CreateScanWithExistingProjectAndAssign_Application(t *testing.T) { func Test_CreateScanWithExistingProjectAndAssign_FailedNoApplication_NameProvided(t *testing.T) { file := createOutputFile(t, outputFileName) - defer deleteOutputFile(file) + //defer deleteOutputFile(file) defer logger.SetOutput(os.Stdout) baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", ".", "--branch", "main", "--debug"} @@ -3655,7 +3655,7 @@ func Test_CreateScanWithExistingProjectAndAssign_FailedNoApplication_NameProvide if err != nil { t.Fatalf("Failed to read log file: %v", err) } - assert.Equal(t, strings.Contains(stdoutString, "No Application Name provided. Skipping application update"), true, "Expected output: %s", "No Application Name provided. Skipping application update") + assert.Equal(t, strings.Contains(stdoutString, "No application name provided. Skipping application update"), true, "Expected output: %s", "No application name provided. Skipping application update") } func Test_CreateScanWithExistingProjectAndAssign_FailedApplication_DoesNot_Exist(t *testing.T) { From 0f45a2e0b9561a2d7a204fdb91e06231516b994b Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 17:40:18 +0530 Subject: [PATCH 05/14] fixed assertion --- internal/commands/scan_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index e44669dcc..77a8c669b 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -3643,7 +3643,7 @@ func Test_CreateScanWithExistingProjectAndAssign_Application(t *testing.T) { func Test_CreateScanWithExistingProjectAndAssign_FailedNoApplication_NameProvided(t *testing.T) { file := createOutputFile(t, outputFileName) - //defer deleteOutputFile(file) + defer deleteOutputFile(file) defer logger.SetOutput(os.Stdout) baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", ".", "--branch", "main", "--debug"} From 0b8bc6f97c91809c3622279e567cb663bad17b08 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:01:09 +0530 Subject: [PATCH 06/14] fixed error msgs --- internal/commands/scan_test.go | 25 +++++++++++-------------- internal/services/applications.go | 4 ++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index 77a8c669b..c41a38baa 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -193,8 +193,9 @@ func TestScanCreate_ApplicationNameIsNotExactMatch_FailedToCreateScan(t *testing assert.Assert(t, err.Error() == errorConstants.ApplicationDoesntExistOrNoPermission) } -func TestScanCreate_ExistingProjectAndApplicationWithNoPermission_ShouldCreateScan(t *testing.T) { - execCmdNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--application-name", mock.ApplicationDoesntExist, "-s", dummyRepo, "-b", "dummy_branch") +func TestScanCreate_ExistingProjectAndApplicationWithNoPermission_ShouldFailScan(t *testing.T) { + err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--application-name", mock.NoPermissionApp, "-s", dummyRepo, "-b", "dummy_branch") + assert.Assert(t, strings.Contains(err.Error(), errorConstants.FailedToGetApplication), err.Error()) } func TestScanCreate_ExistingApplicationWithNoPermission_FailedToCreateScan(t *testing.T) { @@ -712,18 +713,13 @@ func TestCreateScan_WhenProjectExists_ShouldIgnoreGroups(t *testing.T) { assert.Equal(t, strings.Contains(stdoutString, noUpdatesForExistingProject), true, "Expected output: %s", noUpdatesForExistingProject) } -func TestCreateScan_WhenProjectExists_ShouldIgnoreApplication(t *testing.T) { - file := createOutputFile(t, outputFileName) - defer deleteOutputFile(file) - defer logger.SetOutput(os.Stdout) +// Now as we give the ability to assign existing projects to applications , there is validation if application exists + +func TestCreateScan_WhenProjectExists_GetApplication_Fails500Err_Failed(t *testing.T) { baseArgs := []string{scanCommand, "create", "--project-name", "MOCK", "-s", dummyRepo, "-b", "dummy_branch", - "--debug", "--application-name", "anyApplication"} - execCmdNilAssertion(t, baseArgs...) - stdoutString, err := util.ReadFileAsString(file.Name()) - if err != nil { - t.Fatalf("Failed to read log file: %v", err) - } - assert.Equal(t, strings.Contains(stdoutString, noUpdatesForExistingProject), true, "Expected output: %s", noUpdatesForExistingProject) + "--debug", "--application-name", mock.FakeInternalServerError500} + err := execCmdNotNilAssertion(t, baseArgs...) + assert.ErrorContains(t, err, errorConstants.FailedToGetApplication, err.Error()) } func TestScanCreateLastSastScanTimeWithInvalidValue(t *testing.T) { baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", dummyRepo, "-b", "dummy_branch", "--sca-exploitable-path", "true", "--sca-last-sast-scan-time", "notaniteger"} @@ -3664,5 +3660,6 @@ func Test_CreateScanWithExistingProjectAndAssign_FailedApplication_DoesNot_Exist t, baseArgs..., ) - assert.Error(t, err, "Failed to get Application NoPermissionApp: provided application does not exist or user has no permission to the application", err.Error()) + assert.ErrorContains(t, err, errorConstants.FailedToGetApplication, err.Error()) + } diff --git a/internal/services/applications.go b/internal/services/applications.go index 681b547af..5114dec7d 100644 --- a/internal/services/applications.go +++ b/internal/services/applications.go @@ -71,10 +71,10 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe } applicationResp, err := GetApplication(applicationName, applicationsWrapper) if err != nil { - return errors.Wrapf(err, "Failed to get Application:%s", applicationName) + return errors.Wrapf(err, "%s:%s", errorConstants.FailedToGetApplication, applicationName) } if applicationResp == nil { - return errors.Errorf("Application %s not found", applicationName) + return errors.Errorf("Application not found: %s", applicationName) } var applicationModel wrappers.ApplicationConfiguration var newApplicationRule wrappers.Rule From 6318b5948ba5915ec9f15aa54010275bffc78177 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:09:09 +0530 Subject: [PATCH 07/14] lint new line issue fixed --- internal/commands/scan_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index c41a38baa..75c1feee7 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -3661,5 +3661,4 @@ func Test_CreateScanWithExistingProjectAndAssign_FailedApplication_DoesNot_Exist baseArgs..., ) assert.ErrorContains(t, err, errorConstants.FailedToGetApplication, err.Error()) - } From 638cb5f4394599e62c7daf8a8e8a0ab9290d3f7d Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:26:29 +0530 Subject: [PATCH 08/14] Fixed other intergration tests failing dur to application validation --- test/integration/scan_test.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index ffec59b8c..89164bd3b 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -1667,10 +1667,8 @@ func TestScanCreate_WhenProjectExists_ShouldNotUpdateGroups(t *testing.T) { } -func TestScanCreate_WhenProjectExists_ShouldNotUpdateApplication(t *testing.T) { - projectID, projectName := getRootProject(t) - project := showProject(t, projectID) - applicationsBeforeScanCreate := project.ApplicationIds +func TestScanCreate_WhenProjectExists_ShouldThrow_Error_ApplicationNotFound(t *testing.T) { + _, projectName := getRootProject(t) args := []string{ scanCommand, "create", @@ -1684,15 +1682,7 @@ func TestScanCreate_WhenProjectExists_ShouldNotUpdateApplication(t *testing.T) { } err, _ := executeCommand(t, args...) - if err != nil { - assertError(t, err, "running a scan should pass") - } - - project = showProject(t, projectID) - applicationsAfterScanCreate := project.ApplicationIds - if !reflect.DeepEqual(applicationsBeforeScanCreate, applicationsAfterScanCreate) { - t.Errorf("When project exists, applications before and after scan creation should be equal. Got %v, want %v", applicationsAfterScanCreate, applicationsBeforeScanCreate) - } + assert.Error(t, err, "Application not found: wrong_application") } func TestScanCreateExploitablePath(t *testing.T) { From aa6ed6f6198fe502fa7a605113b51bbde36fbf6b Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 19 Sep 2025 23:23:30 +0530 Subject: [PATCH 09/14] integration fix --- test/integration/scan_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index 89164bd3b..68ab46bac 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -2722,5 +2722,5 @@ func TestCreateScanWithExistingProjectAnd_ApplicationNotFoundFailed(t *testing.T flag(params.ApplicationName), "mock", } err, _ := executeCommand(t, args...) - assert.ErrorContains(t, err, "Application mock not found") + assert.ErrorContains(t, err, "Application not found: mock") } From c74376f84800d1a63be1a3539daefb430a4aa2df Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Mon, 22 Sep 2025 15:13:32 +0530 Subject: [PATCH 10/14] Fixed review constant mapping --- internal/constants/errors/errors.go | 10 ++++++---- internal/services/applications.go | 4 ++-- test/integration/scan_test.go | 14 -------------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/internal/constants/errors/errors.go b/internal/constants/errors/errors.go index 6162fc6c7..62c207a7a 100644 --- a/internal/constants/errors/errors.go +++ b/internal/constants/errors/errors.go @@ -24,15 +24,17 @@ const ( NoASCALicense = "User doesn't have \"AI Protection\" or \"Checkmarx One Assist\" license" FailedUploadFileMsgWithDomain = "Unable to upload the file to the pre-signed URL. Try adding the domain: %s to your allow list." FailedUploadFileMsgWithURL = "Unable to upload the file to the pre-signed URL. Try adding the URL: %s to your allow list." + NoPermissionToUpdateApplication = "you do not have permission to update the application" + FailedToUpdateApplication = "failed to update application" + ApplicationNotFound = "Application not found" // asca Engine FileExtensionIsRequired = "file must have an extension" // Realtime - RealtimeEngineErrFormat = "realtime engine error: %s" - RealtimeEngineNotAvailable = "Realtime engine is not available for this tenant" - RealtimeEngineFilePathRequired = "file path is required for realtime scan" - NoPermissionToUpdateApplication = "you do not have permission to update the application" + RealtimeEngineErrFormat = "realtime engine error: %s" + RealtimeEngineNotAvailable = "Realtime engine is not available for this tenant" + RealtimeEngineFilePathRequired = "file path is required for realtime scan" ) type RealtimeEngineError struct { diff --git a/internal/services/applications.go b/internal/services/applications.go index 5114dec7d..3004249c5 100644 --- a/internal/services/applications.go +++ b/internal/services/applications.go @@ -74,7 +74,7 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe return errors.Wrapf(err, "%s:%s", errorConstants.FailedToGetApplication, applicationName) } if applicationResp == nil { - return errors.Errorf("Application not found: %s", applicationName) + return errors.Errorf("%s: %s", errorConstants.ApplicationNotFound, applicationName) } var applicationModel wrappers.ApplicationConfiguration var newApplicationRule wrappers.Rule @@ -101,7 +101,7 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe func updateApplication(applicationModel *wrappers.ApplicationConfiguration, applicationWrapper wrappers.ApplicationsWrapper, applicationID string) error { errorModel, err := applicationWrapper.Update(applicationID, applicationModel) if errorModel != nil { - err = errors.Errorf(ErrorCodeFormat, "failed to update application", errorModel.Code, errorModel.Message) + err = errors.Errorf(ErrorCodeFormat, errorConstants.FailedToUpdateApplication, errorModel.Code, errorModel.Message) } if errorModel == nil && err == nil { logger.PrintIfVerbose("Successfully updated the application") diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index 68ab46bac..cfa5fbf42 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -2710,17 +2710,3 @@ func TestCreateScanWithExistingProjectAnd_AssignApplication(t *testing.T) { err, _ := executeCommand(t, args...) assert.NilError(t, err, "Project should be assigned to application") } - -func TestCreateScanWithExistingProjectAnd_ApplicationNotFoundFailed(t *testing.T) { - _, projectName := createNewProject(t, nil, nil, GenerateRandomProjectNameForScan()) - - args := []string{ - "scan", "create", - flag(params.ProjectName), projectName, - flag(params.BranchFlag), "dummy_branch", - flag(params.SourcesFlag), "data/sources-gitignore.zip", - flag(params.ApplicationName), "mock", - } - err, _ := executeCommand(t, args...) - assert.ErrorContains(t, err, "Application not found: mock") -} From 32d961af187d03a155980ff943426d0fd4775355 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:35:41 +0530 Subject: [PATCH 11/14] Handled direct association of project to application --- internal/commands/scan_test.go | 18 ++++++++++ internal/services/applications.go | 24 +++++++++++++- internal/services/projects.go | 2 +- internal/wrappers/application-http.go | 38 ++++++++++++++++++++++ internal/wrappers/application.go | 5 +++ internal/wrappers/feature-flags.go | 1 + internal/wrappers/mock/application-mock.go | 13 +++++++- 7 files changed, 98 insertions(+), 3 deletions(-) diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index 75c1feee7..be91f8ed7 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -3662,3 +3662,21 @@ func Test_CreateScanWithExistingProjectAndAssign_FailedApplication_DoesNot_Exist ) assert.ErrorContains(t, err, errorConstants.FailedToGetApplication, err.Error()) } + +func Test_CreateScanWithExistingProjectAssign_to_Application_FF_DirectAssociationEnabledShouldPass(t *testing.T) { + file := createOutputFile(t, outputFileName) + defer deleteOutputFile(file) + defer logger.SetOutput(os.Stdout) + + mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.DirectAssociationEnabled, Status: true} + baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", ".", "--branch", "main", "--debug", "--application-name", mock.ExistingApplication} + execCmdNilAssertion( + t, + baseArgs..., + ) + stdoutString, err := util.ReadFileAsString(file.Name()) + if err != nil { + t.Fatalf("Failed to read log file: %v", err) + } + assert.Equal(t, strings.Contains(stdoutString, "Successfully updated the application"), true, "Expected output: %s", "Successfully updated the application") +} diff --git a/internal/services/applications.go b/internal/services/applications.go index 3004249c5..7ffc52892 100644 --- a/internal/services/applications.go +++ b/internal/services/applications.go @@ -64,7 +64,7 @@ func verifyApplicationNameExactMatch(applicationName string, resp *wrappers.Appl return application } -func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper, projectName string) error { +func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper, projectName string, projectID string, featureFlagsWrapper wrappers.FeatureFlagsWrapper) error { if applicationName == "" { logger.PrintfIfVerbose("No application name provided. Skipping application update") return nil @@ -76,6 +76,15 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe if applicationResp == nil { return errors.Errorf("%s: %s", errorConstants.ApplicationNotFound, applicationName) } + + directAssociationEnabled, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.DirectAssociationEnabled) + if directAssociationEnabled.Status { + err = associateProjectToApplication(applicationResp.ID, projectID, applicationResp.ProjectIds, applicationsWrapper) + if err != nil { + return err + } + return nil + } var applicationModel wrappers.ApplicationConfiguration var newApplicationRule wrappers.Rule var applicationID string @@ -100,6 +109,19 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe func updateApplication(applicationModel *wrappers.ApplicationConfiguration, applicationWrapper wrappers.ApplicationsWrapper, applicationID string) error { errorModel, err := applicationWrapper.Update(applicationID, applicationModel) + return handleApplicationUpdateResponse(errorModel, err) +} + +func associateProjectToApplication(applicationID string, projectID string, associatedProjectIds []string, applicationsWrapper wrappers.ApplicationsWrapper) error { + associatedProjectIds = append(associatedProjectIds, projectID) + associateProjectsModel := &wrappers.AssociateProjectModel{ + ProjectIds: associatedProjectIds, + } + errorModel, err := applicationsWrapper.CreateProjectAssociation(applicationID, associateProjectsModel) + return handleApplicationUpdateResponse(errorModel, err) +} + +func handleApplicationUpdateResponse(errorModel *wrappers.ErrorModel, err error) error { if errorModel != nil { err = errors.Errorf(ErrorCodeFormat, errorConstants.FailedToUpdateApplication, errorModel.Code, errorModel.Message) } diff --git a/internal/services/projects.go b/internal/services/projects.go index 00b7ad1b8..3d34cc110 100644 --- a/internal/services/projects.go +++ b/internal/services/projects.go @@ -44,7 +44,7 @@ func FindProject( for i := 0; i < len(resp.Projects); i++ { project := resp.Projects[i] if project.Name == projectName { - err = findApplicationAndUpdate(applicationName, applicationWrapper, projectName) + err = findApplicationAndUpdate(applicationName, applicationWrapper, projectName, project.ID, featureFlagsWrapper) if err != nil { return "", err } diff --git a/internal/wrappers/application-http.go b/internal/wrappers/application-http.go index 0cebab4c4..4e39d63ce 100644 --- a/internal/wrappers/application-http.go +++ b/internal/wrappers/application-http.go @@ -22,6 +22,44 @@ func NewApplicationsHTTPWrapper(path string) ApplicationsWrapper { } } +func (a *ApplicationsHTTPWrapper) CreateProjectAssociation(applicationID string, projectAssociationModel *AssociateProjectModel) (*ErrorModel, error) { + clientTimeout := viper.GetUint(commonParams.ClientTimeoutKey) + jsonBytes, err := json.Marshal(*projectAssociationModel) + if err != nil { + return nil, err + } + associationPath := fmt.Sprintf("%s/%s/%s", a.path, applicationID, "projects") + resp, err := SendHTTPRequest(http.MethodPost, associationPath, bytes.NewBuffer(jsonBytes), true, clientTimeout) + if err != nil { + return nil, err + } + decoder := json.NewDecoder(resp.Body) + defer func() { + _ = resp.Body.Close() + }() + switch resp.StatusCode { + case http.StatusBadRequest: + errorModel := ErrorModel{} + err = decoder.Decode(&errorModel) + if err != nil { + return nil, errors.Errorf("failed to parse application response for project updation: %s ", err) + } + return &errorModel, nil + + case http.StatusCreated: + return nil, nil + + case http.StatusForbidden: + return nil, errors.New(errorConstants.NoPermissionToUpdateApplication) + + case http.StatusUnauthorized: + return nil, errors.New(errorConstants.StatusUnauthorized) + default: + return nil, errors.Errorf("response status code %d", resp.StatusCode) + } + +} + func (a *ApplicationsHTTPWrapper) Update(applicationID string, applicationBody *ApplicationConfiguration) (*ErrorModel, error) { clientTimeout := viper.GetUint(commonParams.ClientTimeoutKey) jsonBytes, err := json.Marshal(applicationBody) diff --git a/internal/wrappers/application.go b/internal/wrappers/application.go index 5def20cc9..81f4ba8bc 100644 --- a/internal/wrappers/application.go +++ b/internal/wrappers/application.go @@ -30,6 +30,10 @@ type ApplicationConfiguration struct { Tags map[string]string `json:"tags"` } +type AssociateProjectModel struct { + ProjectIds []string `json:"projectIds"` +} + type Rule struct { ID string `json:"id"` Type string `json:"type"` @@ -39,4 +43,5 @@ type Rule struct { type ApplicationsWrapper interface { Get(params map[string]string) (*ApplicationsResponseModel, error) Update(applicationID string, applicationBody *ApplicationConfiguration) (*ErrorModel, error) + CreateProjectAssociation(applicationID string, requestModel *AssociateProjectModel) (*ErrorModel, error) } diff --git a/internal/wrappers/feature-flags.go b/internal/wrappers/feature-flags.go index a243db0f9..e5b229dc3 100644 --- a/internal/wrappers/feature-flags.go +++ b/internal/wrappers/feature-flags.go @@ -16,6 +16,7 @@ const SCSEngineCLIEnabled = "NEW_2MS_SCORECARD_RESULTS_CLI_ENABLED" const RiskManagementEnabled = "RISK_MANAGEMENT_IDES_PROJECT_RESULTS_SCORES_API_ENABLED" const OssRealtimeEnabled = "OSS_REALTIME_ENABLED" const ScsLicensingV2Enabled = "SSCS_NEW_LICENSING_ENABLED" +const DirectAssociationEnabled = "DIRECT_APP_ASSOCIATION_ENABLED" const maxRetries = 3 var DefaultFFLoad bool = false diff --git a/internal/wrappers/mock/application-mock.go b/internal/wrappers/mock/application-mock.go index a565c5762..5d286fd4b 100644 --- a/internal/wrappers/mock/application-mock.go +++ b/internal/wrappers/mock/application-mock.go @@ -54,7 +54,18 @@ func (a ApplicationsMockWrapper) Get(params map[string]string) (*wrappers.Applic } func (a ApplicationsMockWrapper) Update(applicationID string, applicationBody *wrappers.ApplicationConfiguration) (*wrappers.ErrorModel, error) { - fmt.Println("called Update project") + fmt.Println("called Update application") + if applicationID == FakeForbidden403 { + return nil, errors.Errorf(errorConstants.NoPermissionToUpdateApplication) + } + if applicationID == FakeUnauthorized401 { + return nil, errors.Errorf(errorConstants.StatusUnauthorized) + } + return nil, nil +} + +func (a ApplicationsMockWrapper) CreateProjectAssociation(applicationID string, requestModel *wrappers.AssociateProjectModel) (*wrappers.ErrorModel, error) { + fmt.Println("called Create project association to application") if applicationID == FakeForbidden403 { return nil, errors.Errorf(errorConstants.NoPermissionToUpdateApplication) } From 41d4e4c603353f17a2b7dc9db518540abd0d7825 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Wed, 24 Sep 2025 17:51:36 +0530 Subject: [PATCH 12/14] fixed lint and added more unit tests --- internal/services/applications.go | 4 +- internal/services/applications_test.go | 51 ++++++++++++++++++++++ internal/wrappers/mock/application-mock.go | 16 +++++++ test/integration/scan_test.go | 2 +- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/internal/services/applications.go b/internal/services/applications.go index 7ffc52892..ad6298de3 100644 --- a/internal/services/applications.go +++ b/internal/services/applications.go @@ -64,7 +64,7 @@ func verifyApplicationNameExactMatch(applicationName string, resp *wrappers.Appl return application } -func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper, projectName string, projectID string, featureFlagsWrapper wrappers.FeatureFlagsWrapper) error { +func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper, projectName, projectID string, featureFlagsWrapper wrappers.FeatureFlagsWrapper) error { if applicationName == "" { logger.PrintfIfVerbose("No application name provided. Skipping application update") return nil @@ -112,7 +112,7 @@ func updateApplication(applicationModel *wrappers.ApplicationConfiguration, appl return handleApplicationUpdateResponse(errorModel, err) } -func associateProjectToApplication(applicationID string, projectID string, associatedProjectIds []string, applicationsWrapper wrappers.ApplicationsWrapper) error { +func associateProjectToApplication(applicationID, projectID string, associatedProjectIds []string, applicationsWrapper wrappers.ApplicationsWrapper) error { associatedProjectIds = append(associatedProjectIds, projectID) associateProjectsModel := &wrappers.AssociateProjectModel{ ProjectIds: associatedProjectIds, diff --git a/internal/services/applications_test.go b/internal/services/applications_test.go index 06ecad984..51849a1f2 100644 --- a/internal/services/applications_test.go +++ b/internal/services/applications_test.go @@ -1,7 +1,12 @@ package services import ( + errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" + "github.com/checkmarx/ast-cli/internal/wrappers" + "github.com/checkmarx/ast-cli/internal/wrappers/mock" + "gotest.tools/assert" "reflect" + "strings" "testing" ) @@ -36,3 +41,49 @@ func Test_createApplicationIds(t *testing.T) { }) } } +func Test_ProjectAssociation_ToApplicationDirectly(t *testing.T) { + applicationWrapper := &mock.ApplicationsMockWrapper{} + + tests := []struct { + description string + applicationName string + projectName string + error string + }{ + {"Project association to Application should fail with 403 forbidden permission error", mock.FakeForbidden403, "random-project", errorConstants.NoPermissionToUpdateApplication}, + {"Project association to Application should fail with 401 unauthorized error", mock.FakeUnauthorized401, "random-project", errorConstants.StatusUnauthorized}, + {"Project association to Application should fail with 400 BadRequest error", mock.FakeBadRequest400, "random-project", errorConstants.FailedToUpdateApplication}, + } + + for _, test := range tests { + tt := test + t.Run(tt.description, func(t *testing.T) { + err := associateProjectToApplication(tt.applicationName, tt.projectName, []string{}, applicationWrapper) + assert.Assert(t, strings.Contains(err.Error(), tt.error), err.Error()) + }) + } +} + +func Test_ProjectAssociation_ToApplicationWithoutDirectAssociation(t *testing.T) { + applicationModel := wrappers.ApplicationConfiguration{} + applicationWrapper := &mock.ApplicationsMockWrapper{} + + tests := []struct { + description string + applicationId string + projectName string + error string + }{ + {"Application update should fail with 403 forbidden permission error", mock.FakeForbidden403, "random-project", errorConstants.NoPermissionToUpdateApplication}, + {"Application update should fail with 401 unauthorized error", mock.FakeUnauthorized401, "random-project", errorConstants.StatusUnauthorized}, + {"Application update should fail with 400 BadRequest error", mock.FakeBadRequest400, "random-project", errorConstants.FailedToUpdateApplication}, + } + + for _, test := range tests { + tt := test + t.Run(tt.description, func(t *testing.T) { + err := updateApplication(&applicationModel, applicationWrapper, tt.applicationId) + assert.Assert(t, strings.Contains(err.Error(), tt.error), err.Error()) + }) + } +} diff --git a/internal/wrappers/mock/application-mock.go b/internal/wrappers/mock/application-mock.go index 5d286fd4b..8f50fa407 100644 --- a/internal/wrappers/mock/application-mock.go +++ b/internal/wrappers/mock/application-mock.go @@ -61,6 +61,13 @@ func (a ApplicationsMockWrapper) Update(applicationID string, applicationBody *w if applicationID == FakeUnauthorized401 { return nil, errors.Errorf(errorConstants.StatusUnauthorized) } + if applicationID == FakeBadRequest400 { + return &wrappers.ErrorModel{ + Message: "invalid applicationBody", + Code: 355, + Type: "validation", + }, nil + } return nil, nil } @@ -72,5 +79,14 @@ func (a ApplicationsMockWrapper) CreateProjectAssociation(applicationID string, if applicationID == FakeUnauthorized401 { return nil, errors.Errorf(errorConstants.StatusUnauthorized) } + + if applicationID == FakeBadRequest400 { + return &wrappers.ErrorModel{ + Message: "invalid applicationBody", + Code: 355, + Type: "validation", + }, nil + } + return nil, nil } diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index cfa5fbf42..db7f6d837 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -299,7 +299,7 @@ func TestScanCreate_ExistingApplicationAndExistingProject_CreateScanSuccessfully _, projectName := createNewProject(t, nil, nil, GenerateRandomProjectNameForScan()) args := []string{ "scan", "create", - flag(params.ApplicationName), "my-application", + flag(params.ApplicationName), "test-app", flag(params.ProjectName), projectName, flag(params.SourcesFlag), ".", flag(params.ScanTypes), params.IacType, From 753d2212726024f5baf5410cf7fc3eaddb5b7843 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Wed, 24 Sep 2025 18:35:20 +0530 Subject: [PATCH 13/14] fixed lint --- internal/services/applications_test.go | 4 ++-- internal/wrappers/application-http.go | 1 - internal/wrappers/mock/application-mock.go | 7 +++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/services/applications_test.go b/internal/services/applications_test.go index 51849a1f2..48e13dc91 100644 --- a/internal/services/applications_test.go +++ b/internal/services/applications_test.go @@ -70,7 +70,7 @@ func Test_ProjectAssociation_ToApplicationWithoutDirectAssociation(t *testing.T) tests := []struct { description string - applicationId string + applicationID string projectName string error string }{ @@ -82,7 +82,7 @@ func Test_ProjectAssociation_ToApplicationWithoutDirectAssociation(t *testing.T) for _, test := range tests { tt := test t.Run(tt.description, func(t *testing.T) { - err := updateApplication(&applicationModel, applicationWrapper, tt.applicationId) + err := updateApplication(&applicationModel, applicationWrapper, tt.applicationID) assert.Assert(t, strings.Contains(err.Error(), tt.error), err.Error()) }) } diff --git a/internal/wrappers/application-http.go b/internal/wrappers/application-http.go index 4e39d63ce..1921aa2c4 100644 --- a/internal/wrappers/application-http.go +++ b/internal/wrappers/application-http.go @@ -57,7 +57,6 @@ func (a *ApplicationsHTTPWrapper) CreateProjectAssociation(applicationID string, default: return nil, errors.Errorf("response status code %d", resp.StatusCode) } - } func (a *ApplicationsHTTPWrapper) Update(applicationID string, applicationBody *ApplicationConfiguration) (*ErrorModel, error) { diff --git a/internal/wrappers/mock/application-mock.go b/internal/wrappers/mock/application-mock.go index 8f50fa407..9bbdd28ef 100644 --- a/internal/wrappers/mock/application-mock.go +++ b/internal/wrappers/mock/application-mock.go @@ -9,6 +9,8 @@ import ( "github.com/pkg/errors" ) +const code = 355 + type ApplicationsMockWrapper struct{} func (a ApplicationsMockWrapper) Get(params map[string]string) (*wrappers.ApplicationsResponseModel, error) { @@ -55,6 +57,7 @@ func (a ApplicationsMockWrapper) Get(params map[string]string) (*wrappers.Applic func (a ApplicationsMockWrapper) Update(applicationID string, applicationBody *wrappers.ApplicationConfiguration) (*wrappers.ErrorModel, error) { fmt.Println("called Update application") + if applicationID == FakeForbidden403 { return nil, errors.Errorf(errorConstants.NoPermissionToUpdateApplication) } @@ -64,7 +67,7 @@ func (a ApplicationsMockWrapper) Update(applicationID string, applicationBody *w if applicationID == FakeBadRequest400 { return &wrappers.ErrorModel{ Message: "invalid applicationBody", - Code: 355, + Code: code, Type: "validation", }, nil } @@ -83,7 +86,7 @@ func (a ApplicationsMockWrapper) CreateProjectAssociation(applicationID string, if applicationID == FakeBadRequest400 { return &wrappers.ErrorModel{ Message: "invalid applicationBody", - Code: 355, + Code: code, Type: "validation", }, nil } From 2e0158febc17d93233d37afd5df1037396eab5e2 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Thu, 25 Sep 2025 10:38:36 +0530 Subject: [PATCH 14/14] Added additional test cases for coverage --- test/integration/predicate_test.go | 53 ++++++++++++++++++++---------- test/integration/scan_test.go | 15 +++++++++ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/test/integration/predicate_test.go b/test/integration/predicate_test.go index 794c74e09..5bd27d029 100644 --- a/test/integration/predicate_test.go +++ b/test/integration/predicate_test.go @@ -214,32 +214,49 @@ func TestSastUpdateAndGetPredicatesForNotFoundSimilarityId(t *testing.T) { } func TestTriageShowAndUpdateWithCustomStates(t *testing.T) { - t.Skip("Skipping this test temporarily until the API becomes available in the DEU environment.") fmt.Println("Step 1: Testing the command 'triage show' with predefined values.") // After the api/custom-states becomes available in the DEU environment, replace the hardcoded value with getRootScan(t) and create "state2" as a custom state in DEU. - projectID := "a2d52ac9-d007-4d95-a65e-bbe61c3451a4" - similarityID := "-1213859962" - scanType := "sast" - - outputBuffer := executeCmdNilAssertion( - t, "Fetching predicates should work.", "triage", "show", - flag(params.FormatFlag), printer.FormatJSON, - flag(params.ProjectIDFlag), projectID, - flag(params.SimilarityIDFlag), similarityID, - flag(params.ScanTypeFlag), scanType, + scanID, projectID := getRootScan(t) + _ = executeCmdNilAssertion( + t, "Results show generating JSON report with options should pass", + "results", "show", + flag(params.ScanIDFlag), scanID, flag(params.TargetFormatFlag), printer.FormatJSON, + flag(params.TargetPathFlag), resultsDirectory, + flag(params.TargetFlag), fileName, ) - predicateResult := []wrappers.Predicate{} - fmt.Println(outputBuffer) - _ = unmarshall(t, outputBuffer, &predicateResult, "Reading results should pass") - assert.Assert(t, len(predicateResult) >= 1, "Should have at least 1 predicate as the result.") + defer func() { + _ = os.RemoveAll(fmt.Sprintf(resultsDirectory)) + }() + + result := wrappers.ScanResultsCollection{} + + _, err := os.Stat(fmt.Sprintf("%s%s.%s", resultsDirectory, fileName, printer.FormatJSON)) + assert.NilError(t, err, "Report file should exist for extension "+printer.FormatJSON) + + file, err := os.ReadFile(fmt.Sprintf("%s%s.%s", resultsDirectory, fileName, printer.FormatJSON)) + assert.NilError(t, err, "error reading file") + + err = json.Unmarshal(file, &result) + assert.NilError(t, err, "error unmarshalling file") + + index := 0 + for i := range result.Results { + if strings.EqualFold(result.Results[i].Type, params.SastType) { + index = i + break + } + } + + similarityID := result.Results[index].SimilarityID + scanType := result.Results[index].Type fmt.Println("Step 2: Updating the predicate state.") - newState := "state2" + newState := "Custom1" newSeverity := "HIGH" - comment := "" + comment := "Updating State test" - err, _ := executeCommand( + err, _ = executeCommand( t, "triage", "update", flag(params.ProjectIDFlag), projectID, flag(params.SimilarityIDFlag), similarityID, diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index db7f6d837..2cd7c2360 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -2710,3 +2710,18 @@ func TestCreateScanWithExistingProjectAnd_AssignApplication(t *testing.T) { err, _ := executeCommand(t, args...) assert.NilError(t, err, "Project should be assigned to application") } + +func TestCreateScanWithNewProjectName_Assign_Groups(t *testing.T) { + + defer deleteProjectByName(t, getProjectNameForTest()) + args := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.BranchFlag), "dummy_branch", + flag(params.SourcesFlag), "data/sources-gitignore.zip", + flag(params.ProjectGroupList), formatGroups(Groups), + } + err, _ := executeCommand(t, args...) + assert.NilError(t, err, "Groups should be assigned to newly created projects") + +}