Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 52 additions & 13 deletions internal/commands/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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"}
Expand Down Expand Up @@ -3623,3 +3619,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.ErrorContains(t, err, errorConstants.FailedToGetApplication, err.Error())
}
7 changes: 4 additions & 3 deletions internal/constants/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
51 changes: 51 additions & 0 deletions internal/services/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -58,3 +63,49 @@ 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, "%s:%s", errorConstants.FailedToGetApplication, applicationName)
}
if applicationResp == nil {
return errors.Errorf("Application not found: %s", 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(applicationModel.Rules, applicationResp.Rules...)
applicationModel.Rules = append(applicationModel.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
}
7 changes: 5 additions & 2 deletions internal/services/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
40 changes: 40 additions & 0 deletions internal/wrappers/application-http.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package wrappers

import (
"bytes"
"encoding/json"
"fmt"
"net/http"

errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors"
Expand All @@ -20,6 +22,44 @@ 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)
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: %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
Expand Down
34 changes: 20 additions & 14 deletions internal/wrappers/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
13 changes: 12 additions & 1 deletion internal/wrappers/mock/application-mock.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mock

import (
"fmt"
"time"

errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors"
Expand Down Expand Up @@ -49,6 +50,16 @@ 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) {
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
}
44 changes: 31 additions & 13 deletions test/integration/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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) {
Expand Down Expand Up @@ -2706,3 +2696,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 not found: mock")
}
Loading