github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/fortifyExecuteScan_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package cmd
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"os"
    14  	"path/filepath"
    15  	"reflect"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/SAP/jenkins-library/pkg/mock"
    21  
    22  	"github.com/SAP/jenkins-library/pkg/fortify"
    23  	"github.com/SAP/jenkins-library/pkg/log"
    24  	"github.com/SAP/jenkins-library/pkg/piperutils"
    25  	"github.com/SAP/jenkins-library/pkg/versioning"
    26  
    27  	"github.com/google/go-github/v45/github"
    28  	"github.com/stretchr/testify/assert"
    29  
    30  	"github.com/piper-validation/fortify-client-go/models"
    31  )
    32  
    33  const author string = "johnDoe178"
    34  
    35  type fortifyTestUtilsBundle struct {
    36  	*execRunnerMock
    37  	*mock.FilesMock
    38  	getArtifactShouldFail bool
    39  }
    40  
    41  func (f *fortifyTestUtilsBundle) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error {
    42  	panic("not expected to be called in tests")
    43  }
    44  
    45  func (f *fortifyTestUtilsBundle) GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) {
    46  	if f.getArtifactShouldFail {
    47  		return nil, fmt.Errorf("build tool '%v' not supported", buildTool)
    48  	}
    49  	return artifactMock{Coordinates: newCoordinatesMock()}, nil
    50  }
    51  
    52  func (f *fortifyTestUtilsBundle) GetIssueService() *github.IssuesService {
    53  	return nil
    54  }
    55  
    56  func (cf *fortifyTestUtilsBundle) GetSearchService() *github.SearchService {
    57  	return nil
    58  }
    59  
    60  func newFortifyTestUtilsBundle() fortifyTestUtilsBundle {
    61  	utilsBundle := fortifyTestUtilsBundle{
    62  		execRunnerMock: &execRunnerMock{},
    63  		FilesMock:      &mock.FilesMock{},
    64  	}
    65  	return utilsBundle
    66  }
    67  func mockExecinPath(exec string) (string, error) {
    68  	executable_list := []string{"fortifyupdate", "sourceanalyzer"}
    69  	for _, exec := range executable_list {
    70  		if exec == "fortifyupdate" || exec == "sourceanalyzer" {
    71  			return "/" + exec, nil
    72  		} else {
    73  			err_string := fmt.Sprintf("ERROR , command not found: %s. Please configure a supported docker image or install Fortify SCA on the system.", exec)
    74  			return "", errors.New(err_string)
    75  		}
    76  	}
    77  	return "", nil
    78  }
    79  
    80  func failMockExecinPathfortifyupdate(exec string) (string, error) {
    81  	if exec == "fortifyupdate" {
    82  		return "", errors.New("Command not found: fortifyupdate. Please configure a supported docker image or install Fortify SCA on the system.")
    83  	}
    84  	return "/fortifyupdate", nil
    85  }
    86  func failMockExecinPathsourceanalyzer(exec string) (string, error) {
    87  	if exec == "sourceanalyzer" {
    88  		return "", errors.New("Command not found: sourceanalyzer. Please configure a supported docker image or install Fortify SCA on the system.")
    89  	}
    90  	return "/sourceanalyzer", nil
    91  }
    92  
    93  type artifactMock struct {
    94  	Coordinates versioning.Coordinates
    95  }
    96  
    97  func newCoordinatesMock() versioning.Coordinates {
    98  	return versioning.Coordinates{
    99  		GroupID:    "a",
   100  		ArtifactID: "b",
   101  		Version:    "1.0.0",
   102  	}
   103  }
   104  
   105  func (a artifactMock) VersioningScheme() string {
   106  	return "full"
   107  }
   108  
   109  func (a artifactMock) GetVersion() (string, error) {
   110  	return a.Coordinates.Version, nil
   111  }
   112  
   113  func (a artifactMock) SetVersion(v string) error {
   114  	a.Coordinates.Version = v
   115  	return nil
   116  }
   117  
   118  func (a artifactMock) GetCoordinates() (versioning.Coordinates, error) {
   119  	return a.Coordinates, nil
   120  }
   121  
   122  type fortifyMock struct {
   123  	Successive                       bool
   124  	getArtifactsOfProjectVersionIdx  int
   125  	getArtifactsOfProjectVersionTime time.Time
   126  }
   127  
   128  func (f *fortifyMock) GetProjectByName(name string, autoCreate bool, projectVersion string) (*models.Project, error) {
   129  	return &models.Project{Name: &name, ID: 64}, nil
   130  }
   131  
   132  func (f *fortifyMock) GetProjectVersionDetailsByProjectIDAndVersionName(id int64, name string, autoCreate bool, projectName string) (*models.ProjectVersion, error) {
   133  	return &models.ProjectVersion{ID: id, Name: &name, Project: &models.Project{Name: &projectName}}, nil
   134  }
   135  
   136  func (f *fortifyMock) GetProjectVersionAttributesByProjectVersionID(id int64) ([]*models.Attribute, error) {
   137  	return []*models.Attribute{}, nil
   138  }
   139  
   140  func (f *fortifyMock) SetProjectVersionAttributesByProjectVersionID(id int64, attributes []*models.Attribute) ([]*models.Attribute, error) {
   141  	return attributes, nil
   142  }
   143  
   144  func (f *fortifyMock) CreateProjectVersionIfNotExist(projectName, projectVersionName, description string) (*models.ProjectVersion, error) {
   145  	return &models.ProjectVersion{ID: 4711, Name: &projectVersionName, Project: &models.Project{Name: &projectName}}, nil
   146  }
   147  
   148  func (f *fortifyMock) LookupOrCreateProjectVersionDetailsForPullRequest(projectID int64, masterProjectVersion *models.ProjectVersion, pullRequestName string) (*models.ProjectVersion, error) {
   149  	return &models.ProjectVersion{ID: 4712, Name: &pullRequestName, Project: masterProjectVersion.Project}, nil
   150  }
   151  
   152  func (f *fortifyMock) CreateProjectVersion(version *models.ProjectVersion) (*models.ProjectVersion, error) {
   153  	return version, nil
   154  }
   155  
   156  func (f *fortifyMock) ProjectVersionCopyFromPartial(sourceID, targetID int64) error {
   157  	return nil
   158  }
   159  
   160  func (f *fortifyMock) ProjectVersionCopyCurrentState(sourceID, targetID int64) error {
   161  	return nil
   162  }
   163  
   164  func (f *fortifyMock) ProjectVersionCopyPermissions(sourceID, targetID int64) error {
   165  	return nil
   166  }
   167  
   168  func (f *fortifyMock) CommitProjectVersion(id int64) (*models.ProjectVersion, error) {
   169  	name := "Committed"
   170  	return &models.ProjectVersion{ID: id, Name: &name}, nil
   171  }
   172  
   173  func (f *fortifyMock) MergeProjectVersionStateOfPRIntoMaster(downloadEndpoint, uploadEndpoint string, masterProjectID, masterProjectVersionID int64, pullRequestName string) error {
   174  	return nil
   175  }
   176  
   177  func (f *fortifyMock) GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact, error) {
   178  	switch id {
   179  	case 4711:
   180  		return []*models.Artifact{{
   181  			Status:     "PROCESSED",
   182  			UploadDate: toFortifyTime(time.Now()),
   183  		}}, nil
   184  	case 4712:
   185  		return []*models.Artifact{{
   186  			Status:     "ERROR_PROCESSING",
   187  			UploadDate: toFortifyTime(time.Now()),
   188  		}}, nil
   189  	case 4713:
   190  		return []*models.Artifact{{
   191  			Status:     "REQUIRE_AUTH",
   192  			UploadDate: toFortifyTime(time.Now()),
   193  		}}, nil
   194  	case 4714:
   195  		return []*models.Artifact{{
   196  			Status:     "PROCESSING",
   197  			UploadDate: toFortifyTime(time.Now()),
   198  		}}, nil
   199  	case 4715:
   200  		return []*models.Artifact{{
   201  			Status: "PROCESSED",
   202  			Embed: &models.EmbeddedScans{
   203  				Scans: []*models.Scan{{BuildLabel: "/commit/test"}},
   204  			},
   205  			UploadDate: toFortifyTime(time.Now()),
   206  		}}, nil
   207  	case 4716:
   208  		var status string
   209  		if f.getArtifactsOfProjectVersionIdx == 0 {
   210  			f.getArtifactsOfProjectVersionTime = time.Now().Add(-2 * time.Minute)
   211  		}
   212  		if f.getArtifactsOfProjectVersionIdx < 2 {
   213  			status = "PROCESSING"
   214  		} else {
   215  			f.getArtifactsOfProjectVersionTime = time.Now()
   216  			status = "PROCESSED"
   217  		}
   218  		f.getArtifactsOfProjectVersionIdx++
   219  
   220  		return []*models.Artifact{{
   221  			Status:     status,
   222  			UploadDate: toFortifyTime(f.getArtifactsOfProjectVersionTime),
   223  		}}, nil
   224  	case 4718:
   225  		return []*models.Artifact{
   226  			{
   227  				Status:     "PROCESSED",
   228  				UploadDate: toFortifyTime(time.Now()),
   229  			},
   230  			{
   231  				Status:     "ERROR_PROCESSING",
   232  				UploadDate: toFortifyTime(time.Now().Add(-2 * time.Minute)),
   233  			},
   234  		}, nil
   235  
   236  	default:
   237  		return []*models.Artifact{}, nil
   238  	}
   239  }
   240  
   241  func (f *fortifyMock) GetFilterSetOfProjectVersionByTitle(id int64, title string) (*models.FilterSet, error) {
   242  	return &models.FilterSet{}, nil
   243  }
   244  
   245  func (f *fortifyMock) GetIssueFilterSelectorOfProjectVersionByName(id int64, names []string, options []string) (*models.IssueFilterSelectorSet, error) {
   246  	return &models.IssueFilterSelectorSet{}, nil
   247  }
   248  
   249  func (f *fortifyMock) GetFilterSetByDisplayName(issueFilterSelectorSet *models.IssueFilterSelectorSet, name string) *models.IssueFilterSelector {
   250  	if issueFilterSelectorSet.FilterBySet != nil {
   251  		for _, filter := range issueFilterSelectorSet.FilterBySet {
   252  			if filter.DisplayName == name {
   253  				return filter
   254  			}
   255  		}
   256  	}
   257  	return &models.IssueFilterSelector{DisplayName: name}
   258  }
   259  
   260  func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) {
   261  	if filter == "ET1:abcd" {
   262  		group := "HTTP Verb tampering"
   263  		total := int32(4)
   264  		audited := int32(3)
   265  		group2 := "Password in code"
   266  		total2 := int32(4)
   267  		audited2 := int32(4)
   268  		group3 := "Memory leak"
   269  		total3 := int32(5)
   270  		audited3 := int32(4)
   271  		return []*models.ProjectVersionIssueGroup{
   272  			{ID: &group, TotalCount: &total, AuditedCount: &audited},
   273  			{ID: &group2, TotalCount: &total2, AuditedCount: &audited2},
   274  			{ID: &group3, TotalCount: &total3, AuditedCount: &audited3},
   275  		}, nil
   276  	}
   277  	if issueFilterSelectorSet != nil && issueFilterSelectorSet.FilterBySet != nil && len(issueFilterSelectorSet.FilterBySet) > 0 && issueFilterSelectorSet.FilterBySet[0].GUID == "3" {
   278  		groupName := "Suspicious"
   279  		groupName2 := "Exploitable"
   280  		group := "3"
   281  		total := int32(4)
   282  		audited := int32(0)
   283  		group2 := "4"
   284  		total2 := int32(5)
   285  		audited2 := int32(0)
   286  		return []*models.ProjectVersionIssueGroup{
   287  			{ID: &group, CleanName: &groupName, TotalCount: &total, AuditedCount: &audited},
   288  			{ID: &group2, CleanName: &groupName2, TotalCount: &total2, AuditedCount: &audited2},
   289  		}, nil
   290  	}
   291  	group := "Audit All"
   292  	total := int32(15)
   293  	audited := int32(12)
   294  	group2 := "Corporate Security Requirements"
   295  	total2 := int32(20)
   296  	audited2 := int32(11)
   297  	group3 := "Spot Checks of Each Category"
   298  	total3 := int32(5)
   299  	audited3 := int32(4)
   300  	return []*models.ProjectVersionIssueGroup{
   301  		{ID: &group, CleanName: &group, TotalCount: &total, AuditedCount: &audited},
   302  		{ID: &group2, CleanName: &group2, TotalCount: &total2, AuditedCount: &audited2},
   303  		{ID: &group3, CleanName: &group3, TotalCount: &total3, AuditedCount: &audited3},
   304  	}, nil
   305  }
   306  
   307  func (f *fortifyMock) ReduceIssueFilterSelectorSet(issueFilterSelectorSet *models.IssueFilterSelectorSet, names []string, options []string) *models.IssueFilterSelectorSet {
   308  	return issueFilterSelectorSet
   309  }
   310  
   311  func (f *fortifyMock) GetIssueStatisticsOfProjectVersion(id int64) ([]*models.IssueStatistics, error) {
   312  	suppressed := int32(6)
   313  	return []*models.IssueStatistics{{SuppressedCount: &suppressed}}, nil
   314  }
   315  
   316  func (f *fortifyMock) GenerateQGateReport(projectID, projectVersionID, reportTemplateID int64, projectName, projectVersionName, reportFormat string) (*models.SavedReport, error) {
   317  	if !f.Successive {
   318  		f.Successive = true
   319  		return &models.SavedReport{Status: "PROCESSING"}, nil
   320  	}
   321  	f.Successive = false
   322  	return &models.SavedReport{Status: "PROCESS_COMPLETE"}, nil
   323  }
   324  
   325  func (f *fortifyMock) GetReportDetails(id int64) (*models.SavedReport, error) {
   326  	return &models.SavedReport{Status: "PROCESS_COMPLETE"}, nil
   327  }
   328  
   329  func (f *fortifyMock) GetAllIssueDetails(projectVersionId int64) ([]*models.ProjectVersionIssue, error) {
   330  	exploitable := "Exploitable"
   331  	friority := "High"
   332  	hascomments := true
   333  	return []*models.ProjectVersionIssue{{ID: 1111, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}, {ID: 1112, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}}, nil
   334  }
   335  
   336  func (f *fortifyMock) GetIssueDetails(projectVersionId int64, issueInstanceId string) ([]*models.ProjectVersionIssue, error) {
   337  	exploitable := "Exploitable"
   338  	friority := "High"
   339  	hascomments := true
   340  	return []*models.ProjectVersionIssue{{ID: 1111, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}}, nil
   341  }
   342  
   343  func (f *fortifyMock) GetIssueComments(parentId int64) ([]*models.IssueAuditComment, error) {
   344  	comment := "Dummy"
   345  	return []*models.IssueAuditComment{{Comment: &comment}}, nil
   346  }
   347  
   348  func (f *fortifyMock) UploadResultFile(endpoint, file string, projectVersionID int64) error {
   349  	return nil
   350  }
   351  
   352  func (f *fortifyMock) DownloadReportFile(endpoint string, reportID int64) ([]byte, error) {
   353  	return []byte("abcd"), nil
   354  }
   355  
   356  func (f *fortifyMock) DownloadResultFile(endpoint string, projectVersionID int64) ([]byte, error) {
   357  	return []byte("defg"), nil
   358  }
   359  
   360  type pullRequestServiceMock struct{}
   361  
   362  func (prService pullRequestServiceMock) ListPullRequestsWithCommit(ctx context.Context, owner, repo, sha string, opts *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error) {
   363  	authorString := author
   364  	user := github.User{Login: &authorString}
   365  	if owner == "A" {
   366  		result := 17
   367  		return []*github.PullRequest{{Number: &result, User: &user}}, &github.Response{}, nil
   368  	} else if owner == "C" {
   369  		return []*github.PullRequest{{User: &user}}, &github.Response{}, errors.New("Test error")
   370  	} else if owner == "E" {
   371  		return []*github.PullRequest{{User: nil}}, &github.Response{}, errors.New("Test error")
   372  	}
   373  	return []*github.PullRequest{}, &github.Response{}, nil
   374  }
   375  
   376  type execRunnerMock struct {
   377  	numExecutions int
   378  	current       *execution
   379  	executions    []*execution
   380  }
   381  
   382  type execution struct {
   383  	dirValue   string
   384  	envValue   []string
   385  	outWriter  io.Writer
   386  	errWriter  io.Writer
   387  	executable string
   388  	parameters []string
   389  }
   390  
   391  func (er *execRunnerMock) newExecution() *execution {
   392  	newExecution := &execution{}
   393  	er.executions = append(er.executions, newExecution)
   394  	return newExecution
   395  }
   396  
   397  func (er *execRunnerMock) currentExecution() *execution {
   398  	if nil == er.current {
   399  		er.numExecutions = 0
   400  		er.current = er.newExecution()
   401  	}
   402  	return er.current
   403  }
   404  
   405  func (er *execRunnerMock) SetDir(d string) {
   406  	er.currentExecution().dirValue = d
   407  }
   408  
   409  func (er *execRunnerMock) SetEnv(e []string) {
   410  	er.currentExecution().envValue = e
   411  }
   412  
   413  func (er *execRunnerMock) Stdout(out io.Writer) {
   414  	er.currentExecution().outWriter = out
   415  }
   416  
   417  func (er *execRunnerMock) Stderr(err io.Writer) {
   418  	er.currentExecution().errWriter = err
   419  }
   420  
   421  func (er *execRunnerMock) RunExecutable(e string, p ...string) error {
   422  	er.numExecutions++
   423  	er.currentExecution().executable = e
   424  	if len(p) > 0 && piperutils.ContainsString(p, "--failTranslate") {
   425  		return errors.New("Translate failed")
   426  	}
   427  	er.currentExecution().parameters = p
   428  	classpathPip := "/usr/lib/python35.zip;/usr/lib/python3.5;/usr/lib/python3.5/plat-x86_64-linux-gnu;/usr/lib/python3.5/lib-dynload;/home/piper/.local/lib/python3.5/site-packages;/usr/local/lib/python3.5/dist-packages;/usr/lib/python3/dist-packages;./lib"
   429  	classpathMaven := "some.jar;someother.jar"
   430  	if e == "python2" {
   431  		if p[1] == "invalid" {
   432  			return errors.New("Invalid command")
   433  		}
   434  		_, err := er.currentExecution().outWriter.Write([]byte(classpathPip))
   435  		if err != nil {
   436  			return err
   437  		}
   438  	} else if e == "mvn" {
   439  		path := strings.ReplaceAll(p[2], "-Dmdep.outputFile=", "")
   440  		err := os.WriteFile(path, []byte(classpathMaven), 0o644)
   441  		if err != nil {
   442  			return err
   443  		}
   444  	}
   445  	er.current = er.newExecution()
   446  	return nil
   447  }
   448  
   449  func TestDetermineArtifact(t *testing.T) {
   450  	t.Run("Cannot get artifact without build tool", func(t *testing.T) {
   451  		utilsMock := newFortifyTestUtilsBundle()
   452  		utilsMock.getArtifactShouldFail = true
   453  
   454  		_, err := determineArtifact(fortifyExecuteScanOptions{}, &utilsMock)
   455  		assert.EqualError(t, err, "Unable to get artifact from descriptor : build tool '' not supported")
   456  	})
   457  }
   458  
   459  func TestFailFortifyexecinPath(t *testing.T) {
   460  	t.Run("Testing if fortifyupdate in $PATH or not", func(t *testing.T) {
   461  		ff := fortifyMock{}
   462  		ctx := context.Background()
   463  		utils := newFortifyTestUtilsBundle()
   464  		influx := fortifyExecuteScanInflux{}
   465  		auditStatus := map[string]string{}
   466  		execInPath = failMockExecinPathfortifyupdate
   467  		config := fortifyExecuteScanOptions{SpotCheckMinimum: 4, MustAuditIssueGroups: "Audit All, Corporate Security Requirements", SpotAuditIssueGroups: "Spot Checks of Each Category"}
   468  		_, err := runFortifyScan(ctx, config, &ff, &utils, nil, &influx, auditStatus)
   469  		assert.EqualError(t, err, "Command not found: fortifyupdate. Please configure a supported docker image or install Fortify SCA on the system.")
   470  
   471  	})
   472  	t.Run("Testing if sourceanalyzer in $PATH or not", func(t *testing.T) {
   473  		ff := fortifyMock{}
   474  		ctx := context.Background()
   475  		utils := newFortifyTestUtilsBundle()
   476  		influx := fortifyExecuteScanInflux{}
   477  		auditStatus := map[string]string{}
   478  		execInPath = failMockExecinPathsourceanalyzer
   479  		config := fortifyExecuteScanOptions{SpotCheckMinimum: 4, MustAuditIssueGroups: "Audit All, Corporate Security Requirements", SpotAuditIssueGroups: "Spot Checks of Each Category"}
   480  		_, err := runFortifyScan(ctx, config, &ff, &utils, nil, &influx, auditStatus)
   481  		assert.EqualError(t, err, "Command not found: sourceanalyzer. Please configure a supported docker image or install Fortify SCA on the system.")
   482  
   483  	})
   484  }
   485  
   486  func TestExecutions(t *testing.T) {
   487  	type parameterTestData struct {
   488  		nameOfRun             string
   489  		config                fortifyExecuteScanOptions
   490  		expectedReportsLength int
   491  		expectedReports       []string
   492  	}
   493  
   494  	testData := []parameterTestData{
   495  		{
   496  			nameOfRun:             "golang scan and verify",
   497  			config:                fortifyExecuteScanOptions{BuildTool: "golang", BuildDescriptorFile: "go.mod"},
   498  			expectedReportsLength: 2,
   499  			expectedReports:       []string{"target/fortify-scan.*", "target/*.fpr"},
   500  		},
   501  		{
   502  			nameOfRun:             "golang verify only",
   503  			config:                fortifyExecuteScanOptions{BuildTool: "golang", BuildDescriptorFile: "go.mod", VerifyOnly: true},
   504  			expectedReportsLength: 0,
   505  		},
   506  		{
   507  			nameOfRun:             "maven scan and verify",
   508  			config:                fortifyExecuteScanOptions{BuildTool: "maven", BuildDescriptorFile: "pom.xml", UpdateRulePack: true, Reporting: true, UploadResults: true},
   509  			expectedReportsLength: 2,
   510  			expectedReports:       []string{"target/fortify-scan.*", "target/*.fpr"},
   511  		},
   512  	}
   513  
   514  	for _, data := range testData {
   515  		t.Run(data.nameOfRun, func(t *testing.T) {
   516  			ctx := context.Background()
   517  			ff := fortifyMock{}
   518  			utils := newFortifyTestUtilsBundle()
   519  			influx := fortifyExecuteScanInflux{}
   520  			auditStatus := map[string]string{}
   521  			execInPath = mockExecinPath
   522  			reports, _ := runFortifyScan(ctx, data.config, &ff, &utils, nil, &influx, auditStatus)
   523  			if len(data.expectedReports) != data.expectedReportsLength {
   524  				assert.Fail(t, fmt.Sprintf("Wrong number of reports detected, expected %v, actual %v", data.expectedReportsLength, len(data.expectedReports)))
   525  			}
   526  			if len(data.expectedReports) > 0 {
   527  				for _, expectedPath := range data.expectedReports {
   528  					found := false
   529  					for _, actualPath := range reports {
   530  						if actualPath.Target == expectedPath {
   531  							found = true
   532  						}
   533  					}
   534  					if !found {
   535  						assert.Failf(t, "Expected path %s not found", expectedPath)
   536  					}
   537  				}
   538  			}
   539  		})
   540  	}
   541  }
   542  
   543  func TestAnalyseSuspiciousExploitable(t *testing.T) {
   544  	config := fortifyExecuteScanOptions{SpotCheckMinimum: 4, MustAuditIssueGroups: "Audit All, Corporate Security Requirements", SpotAuditIssueGroups: "Spot Checks of Each Category"}
   545  	ff := fortifyMock{}
   546  	influx := fortifyExecuteScanInflux{}
   547  	name := "test"
   548  	selectorGUID := "3"
   549  	selectorName := "Analysis"
   550  	selectorEntityType := "CUSTOMTAG"
   551  	projectVersion := models.ProjectVersion{ID: 4711, Name: &name}
   552  	auditStatus := map[string]string{}
   553  	selectorSet := models.IssueFilterSelectorSet{
   554  		FilterBySet: []*models.IssueFilterSelector{
   555  			{
   556  				GUID:        selectorGUID,
   557  				DisplayName: selectorName,
   558  				EntityType:  selectorEntityType,
   559  			},
   560  		},
   561  		GroupBySet: []*models.IssueSelector{
   562  			{
   563  				GUID:        &selectorGUID,
   564  				DisplayName: &selectorName,
   565  				EntityType:  &selectorEntityType,
   566  			},
   567  		},
   568  	}
   569  	issues, groups := analyseSuspiciousExploitable(config, &ff, &projectVersion, &models.FilterSet{}, &selectorSet, &influx, auditStatus)
   570  	assert.Equal(t, 9, issues)
   571  	assert.Equal(t, 2, len(groups))
   572  
   573  	assert.Equal(t, 4, influx.fortify_data.fields.suspicious)
   574  	assert.Equal(t, 5, influx.fortify_data.fields.exploitable)
   575  	assert.Equal(t, 6, influx.fortify_data.fields.suppressed)
   576  }
   577  
   578  func TestAnalyseUnauditedIssues(t *testing.T) {
   579  	config := fortifyExecuteScanOptions{SpotCheckMinimumUnit: "number", SpotCheckMinimum: 4, MustAuditIssueGroups: "Audit All, Corporate Security Requirements", SpotAuditIssueGroups: "Spot Checks of Each Category"}
   580  	ff := fortifyMock{}
   581  	influx := fortifyExecuteScanInflux{}
   582  	name := "test"
   583  	projectVersion := models.ProjectVersion{ID: 4711, Name: &name}
   584  	auditStatus := map[string]string{}
   585  	selectorSet := models.IssueFilterSelectorSet{
   586  		FilterBySet: []*models.IssueFilterSelector{
   587  			{
   588  				GUID:        "1",
   589  				DisplayName: "Folder",
   590  				EntityType:  "ET1",
   591  				SelectorOptions: []*models.SelectorOption{
   592  					{
   593  						Value: "abcd",
   594  					},
   595  				},
   596  			},
   597  			{
   598  				GUID:        "2",
   599  				DisplayName: "Category",
   600  				EntityType:  "ET2",
   601  				SelectorOptions: []*models.SelectorOption{
   602  					{
   603  						Value: "abcd",
   604  					},
   605  				},
   606  			},
   607  			{
   608  				GUID:        "3",
   609  				DisplayName: "Analysis",
   610  				EntityType:  "ET3",
   611  				SelectorOptions: []*models.SelectorOption{
   612  					{
   613  						Value: "abcd",
   614  					},
   615  				},
   616  			},
   617  		},
   618  	}
   619  
   620  	spotChecksCountByCategory := []fortify.SpotChecksAuditCount{}
   621  	issues, groups, err := analyseUnauditedIssues(config, &ff, &projectVersion, &models.FilterSet{}, &selectorSet, &influx, auditStatus, &spotChecksCountByCategory)
   622  	assert.NoError(t, err)
   623  	assert.Equal(t, 13, issues)
   624  	assert.Equal(t, 3, len(groups))
   625  
   626  	assert.Equal(t, 15, influx.fortify_data.fields.auditAllTotal)
   627  	assert.Equal(t, 12, influx.fortify_data.fields.auditAllAudited)
   628  	assert.Equal(t, 20, influx.fortify_data.fields.corporateTotal)
   629  	assert.Equal(t, 11, influx.fortify_data.fields.corporateAudited)
   630  	assert.Equal(t, 13, influx.fortify_data.fields.spotChecksTotal)
   631  	assert.Equal(t, 11, influx.fortify_data.fields.spotChecksAudited)
   632  	assert.Equal(t, 1, influx.fortify_data.fields.spotChecksGap)
   633  	assert.Equal(t, 3, len(spotChecksCountByCategory))
   634  }
   635  
   636  func TestAnalyseUnauditedIssuesWithWrongConfig(t *testing.T) {
   637  	config := fortifyExecuteScanOptions{SpotCheckMinimumUnit: "float"}
   638  	spotChecksCountByCategory := []fortify.SpotChecksAuditCount{}
   639  	ff := fortifyMock{}
   640  	auditStatus := map[string]string{}
   641  	_, _, err := analyseUnauditedIssues(config, &ff, &models.ProjectVersion{}, &models.FilterSet{}, &models.IssueFilterSelectorSet{}, &fortifyExecuteScanInflux{}, auditStatus, &spotChecksCountByCategory)
   642  	assert.Error(t, err)
   643  	assert.Equal(t, "Invalid spotCheckMinimumUnit. Please set it as 'percentage' or 'number'.", err.Error())
   644  }
   645  
   646  func TestTriggerFortifyScan(t *testing.T) {
   647  	t.Run("maven", func(t *testing.T) {
   648  		dir := t.TempDir()
   649  		oldCWD, _ := os.Getwd()
   650  		_ = os.Chdir(dir)
   651  		// clean up tmp dir
   652  		defer func() {
   653  			_ = os.Chdir(oldCWD)
   654  		}()
   655  
   656  		utils := newFortifyTestUtilsBundle()
   657  		config := fortifyExecuteScanOptions{
   658  			BuildTool:                "maven",
   659  			AutodetectClasspath:      true,
   660  			BuildDescriptorFile:      "./pom.xml",
   661  			AdditionalScanParameters: []string{"-Dtest=property"},
   662  			Memory:                   "-Xmx4G -Xms2G",
   663  			Src:                      []string{"**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "src/main/resources/**/*", "src/main/java/**/*"},
   664  		}
   665  		triggerFortifyScan(config, &utils, "test", "testLabel", "my.group-myartifact")
   666  
   667  		assert.Equal(t, 3, utils.numExecutions)
   668  
   669  		assert.Equal(t, "mvn", utils.executions[0].executable)
   670  		assert.Equal(t, []string{"--file", "./pom.xml", "-Dmdep.outputFile=fortify-execute-scan-cp.txt", "-Dfortify", "-DincludeScope=compile", "-DskipTests", "-Dmaven.javadoc.skip=true", "--fail-at-end", "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn", "--batch-mode", "dependency:build-classpath", "package"}, utils.executions[0].parameters)
   671  
   672  		assert.Equal(t, "sourceanalyzer", utils.executions[1].executable)
   673  		assert.True(t, reflect.DeepEqual([]string{"-verbose", "-64", "-b", "test", "-Xmx4G", "-Xms2G", "-cp", "some.jar;someother.jar", "-exclude", "**/src/test/**/*", "**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "src/main/resources/**/*", "src/main/java/**/*"}, utils.executions[1].parameters) || reflect.DeepEqual([]string{"-verbose", "-64", "-b", "test", "-Xmx4G", "-Xms2G", "-cp", "some.jar;someother.jar", "-exclude", "**/src/test/**/*", "**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "src/main/resources/**/*", "src/main/java/**/*"}, utils.executions[1].parameters))
   674  
   675  		assert.Equal(t, "sourceanalyzer", utils.executions[2].executable)
   676  		assert.Equal(t, []string{"-verbose", "-64", "-b", "test", "-scan", "-Xmx4G", "-Xms2G", "-Dtest=property", "-build-label", "testLabel", "-build-project", "my.group-myartifact", "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr"}, utils.executions[2].parameters)
   677  	})
   678  
   679  	t.Run("pip", func(t *testing.T) {
   680  		dir := t.TempDir()
   681  		oldCWD, _ := os.Getwd()
   682  		_ = os.Chdir(dir)
   683  		// clean up tmp dir
   684  		defer func() {
   685  			_ = os.Chdir(oldCWD)
   686  		}()
   687  
   688  		utils := newFortifyTestUtilsBundle()
   689  		config := fortifyExecuteScanOptions{BuildTool: "pip", PythonVersion: "python2", AutodetectClasspath: true, BuildDescriptorFile: "./setup.py", PythonRequirementsFile: "./requirements.txt", PythonInstallCommand: "pip2 install --user", Memory: "-Xmx4G -Xms2G"}
   690  		triggerFortifyScan(config, &utils, "test", "testLabel", "")
   691  
   692  		assert.Equal(t, 5, utils.numExecutions)
   693  
   694  		assert.Equal(t, "python2", utils.executions[0].executable)
   695  		separator := getSeparator()
   696  		template := fmt.Sprintf("import sys;p=sys.path;p.remove('');print('%v'.join(p))", separator)
   697  		assert.Equal(t, []string{"-c", template}, utils.executions[0].parameters)
   698  
   699  		assert.Equal(t, "pip2", utils.executions[1].executable)
   700  		assert.Equal(t, []string{"install", "--user", "-r", "./requirements.txt", ""}, utils.executions[1].parameters)
   701  
   702  		assert.Equal(t, "pip2", utils.executions[2].executable)
   703  		assert.Equal(t, []string{"install", "--user"}, utils.executions[2].parameters)
   704  
   705  		assert.Equal(t, "sourceanalyzer", utils.executions[3].executable)
   706  		assert.Equal(t, []string{"-verbose", "-64", "-b", "test", "-Xmx4G", "-Xms2G", "-python-path", "/usr/lib/python35.zip;/usr/lib/python3.5;/usr/lib/python3.5/plat-x86_64-linux-gnu;/usr/lib/python3.5/lib-dynload;/home/piper/.local/lib/python3.5/site-packages;/usr/local/lib/python3.5/dist-packages;/usr/lib/python3/dist-packages;./lib", "-python-version", "2", "-exclude", fmt.Sprintf("./**/tests/**/*%s./**/setup.py", separator), "./**/*"}, utils.executions[3].parameters)
   707  
   708  		assert.Equal(t, "sourceanalyzer", utils.executions[4].executable)
   709  		assert.Equal(t, []string{"-verbose", "-64", "-b", "test", "-scan", "-Xmx4G", "-Xms2G", "-build-label", "testLabel", "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr"}, utils.executions[4].parameters)
   710  	})
   711  
   712  	t.Run("invalid buildTool", func(t *testing.T) {
   713  		dir := t.TempDir()
   714  		oldCWD, _ := os.Getwd()
   715  		_ = os.Chdir(dir)
   716  		// clean up tmp dir
   717  		defer func() {
   718  			_ = os.Chdir(oldCWD)
   719  		}()
   720  
   721  		utils := newFortifyTestUtilsBundle()
   722  		config := fortifyExecuteScanOptions{
   723  			BuildTool:           "docker",
   724  			AutodetectClasspath: true,
   725  		}
   726  		err := triggerFortifyScan(config, &utils, "test", "testLabel", "my.group-myartifact")
   727  
   728  		assert.Error(t, err)
   729  		assert.Equal(t, "buildTool 'docker' is not supported by this step", err.Error())
   730  	})
   731  }
   732  
   733  func TestGetMinSpotChecksPerCategory(t *testing.T) {
   734  	testExpectedGetMinSpotChecksPerCategory := func(spotChecksMinUnit string, spotChecksMax int, spotChecksMin int, issuesPerCategory int, spotChecksMinCalculatedExpected int) {
   735  		testName := fmt.Sprintf("Test GetMinSpotChecksPerCategory for SpotCheckMinimumUnit: %v, SpotCheckMaximum: %v, SpotCheckMinimum: %v, issuesPerCategory: %v", spotChecksMinUnit, spotChecksMax, spotChecksMin, issuesPerCategory)
   736  		t.Run(testName, func(t *testing.T) {
   737  			config := fortifyExecuteScanOptions{SpotCheckMinimumUnit: spotChecksMinUnit, SpotCheckMaximum: spotChecksMax, SpotCheckMinimum: spotChecksMin}
   738  			spotCheckMin := getMinSpotChecksPerCategory(config, issuesPerCategory)
   739  			assert.Equal(t, spotChecksMinCalculatedExpected, spotCheckMin)
   740  		})
   741  	}
   742  
   743  	testExpectedGetMinSpotChecksPerCategory("percentage", 0, 1, 10, 1)
   744  	testExpectedGetMinSpotChecksPerCategory("percentage", 10, 10, 3, 1)
   745  	testExpectedGetMinSpotChecksPerCategory("percentage", 10, 10, 8, 1)
   746  	testExpectedGetMinSpotChecksPerCategory("percentage", 10, 10, 10, 1)
   747  	testExpectedGetMinSpotChecksPerCategory("percentage", 10, 10, 24, 3)
   748  	testExpectedGetMinSpotChecksPerCategory("percentage", 10, 10, 26, 3)
   749  	testExpectedGetMinSpotChecksPerCategory("percentage", 10, 10, 100, 10)
   750  	testExpectedGetMinSpotChecksPerCategory("percentage", 10, 10, 200, 10)
   751  	testExpectedGetMinSpotChecksPerCategory("percentage", 10, 50, 10, 5)
   752  	testExpectedGetMinSpotChecksPerCategory("percentage", 0, 50, 100, 50)
   753  	testExpectedGetMinSpotChecksPerCategory("percentage", -10, 50, 100, 50)
   754  
   755  	testExpectedGetMinSpotChecksPerCategory("number", 0, 1, 10, 1)
   756  	testExpectedGetMinSpotChecksPerCategory("number", 5, 10, 100, 5)
   757  }
   758  
   759  func TestGenerateAndDownloadQGateReport(t *testing.T) {
   760  	ffMock := fortifyMock{Successive: false}
   761  	config := fortifyExecuteScanOptions{ReportTemplateID: 18, ReportType: "PDF"}
   762  	name := "test"
   763  	projectVersion := models.ProjectVersion{ID: 4711, Name: &name}
   764  	project := models.Project{ID: 815, Name: &name}
   765  	projectVersion.Project = &project
   766  
   767  	t.Run("success", func(t *testing.T) {
   768  		data, err := generateAndDownloadQGateReport(config, &ffMock, &project, &projectVersion)
   769  		assert.NoError(t, err)
   770  		assert.Equal(t, []byte("abcd"), data)
   771  	})
   772  }
   773  
   774  var (
   775  	defaultPollingDelay   = 10 * time.Second
   776  	defaultPollingTimeout = 0 * time.Minute
   777  )
   778  
   779  func verifyScanResultsFinishedUploadingDefaults(config fortifyExecuteScanOptions, sys fortify.System, projectVersionID int64) error {
   780  	return verifyScanResultsFinishedUploading(config, sys, projectVersionID, "", &models.FilterSet{},
   781  		defaultPollingDelay, defaultPollingTimeout)
   782  }
   783  
   784  func TestVerifyScanResultsFinishedUploading(t *testing.T) {
   785  	t.Parallel()
   786  
   787  	t.Run("error no recent upload detected", func(t *testing.T) {
   788  		ffMock := fortifyMock{}
   789  		config := fortifyExecuteScanOptions{DeltaMinutes: -1}
   790  		err := verifyScanResultsFinishedUploadingDefaults(config, &ffMock, 4711)
   791  		assert.EqualError(t, err, "no recent upload detected on Project Version")
   792  	})
   793  
   794  	config := fortifyExecuteScanOptions{DeltaMinutes: 20}
   795  	t.Run("success", func(t *testing.T) {
   796  		ffMock := fortifyMock{}
   797  		err := verifyScanResultsFinishedUploadingDefaults(config, &ffMock, 4711)
   798  		assert.NoError(t, err)
   799  	})
   800  
   801  	t.Run("error processing", func(t *testing.T) {
   802  		ffMock := fortifyMock{}
   803  		err := verifyScanResultsFinishedUploadingDefaults(config, &ffMock, 4712)
   804  		assert.EqualError(t, err, "There are artifacts that failed processing for Project Version 4712\n/html/ssc/index.jsp#!/version/4712/artifacts?filterSet=")
   805  	})
   806  
   807  	t.Run("error required auth", func(t *testing.T) {
   808  		ffMock := fortifyMock{}
   809  		err := verifyScanResultsFinishedUploadingDefaults(config, &ffMock, 4713)
   810  		assert.EqualError(t, err, "There are artifacts that require manual approval for Project Version 4713, please visit Fortify SSC and approve them for processing\n/html/ssc/index.jsp#!/version/4713/artifacts?filterSet=")
   811  	})
   812  
   813  	t.Run("error polling timeout", func(t *testing.T) {
   814  		ffMock := fortifyMock{}
   815  		err := verifyScanResultsFinishedUploadingDefaults(config, &ffMock, 4714)
   816  		assert.EqualError(t, err, "terminating after 0s since artifact for Project Version 4714 is still in status PROCESSING")
   817  	})
   818  
   819  	t.Run("success build label", func(t *testing.T) {
   820  		ffMock := fortifyMock{}
   821  		err := verifyScanResultsFinishedUploading(config, &ffMock, 4715, "/commit/test", &models.FilterSet{},
   822  			10*time.Second, time.Duration(config.PollingMinutes)*time.Minute)
   823  		assert.NoError(t, err)
   824  	})
   825  
   826  	t.Run("failure after polling", func(t *testing.T) {
   827  		config := fortifyExecuteScanOptions{DeltaMinutes: 1}
   828  		ffMock := fortifyMock{}
   829  		const pollingDelay = 1 * time.Second
   830  		const timeout = 1 * time.Second
   831  		err := verifyScanResultsFinishedUploading(config, &ffMock, 4716, "", &models.FilterSet{}, pollingDelay, timeout)
   832  		assert.EqualError(t, err, "terminating after 1s since artifact for Project Version 4716 is still in status PROCESSING")
   833  	})
   834  
   835  	t.Run("success after polling", func(t *testing.T) {
   836  		config := fortifyExecuteScanOptions{DeltaMinutes: 1}
   837  		ffMock := fortifyMock{}
   838  		const pollingDelay = 500 * time.Millisecond
   839  		const timeout = 1 * time.Second
   840  		err := verifyScanResultsFinishedUploading(config, &ffMock, 4716, "", &models.FilterSet{}, pollingDelay, timeout)
   841  		assert.NoError(t, err)
   842  	})
   843  
   844  	t.Run("error no artifacts", func(t *testing.T) {
   845  		ffMock := fortifyMock{}
   846  		err := verifyScanResultsFinishedUploadingDefaults(config, &ffMock, 4717)
   847  		assert.EqualError(t, err, "no uploaded artifacts for assessment detected for project version with ID 4717")
   848  	})
   849  
   850  	t.Run("warn old artifacts have errors", func(t *testing.T) {
   851  		ffMock := fortifyMock{}
   852  
   853  		logBuffer := new(bytes.Buffer)
   854  		logOutput := log.Entry().Logger.Out
   855  		log.Entry().Logger.Out = logBuffer
   856  		defer func() { log.Entry().Logger.Out = logOutput }()
   857  
   858  		err := verifyScanResultsFinishedUploadingDefaults(config, &ffMock, 4718)
   859  		assert.NoError(t, err)
   860  		assert.Contains(t, logBuffer.String(), "Previous uploads detected that failed processing")
   861  	})
   862  }
   863  
   864  func TestCalculateTimeDifferenceToLastUpload(t *testing.T) {
   865  	diffSeconds := calculateTimeDifferenceToLastUpload(models.Iso8601MilliDateTime(time.Now().UTC()), 1234)
   866  
   867  	assert.Equal(t, true, diffSeconds < 1)
   868  }
   869  
   870  func TestExecuteTemplatedCommand(t *testing.T) {
   871  	utils := newFortifyTestUtilsBundle()
   872  	template := []string{"{{.Executable}}", "-c", "{{.Param}}"}
   873  	context := map[string]string{"Executable": "test.cmd", "Param": "abcd"}
   874  	executeTemplatedCommand(&utils, template, context)
   875  
   876  	assert.Equal(t, "test.cmd", utils.executions[0].executable)
   877  	assert.Equal(t, []string{"-c", "abcd"}, utils.executions[0].parameters)
   878  }
   879  
   880  func TestDeterminePullRequestMerge(t *testing.T) {
   881  	config := fortifyExecuteScanOptions{CommitMessage: "Merge pull request #2462 from branch f-test", PullRequestMessageRegex: `(?m).*Merge pull request #(\d+) from.*`, PullRequestMessageRegexGroup: 1}
   882  
   883  	t.Run("success", func(t *testing.T) {
   884  		match, authorString := determinePullRequestMerge(config)
   885  		assert.Equal(t, "2462", match, "Expected different result")
   886  		assert.Equal(t, "", authorString, "Expected different result")
   887  	})
   888  
   889  	t.Run("no match", func(t *testing.T) {
   890  		config.CommitMessage = "Some test commit"
   891  		match, authorString := determinePullRequestMerge(config)
   892  		assert.Equal(t, "0", match, "Expected different result")
   893  		assert.Equal(t, "", authorString, "Expected different result")
   894  	})
   895  }
   896  
   897  func TestDeterminePullRequestMergeGithub(t *testing.T) {
   898  	prServiceMock := pullRequestServiceMock{}
   899  
   900  	t.Run("success", func(t *testing.T) {
   901  		match, authorString, err := determinePullRequestMergeGithub(nil, fortifyExecuteScanOptions{Owner: "A"}, prServiceMock)
   902  		assert.NoError(t, err)
   903  		assert.Equal(t, "17", match, "Expected different result")
   904  		assert.Equal(t, author, authorString, "Expected different result")
   905  	})
   906  
   907  	t.Run("no match", func(t *testing.T) {
   908  		match, authorString, err := determinePullRequestMergeGithub(nil, fortifyExecuteScanOptions{Owner: "B"}, prServiceMock)
   909  		assert.NoError(t, err)
   910  		assert.Equal(t, "0", match, "Expected different result")
   911  		assert.Equal(t, "", authorString, "Expected different result")
   912  	})
   913  
   914  	t.Run("error", func(t *testing.T) {
   915  		match, authorString, err := determinePullRequestMergeGithub(nil, fortifyExecuteScanOptions{Owner: "E"}, prServiceMock)
   916  		assert.EqualError(t, err, "Test error")
   917  		assert.Equal(t, "0", match, "Expected different result")
   918  		assert.Equal(t, "", authorString, "Expected different result")
   919  	})
   920  }
   921  
   922  func TestTranslateProject(t *testing.T) {
   923  	t.Run("python", func(t *testing.T) {
   924  		utils := newFortifyTestUtilsBundle()
   925  		config := fortifyExecuteScanOptions{BuildTool: "pip", Memory: "-Xmx4G", Translate: `[{"pythonPath":"./some/path","src":"./**/*","exclude":"./tests/**/*"}]`}
   926  		translateProject(&config, &utils, "/commit/7267658798797", "")
   927  		assert.Equal(t, "sourceanalyzer", utils.executions[0].executable, "Expected different executable")
   928  		assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-Xmx4G", "-python-path", "./some/path", "-exclude", "./tests/**/*", "./**/*"}, utils.executions[0].parameters, "Expected different parameters")
   929  	})
   930  
   931  	t.Run("asp", func(t *testing.T) {
   932  		utils := newFortifyTestUtilsBundle()
   933  		config := fortifyExecuteScanOptions{BuildTool: "windows", Memory: "-Xmx6G", Translate: `[{"aspnetcore":"true","dotNetCoreVersion":"3.5","exclude":"./tests/**/*","libDirs":"tmp/","src":"./**/*"}]`}
   934  		translateProject(&config, &utils, "/commit/7267658798797", "")
   935  		assert.Equal(t, "sourceanalyzer", utils.executions[0].executable, "Expected different executable")
   936  		assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-Xmx6G", "-aspnetcore", "-dotnet-core-version", "3.5", "-libdirs", "tmp/", "-exclude", "./tests/**/*", "./**/*"}, utils.executions[0].parameters, "Expected different parameters")
   937  	})
   938  
   939  	t.Run("java", func(t *testing.T) {
   940  		utils := newFortifyTestUtilsBundle()
   941  		config := fortifyExecuteScanOptions{BuildTool: "maven", Memory: "-Xmx2G", Translate: `[{"classpath":"./classes/*.jar","extdirs":"tmp/","jdk":"1.8.0-21","source":"1.8","sourcepath":"src/ext/","src":"./**/*"}]`}
   942  		translateProject(&config, &utils, "/commit/7267658798797", "")
   943  		assert.Equal(t, "sourceanalyzer", utils.executions[0].executable, "Expected different executable")
   944  		assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-Xmx2G", "-cp", "./classes/*.jar", "-extdirs", "tmp/", "-source", "1.8", "-jdk", "1.8.0-21", "-sourcepath", "src/ext/", "./**/*"}, utils.executions[0].parameters, "Expected different parameters")
   945  	})
   946  
   947  	t.Run("auto classpath", func(t *testing.T) {
   948  		utils := newFortifyTestUtilsBundle()
   949  		config := fortifyExecuteScanOptions{BuildTool: "maven", Memory: "-Xmx2G", Translate: `[{"classpath":"./classes/*.jar", "extdirs":"tmp/","jdk":"1.8.0-21","source":"1.8","sourcepath":"src/ext/","src":"./**/*"}]`}
   950  		translateProject(&config, &utils, "/commit/7267658798797", "./WEB-INF/lib/*.jar")
   951  		assert.Equal(t, "sourceanalyzer", utils.executions[0].executable, "Expected different executable")
   952  		assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-Xmx2G", "-cp", "./WEB-INF/lib/*.jar", "-extdirs", "tmp/", "-source", "1.8", "-jdk", "1.8.0-21", "-sourcepath", "src/ext/", "./**/*"}, utils.executions[0].parameters, "Expected different parameters")
   953  	})
   954  
   955  	t.Run("failure propagated", func(t *testing.T) {
   956  		utils := newFortifyTestUtilsBundle()
   957  		config := fortifyExecuteScanOptions{BuildTool: "maven", Memory: "-Xmx2G", Translate: `[{"classpath":"./classes/*.jar", "extdirs":"tmp/","jdk":"1.8.0-21","source":"1.8","sourcepath":"src/ext/","src":"./**/*"}]`}
   958  		err := translateProject(&config, &utils, "--failTranslate", "./WEB-INF/lib/*.jar")
   959  		assert.Error(t, err)
   960  		assert.Equal(t, "failed to execute sourceanalyzer translate command with options [-verbose -64 -b --failTranslate -Xmx2G -cp ./WEB-INF/lib/*.jar -extdirs tmp/ -source 1.8 -jdk 1.8.0-21 -sourcepath src/ext/ ./**/*]: Translate failed", err.Error())
   961  	})
   962  }
   963  
   964  func TestScanProject(t *testing.T) {
   965  	config := fortifyExecuteScanOptions{Memory: "-Xmx4G"}
   966  
   967  	t.Run("normal", func(t *testing.T) {
   968  		utils := newFortifyTestUtilsBundle()
   969  		scanProject(&config, &utils, "/commit/7267658798797", "label", "my.group-myartifact")
   970  		assert.Equal(t, "sourceanalyzer", utils.executions[0].executable, "Expected different executable")
   971  		assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-scan", "-Xmx4G", "-build-label", "label", "-build-project", "my.group-myartifact", "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr"}, utils.executions[0].parameters, "Expected different parameters")
   972  	})
   973  
   974  	t.Run("quick", func(t *testing.T) {
   975  		utils := newFortifyTestUtilsBundle()
   976  		config.QuickScan = true
   977  		scanProject(&config, &utils, "/commit/7267658798797", "", "")
   978  		assert.Equal(t, "sourceanalyzer", utils.executions[0].executable, "Expected different executable")
   979  		assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-scan", "-Xmx4G", "-quick", "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr"}, utils.executions[0].parameters, "Expected different parameters")
   980  	})
   981  }
   982  
   983  func TestAutoresolveClasspath(t *testing.T) {
   984  	t.Run("success pip", func(t *testing.T) {
   985  		utils := newFortifyTestUtilsBundle()
   986  		dir := t.TempDir()
   987  		file := filepath.Join(dir, "cp.txt")
   988  
   989  		result, err := autoresolvePipClasspath("python2", []string{"-c", "import sys;p=sys.path;p.remove('');print(';'.join(p))"}, file, &utils)
   990  		assert.NoError(t, err)
   991  		assert.Equal(t, "python2", utils.executions[0].executable, "Expected different executable")
   992  		assert.Equal(t, []string{"-c", "import sys;p=sys.path;p.remove('');print(';'.join(p))"}, utils.executions[0].parameters, "Expected different parameters")
   993  		assert.Equal(t, "/usr/lib/python35.zip;/usr/lib/python3.5;/usr/lib/python3.5/plat-x86_64-linux-gnu;/usr/lib/python3.5/lib-dynload;/home/piper/.local/lib/python3.5/site-packages;/usr/local/lib/python3.5/dist-packages;/usr/lib/python3/dist-packages;./lib", result, "Expected different result")
   994  	})
   995  
   996  	t.Run("error pip file", func(t *testing.T) {
   997  		utils := newFortifyTestUtilsBundle()
   998  
   999  		_, err := autoresolvePipClasspath("python2", []string{"-c", "import sys;p=sys.path;p.remove('');print(';'.join(p))"}, "../.", &utils)
  1000  		assert.Error(t, err)
  1001  	})
  1002  
  1003  	t.Run("error pip command", func(t *testing.T) {
  1004  		utils := newFortifyTestUtilsBundle()
  1005  		dir := t.TempDir()
  1006  		file := filepath.Join(dir, "cp.txt")
  1007  
  1008  		_, err := autoresolvePipClasspath("python2", []string{"-c", "invalid"}, file, &utils)
  1009  		assert.Error(t, err)
  1010  		assert.Equal(t, "failed to run classpath autodetection command python2 with parameters [-c invalid]: Invalid command", err.Error())
  1011  	})
  1012  
  1013  	t.Run("success maven", func(t *testing.T) {
  1014  		utils := newFortifyTestUtilsBundle()
  1015  		dir := t.TempDir()
  1016  		file := filepath.Join(dir, "cp.txt")
  1017  
  1018  		result, err := autoresolveMavenClasspath(fortifyExecuteScanOptions{BuildDescriptorFile: "pom.xml"}, file, &utils)
  1019  		assert.NoError(t, err)
  1020  		assert.Equal(t, "mvn", utils.executions[0].executable, "Expected different executable")
  1021  		assert.Equal(t, []string{"--file", "pom.xml", fmt.Sprintf("-Dmdep.outputFile=%v", file), "-Dfortify", "-DincludeScope=compile", "-DskipTests", "-Dmaven.javadoc.skip=true", "--fail-at-end", "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn", "--batch-mode", "dependency:build-classpath", "package"}, utils.executions[0].parameters, "Expected different parameters")
  1022  		assert.Equal(t, "some.jar;someother.jar", result, "Expected different result")
  1023  	})
  1024  }
  1025  
  1026  func TestPopulateMavenTranslate(t *testing.T) {
  1027  	t.Run("src without translate", func(t *testing.T) {
  1028  		config := fortifyExecuteScanOptions{Src: []string{"./**/*"}}
  1029  		translate, err := populateMavenGradleTranslate(&config, "")
  1030  		assert.NoError(t, err)
  1031  		assert.Equal(t, `[{"classpath":"","exclude":"**/src/test/**/*","src":"./**/*"}]`, translate)
  1032  	})
  1033  
  1034  	t.Run("exclude without translate", func(t *testing.T) {
  1035  		config := fortifyExecuteScanOptions{Exclude: []string{"./**/*"}}
  1036  		translate, err := populateMavenGradleTranslate(&config, "")
  1037  		assert.NoError(t, err)
  1038  		assert.Equal(t, `[{"classpath":"","exclude":"./**/*","src":"**/*.xml:**/*.html:**/*.jsp:**/*.js:**/src/main/resources/**/*:**/src/main/java/**/*:**/src/gen/java/cds/**/*:**/target/main/java/**/*:**/target/main/resources/**/*:**/target/generated-sources/**/*"}]`, translate)
  1039  	})
  1040  
  1041  	t.Run("with translate", func(t *testing.T) {
  1042  		config := fortifyExecuteScanOptions{Translate: `[{"classpath":""}]`, Src: []string{"./**/*"}, Exclude: []string{"./**/*"}}
  1043  		translate, err := populateMavenGradleTranslate(&config, "ignored/path")
  1044  		assert.NoError(t, err)
  1045  		assert.Equal(t, `[{"classpath":""}]`, translate)
  1046  	})
  1047  }
  1048  
  1049  func TestPopulatePipTranslate(t *testing.T) {
  1050  	t.Run("PythonAdditionalPath without translate", func(t *testing.T) {
  1051  		config := fortifyExecuteScanOptions{PythonVersion: "python2", PythonAdditionalPath: []string{"./lib", "."}}
  1052  		translate, err := populatePipTranslate(&config, "")
  1053  		separator := getSeparator()
  1054  		expected := fmt.Sprintf(`[{"exclude":"./**/tests/**/*%v./**/setup.py","pythonPath":"%v./lib%v.","pythonVersion":"2","src":"./**/*"}]`,
  1055  			separator, separator, separator)
  1056  		assert.NoError(t, err)
  1057  		assert.Equal(t, expected, translate)
  1058  	})
  1059  
  1060  	t.Run("Invalid python version", func(t *testing.T) {
  1061  		config := fortifyExecuteScanOptions{PythonVersion: "python4", PythonAdditionalPath: []string{"./lib", "."}}
  1062  		_, err := populatePipTranslate(&config, "")
  1063  		assert.Error(t, err)
  1064  	})
  1065  
  1066  	t.Run("Src without translate", func(t *testing.T) {
  1067  		config := fortifyExecuteScanOptions{PythonVersion: "python3", Src: []string{"./**/*.py"}}
  1068  		translate, err := populatePipTranslate(&config, "")
  1069  		separator := getSeparator()
  1070  		expected := fmt.Sprintf(
  1071  			`[{"exclude":"./**/tests/**/*%v./**/setup.py","pythonPath":"%v","pythonVersion":"3","src":"./**/*.py"}]`,
  1072  			separator, separator)
  1073  		assert.NoError(t, err)
  1074  		assert.Equal(t, expected, translate)
  1075  	})
  1076  
  1077  	t.Run("Exclude without translate", func(t *testing.T) {
  1078  		config := fortifyExecuteScanOptions{PythonVersion: "python3", Exclude: []string{"./**/tests/**/*"}}
  1079  		translate, err := populatePipTranslate(&config, "")
  1080  		separator := getSeparator()
  1081  		expected := fmt.Sprintf(
  1082  			`[{"exclude":"./**/tests/**/*","pythonPath":"%v","pythonVersion":"3","src":"./**/*"}]`,
  1083  			separator)
  1084  		assert.NoError(t, err)
  1085  		assert.Equal(t, expected, translate)
  1086  	})
  1087  
  1088  	t.Run("with translate", func(t *testing.T) {
  1089  		config := fortifyExecuteScanOptions{
  1090  			Translate:            `[{"pythonPath":""}]`,
  1091  			Src:                  []string{"./**/*"},
  1092  			PythonAdditionalPath: []string{"./lib", "."},
  1093  		}
  1094  		translate, err := populatePipTranslate(&config, "ignored/path")
  1095  		assert.NoError(t, err)
  1096  		assert.Equal(t, `[{"pythonPath":""}]`, translate, "Expected different parameters")
  1097  	})
  1098  }
  1099  
  1100  func TestRemoveDuplicates(t *testing.T) {
  1101  	testData := []struct {
  1102  		name      string
  1103  		input     string
  1104  		expected  string
  1105  		separator string
  1106  	}{
  1107  		{"empty", "", "", "x"},
  1108  		{"no duplicates", ":a::b::", "a:b", ":"},
  1109  		{"duplicates", "::a:b:a:b::a", "a:b", ":"},
  1110  		{"long separator", "..a.b....ab..a.b", "a.b..ab", ".."},
  1111  		{"no separator", "abc", "abc", ""},
  1112  	}
  1113  	for _, data := range testData {
  1114  		t.Run(data.name, func(t *testing.T) {
  1115  			assert.Equal(t, data.expected, removeDuplicates(data.input, data.separator))
  1116  		})
  1117  	}
  1118  }
  1119  
  1120  func toFortifyTime(time time.Time) models.Iso8601MilliDateTime {
  1121  	return models.Iso8601MilliDateTime(time.UTC())
  1122  }
  1123  
  1124  func TestGetProxyParams(t *testing.T) {
  1125  	t.Run("Valid Proxy URL", func(t *testing.T) {
  1126  		proxyPort, proxyHost := getProxyParams("http://testproxy.com:8080")
  1127  		assert.Equal(t, "8080", proxyPort)
  1128  		assert.Equal(t, "testproxy.com", proxyHost)
  1129  	})
  1130  
  1131  	t.Run("Invalid Proxy URL", func(t *testing.T) {
  1132  		proxyPort, proxyHost := getProxyParams("testproxy.com:8080")
  1133  		assert.Equal(t, "", proxyPort)
  1134  		assert.Equal(t, "", proxyHost)
  1135  	})
  1136  }