diff --git a/internal/commands/project.go b/internal/commands/project.go index 8afb1f6c7..5a8742ada 100644 --- a/internal/commands/project.go +++ b/internal/commands/project.go @@ -233,7 +233,7 @@ func runCreateProjectCommand( applicationName, _ := cmd.Flags().GetString(commonParams.ApplicationName) var applicationID []string if applicationName != "" { - application, getAppErr := getApplication(applicationName, applicationsWrapper) + application, getAppErr := services.GetApplication(applicationName, applicationsWrapper) if getAppErr != nil { return getAppErr } diff --git a/internal/commands/scan.go b/internal/commands/scan.go index d5b8c29f8..ce31348b9 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -24,7 +24,6 @@ import ( "github.com/checkmarx/ast-cli/internal/commands/util" "github.com/checkmarx/ast-cli/internal/commands/util/printer" "github.com/checkmarx/ast-cli/internal/constants" - errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" exitCodes "github.com/checkmarx/ast-cli/internal/constants/exit-codes" "github.com/checkmarx/ast-cli/internal/logger" "github.com/checkmarx/ast-cli/internal/services" @@ -709,23 +708,8 @@ func setupScanTypeProjectAndConfig( return errors.Errorf("Project name is required") } - applicationName, _ := cmd.Flags().GetString(commonParams.ApplicationName) - - var applicationID []string - if applicationName != "" { - application, getAppErr := getApplication(applicationName, applicationsWrapper) - if getAppErr != nil { - return getAppErr - } - if application == nil { - return errors.Errorf(errorConstants.ApplicationDoesntExistOrNoPermission) - } - applicationID = []string{application.ID} - } - // We need to convert the project name into an ID projectID, findProjectErr := services.FindProject( - applicationID, info["project"].(map[string]interface{})["id"].(string), cmd, projectsWrapper, @@ -799,34 +783,6 @@ func setupScanTypeProjectAndConfig( return nil } -func getApplication(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper) (*wrappers.Application, error) { - if applicationName != "" { - params := make(map[string]string) - params["name"] = applicationName - resp, err := applicationsWrapper.Get(params) - if err != nil { - return nil, err - } - if resp.Applications != nil && len(resp.Applications) > 0 { - application := verifyApplicationNameExactMatch(applicationName, resp) - - return application, nil - } - } - return nil, nil -} - -func verifyApplicationNameExactMatch(applicationName string, resp *wrappers.ApplicationsResponseModel) *wrappers.Application { - var application *wrappers.Application - for i := range resp.Applications { - if resp.Applications[i].Name == applicationName { - application = &resp.Applications[i] - break - } - } - return application -} - func getResubmitConfiguration(scansWrapper wrappers.ScansWrapper, projectID, userScanTypes string) ( []wrappers.Config, error, diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index c8d341feb..39d9a6444 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -55,7 +55,7 @@ const ( SCSScoreCardError = "SCS scan failed to start: Scorecard scan is missing required flags, please include in the ast-cli arguments: " + "--scs-repo-url your_repo_url --scs-repo-token your_repo_token" outputFileName = "test_output.log" - noUpdatesForExistingProject = "No applicationId or tags to update. Skipping project update." + noUpdatesForExistingProject = "No tags to update. Skipping project update." ) func TestScanHelp(t *testing.T) { @@ -193,18 +193,13 @@ func TestCreateScanWithThreshold_ShouldSuccess(t *testing.T) { execCmdNilAssertion(t, "scan", "create", "--project-name", "MOCK", "-s", dummyRepo, "-b", "dummy_branch", "--scan-types", "sast", "--threshold", "sca-low=1 ; sast-medium=2") } -func TestScanCreate_ExistingApplicationAndProject_CreateProjectUnderApplicationSuccessfully(t *testing.T) { - execCmdNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--application-name", "MOCK", "-s", dummyRepo, "-b", "dummy_branch") -} - func TestScanCreate_ApplicationNameIsNotExactMatch_FailedToCreateScan(t *testing.T) { - err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--application-name", "MOC", "-s", dummyRepo, "-b", "dummy_branch") + err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "non-existing-project", "--application-name", "MOC", "-s", dummyRepo, "-b", "dummy_branch") assert.Assert(t, err.Error() == errorConstants.ApplicationDoesntExistOrNoPermission) } -func TestScanCreate_ExistingProjectAndApplicationWithNoPermission_FailedToCreateScan(t *testing.T) { - err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--application-name", mock.ApplicationDoesntExist, "-s", dummyRepo, "-b", "dummy_branch") - 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_ExistingApplicationWithNoPermission_FailedToCreateScan(t *testing.T) { @@ -213,20 +208,16 @@ func TestScanCreate_ExistingApplicationWithNoPermission_FailedToCreateScan(t *te } func TestScanCreate_OnReceivingHttpBadRequestStatusCode_FailedToCreateScan(t *testing.T) { - err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--application-name", mock.FakeBadRequest400, "-s", dummyRepo, "-b", "dummy_branch") + err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "non-existing-project", "--application-name", mock.FakeBadRequest400, "-s", dummyRepo, "-b", "dummy_branch") assert.Assert(t, err.Error() == errorConstants.FailedToGetApplication) } func TestScanCreate_OnReceivingHttpInternalServerErrorStatusCode_FailedToCreateScan(t *testing.T) { - err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--application-name", mock.FakeInternalServerError500, "-s", dummyRepo, "-b", "dummy_branch") + err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "non-existing-project", + "--application-name", mock.FakeInternalServerError500, "-s", dummyRepo, "-b", "dummy_branch") assert.Assert(t, err.Error() == errorConstants.FailedToGetApplication) } -func TestCreateScanInsideApplicationProjectExistNoPermissions(t *testing.T) { - err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--application-name", mock.NoPermissionApp, "-s", dummyRepo, "-b", "dummy_branch") - assert.Assert(t, err.Error() == errorConstants.ApplicationDoesntExistOrNoPermission) -} - func TestCreateScanSourceDirectory(t *testing.T) { baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-b", "dummy_branch"} execCmdNilAssertion(t, append(baseArgs, "-s", "data", "--file-filter", "!.java")...) @@ -411,6 +402,23 @@ func TestCreateScan_WhenProjectNotExists_ShouldCreateProjectAndAssignGroup(t *te assert.Equal(t, strings.Contains(stdoutString, "Updating project groups"), true, "Expected output: %s", "Updating project groups") } +func TestCreateScan_WhenProjectNotExists_ShouldCreateProjectAndAssociateApplication(t *testing.T) { + file := createOutputFile(t, outputFileName) + defer deleteOutputFile(file) + defer logger.SetOutput(os.Stdout) + + baseArgs := []string{"scan", "create", "--project-name", "newProject", "-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, "application association done successfully"), true, "Expected output: %s", "application association done successfully") +} + func TestScanWorkflowMissingID(t *testing.T) { err := execCmdNotNilAssertion(t, "scan", "workflow") assert.Error(t, err, "Please provide a scan ID", err.Error()) @@ -624,6 +632,20 @@ 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) + 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) +} 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"} err := execCmdNotNilAssertion(t, baseArgs...) diff --git a/internal/commands/util/import.go b/internal/commands/util/import.go index 92dccb85e..ea1483265 100644 --- a/internal/commands/util/import.go +++ b/internal/commands/util/import.go @@ -65,7 +65,7 @@ func runImportCommand( return errors.Errorf(errorConstants.ProjectNameIsRequired) } - projectID, err := services.FindProject(nil, projectName, cmd, projectsWrapper, groupsWrapper, accessManagementWrapper, applicationsWrapper, featureFlagsWrapper) + projectID, err := services.FindProject(projectName, cmd, projectsWrapper, groupsWrapper, accessManagementWrapper, applicationsWrapper, featureFlagsWrapper) if err != nil { return err } diff --git a/internal/services/applications.go b/internal/services/applications.go index 586fe9c60..744e89670 100644 --- a/internal/services/applications.go +++ b/internal/services/applications.go @@ -1,6 +1,11 @@ package services -import "github.com/checkmarx/ast-cli/internal/wrappers/utils" +import ( + errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" + "github.com/checkmarx/ast-cli/internal/wrappers" + "github.com/checkmarx/ast-cli/internal/wrappers/utils" + "github.com/pkg/errors" +) func createApplicationIds(applicationID, existingApplicationIds []string) []string { for _, id := range applicationID { @@ -10,3 +15,46 @@ func createApplicationIds(applicationID, existingApplicationIds []string) []stri } return existingApplicationIds } + +func getApplicationID(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper) ([]string, error) { + var applicationID []string + if applicationName != "" { + application, getAppErr := GetApplication(applicationName, applicationsWrapper) + if getAppErr != nil { + return nil, getAppErr + } + if application == nil { + return nil, errors.Errorf(errorConstants.ApplicationDoesntExistOrNoPermission) + } + applicationID = []string{application.ID} + } + return applicationID, nil +} + +func GetApplication(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper) (*wrappers.Application, error) { + if applicationName != "" { + params := make(map[string]string) + params["name"] = applicationName + resp, err := applicationsWrapper.Get(params) + if err != nil { + return nil, err + } + if resp.Applications != nil && len(resp.Applications) > 0 { + application := verifyApplicationNameExactMatch(applicationName, resp) + + return application, nil + } + } + return nil, nil +} + +func verifyApplicationNameExactMatch(applicationName string, resp *wrappers.ApplicationsResponseModel) *wrappers.Application { + var application *wrappers.Application + for i := range resp.Applications { + if resp.Applications[i].Name == applicationName { + application = &resp.Applications[i] + break + } + } + return application +} diff --git a/internal/services/projects.go b/internal/services/projects.go index e536b74ed..c5fd9084a 100644 --- a/internal/services/projects.go +++ b/internal/services/projects.go @@ -22,7 +22,6 @@ const ( ) func FindProject( - applicationID []string, projectName string, cmd *cobra.Command, projectsWrapper wrappers.ProjectsWrapper, @@ -44,11 +43,8 @@ func FindProject( resp, cmd, projectsWrapper, - groupsWrapper, accessManagementWrapper, - applicationWrapper, projectName, - applicationID, projectTags, projectPrivatePackage, featureFlagsWrapper) @@ -57,6 +53,13 @@ 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 + } + projectID, err := createProject(projectName, cmd, projectsWrapper, groupsWrapper, accessManagementWrapper, applicationWrapper, applicationID, projectGroups, projectPrivatePackage, featureFlagsWrapper) if err != nil { @@ -178,18 +181,14 @@ func updateProject( resp *wrappers.ProjectsCollectionResponseModel, cmd *cobra.Command, projectsWrapper wrappers.ProjectsWrapper, - groupsWrapper wrappers.GroupsWrapper, accessManagementWrapper wrappers.AccessManagementWrapper, - applicationsWrapper wrappers.ApplicationsWrapper, projectName string, - applicationID []string, projectTags string, projectPrivatePackage string, featureFlagsWrapper wrappers.FeatureFlagsWrapper, ) (string, error) { var projectID string - applicationName, _ := cmd.Flags().GetString(commonParams.ApplicationName) var projModel = wrappers.Project{} for i := 0; i < len(resp.Projects); i++ { if resp.Projects[i].Name == projectName { @@ -202,8 +201,8 @@ func updateProject( projModel.RepoURL = resp.Projects[i].RepoURL } } - if projectTags == "" && projectPrivatePackage == "" && len(applicationID) == 0 { - logger.PrintIfVerbose("No applicationId or tags to update. Skipping project update.") + if projectTags == "" && projectPrivatePackage == "" { + logger.PrintIfVerbose("No tags to update. Skipping project update.") return projectID, nil } if projectPrivatePackage != "" { @@ -226,22 +225,12 @@ func updateProject( logger.PrintIfVerbose("Updating project tags") projModel.Tags = createTagMap(projectTags) } - if len(applicationID) > 0 { - logger.PrintIfVerbose("Updating project applicationIds") - projModel.ApplicationIds = createApplicationIds(applicationID, projModelResp.ApplicationIds) - } + err = projectsWrapper.Update(projectID, &projModel) if err != nil { return "", errors.Errorf("%s: %v", failedUpdatingProj, err) } - if applicationName != "" || len(applicationID) > 0 { - err = verifyApplicationAssociationDone(applicationName, projectID, applicationsWrapper) - if err != nil { - return projectID, err - } - } - return projectID, nil } diff --git a/internal/services/projects_test.go b/internal/services/projects_test.go index 00235b3fd..0fc0a77ba 100644 --- a/internal/services/projects_test.go +++ b/internal/services/projects_test.go @@ -60,7 +60,6 @@ func TestFindProject(t *testing.T) { ttt := tt t.Run(tt.name, func(t *testing.T) { got, err := FindProject( - ttt.args.applicationID, ttt.args.projectName, ttt.args.cmd, ttt.args.projectsWrapper, @@ -240,19 +239,6 @@ func Test_updateProject(t *testing.T) { projectPrivatePackage: "true", featureFlagsWrapper: &mock.FeatureFlagsMockWrapper{}, }, want: "ID-project-name", wantErr: false}, - {name: "When called with mock fake error model return fake error from project create", args: args{ - projectName: "mock-some-error-model", - resp: &wrappers.ProjectsCollectionResponseModel{ - Projects: []wrappers.ProjectResponseModel{ - {ID: "ID-mock-some-error-model", Name: "mock-some-error-model"}}, - }, - cmd: &cobra.Command{}, - projectsWrapper: &mock.ProjectsMockWrapper{}, - groupsWrapper: &mock.GroupsMockWrapper{}, - accessManagementWrapper: &mock.AccessManagementMockWrapper{}, - applicationID: []string{"1"}, - featureFlagsWrapper: &mock.FeatureFlagsMockWrapper{}, - }, want: "", wantErr: true}, } for _, tt := range tests { ttt := tt @@ -261,11 +247,8 @@ func Test_updateProject(t *testing.T) { ttt.args.resp, ttt.args.cmd, ttt.args.projectsWrapper, - ttt.args.groupsWrapper, ttt.args.accessManagementWrapper, - ttt.args.applicationsWrapper, ttt.args.projectName, - ttt.args.applicationID, ttt.args.projectTags, ttt.args.projectPrivatePackage, ttt.args.featureFlagsWrapper) diff --git a/internal/wrappers/mock/application-mock.go b/internal/wrappers/mock/application-mock.go index d7d83df9b..fdb9e01a9 100644 --- a/internal/wrappers/mock/application-mock.go +++ b/internal/wrappers/mock/application-mock.go @@ -28,14 +28,27 @@ func (a ApplicationsMockWrapper) Get(params map[string]string) (*wrappers.Applic Name: "MOCK", Description: "This is a mock application", Criticality: 2, - ProjectIds: []string{"ProjectID1", "ProjectID2", "MOCK", "test_project", "ID-new-project-name"}, + ProjectIds: []string{"ProjectID1", "ProjectID2", "MOCK", "test_project", "ID-new-project-name", "ID-newProject"}, CreatedAt: time.Now(), } + if params["name"] == ExistingApplication { + mockApplication.Name = ExistingApplication + mockApplication.ID = "ID-newProject" + return &wrappers.ApplicationsResponseModel{ + TotalCount: 1, + Applications: []wrappers.Application{mockApplication}, + }, nil + } response := &wrappers.ApplicationsResponseModel{ TotalCount: 1, Applications: []wrappers.Application{mockApplication}, } + if params["name"] == "anyApplication" { + response.TotalCount = 0 + response.Applications = []wrappers.Application{} + } + return response, nil } diff --git a/internal/wrappers/mock/constants.go b/internal/wrappers/mock/constants.go index 39b4b9984..37ae46493 100644 --- a/internal/wrappers/mock/constants.go +++ b/internal/wrappers/mock/constants.go @@ -2,6 +2,7 @@ package mock const ( ApplicationDoesntExist = "application-doesnt-exist" + ExistingApplication = "application-exists" NoPermissionApp = "NoPermissionApp" FakeBadRequest400 = "fake-http-status-bad-request" FakeUnauthorized401 = "fake-unauthorized-response" diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index 99cfa416f..d2a4e38bd 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -347,15 +347,14 @@ func TestScanCreate_ExistingApplicationAndNotExistingProject_CreatingNewProjectA assert.Assert(t, projectID != "", "Project ID should not be empty") } -func TestScanCreate_ApplicationDoesntExist_FailScanWithError(t *testing.T) { +func TestScanCreate_WithNewProjectAndApplicationDoesntExist_ShouldFailScanWithError(t *testing.T) { args := []string{ "scan", "create", flag(params.ApplicationName), "application-that-doesnt-exist", - flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.ProjectName), "newProject", flag(params.SourcesFlag), ".", flag(params.ScanTypes), params.IacType, flag(params.BranchFlag), "dummy_branch", - flag(params.DebugFlag), } err, _ := executeCommand(t, args...) @@ -1578,6 +1577,35 @@ func TestScanCreate_WhenProjectExists_ShouldNotUpdateGroups(t *testing.T) { t.Errorf("When project exists, groups before and after scan creation should be equal. Got %v, want %v", groupsAfterScanCreate, groupsBeforeScanCreate) } +} + +func TestScanCreate_WhenProjectExists_ShouldNotUpdateApplication(t *testing.T) { + projectID, projectName := getRootProject(t) + project := showProject(t, projectID) + applicationsBeforeScanCreate := project.ApplicationIds + + args := []string{ + scanCommand, "create", + flag(params.ProjectName), projectName, + flag(params.SourcesFlag), Zip, + flag(params.ScanTypes), "sast", + flag(params.PresetName), "Checkmarx Default", + flag(params.BranchFlag), "dummy_branch", + flag(params.ApplicationName), "wrong_application", + "--async", + } + + 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) + } + } func TestScanCreateExploitablePath(t *testing.T) { _, projectName := getRootProject(t)