2020-05-25 19:48:59 +02:00
package cmd
import (
2020-08-11 15:29:00 +02:00
"bytes"
2020-05-25 19:48:59 +02:00
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
2020-09-18 08:19:34 +02:00
"github.com/SAP/jenkins-library/pkg/command"
2020-09-14 12:05:12 +02:00
"github.com/SAP/jenkins-library/pkg/fortify"
"github.com/SAP/jenkins-library/pkg/log"
2020-09-18 08:19:34 +02:00
"github.com/SAP/jenkins-library/pkg/versioning"
2020-09-14 12:05:12 +02:00
"github.com/google/go-github/v32/github"
2020-05-25 19:48:59 +02:00
"github.com/stretchr/testify/assert"
"github.com/piper-validation/fortify-client-go/models"
)
2020-09-18 08:19:34 +02:00
type artifactMock struct {
Coordinates coordinatesMock
}
type coordinatesMock struct {
GroupID string
ArtifactID string
Version string
}
func newCoordinatesMock ( ) coordinatesMock {
return coordinatesMock {
GroupID : "a" ,
ArtifactID : "b" ,
Version : "1.0.0" ,
}
}
func ( a artifactMock ) VersioningScheme ( ) string {
return "full"
}
func ( a artifactMock ) GetVersion ( ) ( string , error ) {
return a . Coordinates . Version , nil
}
func ( a artifactMock ) SetVersion ( v string ) error {
a . Coordinates . Version = v
return nil
}
func ( a artifactMock ) GetCoordinates ( ) ( versioning . Coordinates , error ) {
return a . Coordinates , nil
}
2020-05-25 19:48:59 +02:00
type fortifyMock struct {
2020-08-11 15:29:00 +02:00
Successive bool
getArtifactsOfProjectVersionIdx int
getArtifactsOfProjectVersionTime time . Time
2020-05-25 19:48:59 +02:00
}
func ( f * fortifyMock ) GetProjectByName ( name string , autoCreate bool , projectVersion string ) ( * models . Project , error ) {
2020-09-18 08:19:34 +02:00
return & models . Project { Name : & name , ID : 64 } , nil
2020-05-25 19:48:59 +02:00
}
func ( f * fortifyMock ) GetProjectVersionDetailsByProjectIDAndVersionName ( id int64 , name string , autoCreate bool , projectName string ) ( * models . ProjectVersion , error ) {
return & models . ProjectVersion { ID : id , Name : & name , Project : & models . Project { Name : & projectName } } , nil
}
func ( f * fortifyMock ) GetProjectVersionAttributesByProjectVersionID ( id int64 ) ( [ ] * models . Attribute , error ) {
return [ ] * models . Attribute { } , nil
}
func ( f * fortifyMock ) SetProjectVersionAttributesByProjectVersionID ( id int64 , attributes [ ] * models . Attribute ) ( [ ] * models . Attribute , error ) {
return attributes , nil
}
func ( f * fortifyMock ) CreateProjectVersionIfNotExist ( projectName , projectVersionName , description string ) ( * models . ProjectVersion , error ) {
return & models . ProjectVersion { ID : 4711 , Name : & projectVersionName , Project : & models . Project { Name : & projectName } } , nil
}
func ( f * fortifyMock ) LookupOrCreateProjectVersionDetailsForPullRequest ( projectID int64 , masterProjectVersion * models . ProjectVersion , pullRequestName string ) ( * models . ProjectVersion , error ) {
return & models . ProjectVersion { ID : 4712 , Name : & pullRequestName , Project : masterProjectVersion . Project } , nil
}
func ( f * fortifyMock ) CreateProjectVersion ( version * models . ProjectVersion ) ( * models . ProjectVersion , error ) {
return version , nil
}
func ( f * fortifyMock ) ProjectVersionCopyFromPartial ( sourceID , targetID int64 ) error {
return nil
}
func ( f * fortifyMock ) ProjectVersionCopyCurrentState ( sourceID , targetID int64 ) error {
return nil
}
func ( f * fortifyMock ) ProjectVersionCopyPermissions ( sourceID , targetID int64 ) error {
return nil
}
func ( f * fortifyMock ) CommitProjectVersion ( id int64 ) ( * models . ProjectVersion , error ) {
name := "Committed"
return & models . ProjectVersion { ID : id , Name : & name } , nil
}
func ( f * fortifyMock ) MergeProjectVersionStateOfPRIntoMaster ( downloadEndpoint , uploadEndpoint string , masterProjectID , masterProjectVersionID int64 , pullRequestName string ) error {
return nil
}
func ( f * fortifyMock ) GetArtifactsOfProjectVersion ( id int64 ) ( [ ] * models . Artifact , error ) {
2020-08-11 15:29:00 +02:00
switch id {
case 4711 :
return [ ] * models . Artifact { {
Status : "PROCESSED" ,
UploadDate : toFortifyTime ( time . Now ( ) ) ,
} } , nil
case 4712 :
return [ ] * models . Artifact { {
Status : "ERROR_PROCESSING" ,
UploadDate : toFortifyTime ( time . Now ( ) ) ,
} } , nil
case 4713 :
return [ ] * models . Artifact { {
Status : "REQUIRE_AUTH" ,
UploadDate : toFortifyTime ( time . Now ( ) ) ,
} } , nil
case 4714 :
return [ ] * models . Artifact { {
Status : "PROCESSING" ,
UploadDate : toFortifyTime ( time . Now ( ) ) ,
} } , nil
case 4715 :
return [ ] * models . Artifact { {
Status : "PROCESSED" ,
Embed : & models . EmbeddedScans {
Scans : [ ] * models . Scan { { BuildLabel : "/commit/test" } } ,
} ,
UploadDate : toFortifyTime ( time . Now ( ) ) ,
} } , nil
case 4716 :
var status string
if f . getArtifactsOfProjectVersionIdx == 0 {
f . getArtifactsOfProjectVersionTime = time . Now ( ) . Add ( - 2 * time . Minute )
}
if f . getArtifactsOfProjectVersionIdx < 2 {
status = "PROCESSING"
} else {
f . getArtifactsOfProjectVersionTime = time . Now ( )
status = "PROCESSED"
}
f . getArtifactsOfProjectVersionIdx ++
return [ ] * models . Artifact { {
Status : status ,
UploadDate : toFortifyTime ( f . getArtifactsOfProjectVersionTime ) ,
} } , nil
case 4718 :
return [ ] * models . Artifact {
{
Status : "PROCESSED" ,
UploadDate : toFortifyTime ( time . Now ( ) ) ,
} ,
{
Status : "ERROR_PROCESSING" ,
UploadDate : toFortifyTime ( time . Now ( ) . Add ( - 2 * time . Minute ) ) ,
} ,
} , nil
default :
return [ ] * models . Artifact { } , nil
2020-05-25 19:48:59 +02:00
}
}
func ( f * fortifyMock ) GetFilterSetOfProjectVersionByTitle ( id int64 , title string ) ( * models . FilterSet , error ) {
return & models . FilterSet { } , nil
}
func ( f * fortifyMock ) GetIssueFilterSelectorOfProjectVersionByName ( id int64 , names [ ] string , options [ ] string ) ( * models . IssueFilterSelectorSet , error ) {
return & models . IssueFilterSelectorSet { } , nil
}
func ( f * fortifyMock ) GetFilterSetByDisplayName ( issueFilterSelectorSet * models . IssueFilterSelectorSet , name string ) * models . IssueFilterSelector {
if issueFilterSelectorSet . FilterBySet != nil {
for _ , filter := range issueFilterSelectorSet . FilterBySet {
if filter . DisplayName == name {
return filter
}
}
}
2020-09-18 08:19:34 +02:00
return & models . IssueFilterSelector { DisplayName : name }
2020-05-25 19:48:59 +02:00
}
func ( f * fortifyMock ) GetProjectIssuesByIDAndFilterSetGroupedBySelector ( id int64 , filter , filterSetGUID string , issueFilterSelectorSet * models . IssueFilterSelectorSet ) ( [ ] * models . ProjectVersionIssueGroup , error ) {
if filter == "ET1:abcd" {
group := "HTTP Verb tampering"
total := int32 ( 4 )
audited := int32 ( 3 )
group2 := "Password in code"
total2 := int32 ( 4 )
audited2 := int32 ( 4 )
group3 := "Memory leak"
total3 := int32 ( 5 )
audited3 := int32 ( 4 )
return [ ] * models . ProjectVersionIssueGroup {
{ ID : & group , TotalCount : & total , AuditedCount : & audited } ,
{ ID : & group2 , TotalCount : & total2 , AuditedCount : & audited2 } ,
{ ID : & group3 , TotalCount : & total3 , AuditedCount : & audited3 } ,
} , nil
}
2020-09-18 08:19:34 +02:00
if issueFilterSelectorSet != nil && issueFilterSelectorSet . FilterBySet != nil && len ( issueFilterSelectorSet . FilterBySet ) > 0 && issueFilterSelectorSet . FilterBySet [ 0 ] . GUID == "3" {
2020-05-25 19:48:59 +02:00
group := "3"
total := int32 ( 4 )
audited := int32 ( 0 )
group2 := "4"
total2 := int32 ( 5 )
audited2 := int32 ( 0 )
return [ ] * models . ProjectVersionIssueGroup {
{ ID : & group , TotalCount : & total , AuditedCount : & audited } ,
{ ID : & group2 , TotalCount : & total2 , AuditedCount : & audited2 } ,
} , nil
}
group := "Audit All"
total := int32 ( 15 )
audited := int32 ( 12 )
group2 := "Corporate Security Requirements"
total2 := int32 ( 20 )
audited2 := int32 ( 11 )
group3 := "Spot Checks of Each Category"
total3 := int32 ( 5 )
audited3 := int32 ( 4 )
return [ ] * models . ProjectVersionIssueGroup {
{ ID : & group , TotalCount : & total , AuditedCount : & audited } ,
{ ID : & group2 , TotalCount : & total2 , AuditedCount : & audited2 } ,
{ ID : & group3 , TotalCount : & total3 , AuditedCount : & audited3 } ,
} , nil
}
func ( f * fortifyMock ) ReduceIssueFilterSelectorSet ( issueFilterSelectorSet * models . IssueFilterSelectorSet , names [ ] string , options [ ] string ) * models . IssueFilterSelectorSet {
return issueFilterSelectorSet
}
func ( f * fortifyMock ) GetIssueStatisticsOfProjectVersion ( id int64 ) ( [ ] * models . IssueStatistics , error ) {
suppressed := int32 ( 6 )
return [ ] * models . IssueStatistics { { SuppressedCount : & suppressed } } , nil
}
func ( f * fortifyMock ) GenerateQGateReport ( projectID , projectVersionID , reportTemplateID int64 , projectName , projectVersionName , reportFormat string ) ( * models . SavedReport , error ) {
if ! f . Successive {
f . Successive = true
return & models . SavedReport { Status : "Processing" } , nil
}
f . Successive = false
return & models . SavedReport { Status : "Complete" } , nil
}
func ( f * fortifyMock ) GetReportDetails ( id int64 ) ( * models . SavedReport , error ) {
return & models . SavedReport { Status : "Complete" } , nil
}
func ( f * fortifyMock ) UploadResultFile ( endpoint , file string , projectVersionID int64 ) error {
return nil
}
func ( f * fortifyMock ) DownloadReportFile ( endpoint string , projectVersionID int64 ) ( [ ] byte , error ) {
return [ ] byte ( "abcd" ) , nil
}
func ( f * fortifyMock ) DownloadResultFile ( endpoint string , projectVersionID int64 ) ( [ ] byte , error ) {
return [ ] byte ( "defg" ) , nil
}
type pullRequestServiceMock struct { }
func ( prService pullRequestServiceMock ) ListPullRequestsWithCommit ( ctx context . Context , owner , repo , sha string , opts * github . PullRequestListOptions ) ( [ ] * github . PullRequest , * github . Response , error ) {
if owner == "A" {
result := 17
return [ ] * github . PullRequest { { Number : & result } } , & github . Response { } , nil
} else if owner == "C" {
return [ ] * github . PullRequest { } , & github . Response { } , errors . New ( "Test error" )
}
return [ ] * github . PullRequest { } , & github . Response { } , nil
}
type execRunnerMock struct {
numExecutions int
current * execution
executions [ ] * execution
}
type execution struct {
dirValue string
envValue [ ] string
outWriter io . Writer
errWriter io . Writer
executable string
parameters [ ] string
}
func ( er * execRunnerMock ) newExecution ( ) * execution {
newExecution := & execution { }
er . executions = append ( er . executions , newExecution )
return newExecution
}
func ( er * execRunnerMock ) currentExecution ( ) * execution {
if nil == er . current {
er . numExecutions = 0
er . current = er . newExecution ( )
}
return er . current
}
func ( er * execRunnerMock ) SetDir ( d string ) {
er . currentExecution ( ) . dirValue = d
}
func ( er * execRunnerMock ) SetEnv ( e [ ] string ) {
er . currentExecution ( ) . envValue = e
}
func ( er * execRunnerMock ) Stdout ( out io . Writer ) {
er . currentExecution ( ) . outWriter = out
}
func ( er * execRunnerMock ) Stderr ( err io . Writer ) {
er . currentExecution ( ) . errWriter = err
}
func ( er * execRunnerMock ) RunExecutable ( e string , p ... string ) error {
er . numExecutions ++
er . currentExecution ( ) . executable = e
er . currentExecution ( ) . parameters = p
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"
classpathMaven := "some.jar;someother.jar"
if e == "python2" {
er . currentExecution ( ) . outWriter . Write ( [ ] byte ( classpathPip ) )
} else if e == "mvn" {
path := strings . ReplaceAll ( p [ 2 ] , "-Dmdep.outputFile=" , "" )
err := ioutil . WriteFile ( path , [ ] byte ( classpathMaven ) , 0644 )
if err != nil {
return err
}
}
er . current = er . newExecution ( )
return nil
}
func TestParametersAreValidated ( t * testing . T ) {
type parameterTestData struct {
nameOfRun string
config fortifyExecuteScanOptions
expectedError string
}
testData := [ ] parameterTestData {
{
nameOfRun : "all parameters empty" ,
config : fortifyExecuteScanOptions { } ,
2020-09-18 08:19:34 +02:00
expectedError : "Unable to get artifact from descriptor : build tool '' not supported" ,
} ,
}
for _ , data := range testData {
t . Run ( data . nameOfRun , func ( t * testing . T ) {
_ , err := determineArtifact ( data . config , & command . Command { } )
assert . EqualError ( t , err , data . expectedError )
} )
}
}
func TestExecutions ( t * testing . T ) {
type parameterTestData struct {
nameOfRun string
config fortifyExecuteScanOptions
expectedError string
expectedReportsLength int
expectedReports [ ] string
}
testData := [ ] parameterTestData {
{
nameOfRun : "golang scan and verify" ,
config : fortifyExecuteScanOptions { BuildTool : "golang" , BuildDescriptorFile : "go.mod" } ,
expectedReportsLength : 2 ,
expectedReports : [ ] string { "target/fortify-scan.*" , "target/*.fpr" } ,
} ,
{
nameOfRun : "golang verify only" ,
config : fortifyExecuteScanOptions { BuildTool : "golang" , BuildDescriptorFile : "go.mod" , VerifyOnly : true } ,
expectedReportsLength : 0 ,
2020-05-25 19:48:59 +02:00
} ,
}
for _ , data := range testData {
t . Run ( data . nameOfRun , func ( t * testing . T ) {
ff := fortifyMock { }
2020-09-18 08:19:34 +02:00
runner := & execRunnerMock { }
artMock := artifactMock { Coordinates : newCoordinatesMock ( ) }
2020-05-25 19:48:59 +02:00
influx := fortifyExecuteScanInflux { }
auditStatus := map [ string ] string { }
2020-09-18 08:19:34 +02:00
reports , _ := runFortifyScan ( data . config , & ff , runner , artMock , nil , & influx , auditStatus )
if len ( data . expectedReports ) != data . expectedReportsLength {
assert . Fail ( t , fmt . Sprintf ( "Wrong number of reports detected, expected %v, actual %v" , data . expectedReportsLength , len ( data . expectedReports ) ) )
}
if len ( data . expectedReports ) > 0 {
for _ , expectedPath := range data . expectedReports {
found := false
for _ , actualPath := range reports {
if actualPath . Target == expectedPath {
found = true
}
}
if ! found {
assert . Failf ( t , "Expected path %s not found" , expectedPath )
}
}
}
2020-05-25 19:48:59 +02:00
} )
}
}
func TestAnalyseSuspiciousExploitable ( t * testing . T ) {
config := fortifyExecuteScanOptions { SpotCheckMinimum : 4 , MustAuditIssueGroups : "Audit All, Corporate Security Requirements" , SpotAuditIssueGroups : "Spot Checks of Each Category" }
ff := fortifyMock { }
influx := fortifyExecuteScanInflux { }
name := "test"
selectorGUID := "3"
selectorName := "Analysis"
selectorEntityType := "CUSTOMTAG"
projectVersion := models . ProjectVersion { ID : 4711 , Name : & name }
auditStatus := map [ string ] string { }
selectorSet := models . IssueFilterSelectorSet {
FilterBySet : [ ] * models . IssueFilterSelector {
{
GUID : selectorGUID ,
DisplayName : selectorName ,
EntityType : selectorEntityType ,
} ,
} ,
GroupBySet : [ ] * models . IssueSelector {
{
GUID : & selectorGUID ,
DisplayName : & selectorName ,
EntityType : & selectorEntityType ,
} ,
} ,
}
issues := analyseSuspiciousExploitable ( config , & ff , & projectVersion , & models . FilterSet { } , & selectorSet , & influx , auditStatus )
assert . Equal ( t , 9 , issues )
assert . Equal ( t , "4" , influx . fortify_data . fields . suspicious )
assert . Equal ( t , "5" , influx . fortify_data . fields . exploitable )
assert . Equal ( t , "6" , influx . fortify_data . fields . suppressed )
}
func TestAnalyseUnauditedIssues ( t * testing . T ) {
config := fortifyExecuteScanOptions { SpotCheckMinimum : 4 , MustAuditIssueGroups : "Audit All, Corporate Security Requirements" , SpotAuditIssueGroups : "Spot Checks of Each Category" }
ff := fortifyMock { }
influx := fortifyExecuteScanInflux { }
name := "test"
projectVersion := models . ProjectVersion { ID : 4711 , Name : & name }
auditStatus := map [ string ] string { }
selectorSet := models . IssueFilterSelectorSet {
FilterBySet : [ ] * models . IssueFilterSelector {
{
GUID : "1" ,
DisplayName : "Folder" ,
EntityType : "ET1" ,
SelectorOptions : [ ] * models . SelectorOption {
{
Value : "abcd" ,
} ,
} ,
} ,
{
GUID : "2" ,
DisplayName : "Category" ,
EntityType : "ET2" ,
SelectorOptions : [ ] * models . SelectorOption {
{
Value : "abcd" ,
} ,
} ,
} ,
{
GUID : "3" ,
DisplayName : "Analysis" ,
EntityType : "ET3" ,
SelectorOptions : [ ] * models . SelectorOption {
{
Value : "abcd" ,
} ,
} ,
} ,
} ,
}
issues := analyseUnauditedIssues ( config , & ff , & projectVersion , & models . FilterSet { } , & selectorSet , & influx , auditStatus )
assert . Equal ( t , 13 , issues )
assert . Equal ( t , "15" , influx . fortify_data . fields . auditAllTotal )
assert . Equal ( t , "12" , influx . fortify_data . fields . auditAllAudited )
assert . Equal ( t , "20" , influx . fortify_data . fields . corporateTotal )
assert . Equal ( t , "11" , influx . fortify_data . fields . corporateAudited )
assert . Equal ( t , "13" , influx . fortify_data . fields . spotChecksTotal )
assert . Equal ( t , "11" , influx . fortify_data . fields . spotChecksAudited )
assert . Equal ( t , "1" , influx . fortify_data . fields . spotChecksGap )
}
func TestTriggerFortifyScan ( t * testing . T ) {
t . Run ( "maven" , func ( t * testing . T ) {
dir , err := ioutil . TempDir ( "" , "test trigger fortify scan" )
if err != nil {
t . Fatal ( "Failed to create temporary directory" )
}
oldCWD , _ := os . Getwd ( )
_ = os . Chdir ( dir )
// clean up tmp dir
defer func ( ) {
_ = os . Chdir ( oldCWD )
_ = os . RemoveAll ( dir )
} ( )
runner := execRunnerMock { }
2020-06-02 13:47:07 +02:00
config := fortifyExecuteScanOptions {
BuildTool : "maven" ,
AutodetectClasspath : true ,
BuildDescriptorFile : "./pom.xml" ,
Memory : "-Xmx4G -Xms2G" ,
Src : [ ] string { "**/*.xml" , "**/*.html" , "**/*.jsp" , "**/*.js" , "src/main/resources/**/*" , "src/main/java/**/*" } }
2020-05-28 10:45:06 +02:00
triggerFortifyScan ( config , & runner , "test" , "testLabel" , "my.group-myartifact" )
2020-05-25 19:48:59 +02:00
assert . Equal ( t , 3 , runner . numExecutions )
assert . Equal ( t , "mvn" , runner . executions [ 0 ] . executable )
2020-05-29 15:42:35 +02:00
assert . Equal ( t , [ ] string { "--file" , "./pom.xml" , "-Dmdep.outputFile=fortify-execute-scan-cp.txt" , "-DincludeScope=compile" , "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" , "--batch-mode" , "dependency:build-classpath" } , runner . executions [ 0 ] . parameters )
2020-05-25 19:48:59 +02:00
assert . Equal ( t , "sourceanalyzer" , runner . executions [ 1 ] . executable )
assert . Equal ( t , [ ] string { "-verbose" , "-64" , "-b" , "test" , "-Xmx4G" , "-Xms2G" , "-cp" , "some.jar;someother.jar" , "**/*.xml" , "**/*.html" , "**/*.jsp" , "**/*.js" , "src/main/resources/**/*" , "src/main/java/**/*" } , runner . executions [ 1 ] . parameters )
assert . Equal ( t , "sourceanalyzer" , runner . executions [ 2 ] . executable )
2020-05-28 10:45:06 +02:00
assert . Equal ( t , [ ] string { "-verbose" , "-64" , "-b" , "test" , "-scan" , "-Xmx4G" , "-Xms2G" , "-build-label" , "testLabel" , "-build-project" , "my.group-myartifact" , "-logfile" , "target/fortify-scan.log" , "-f" , "target/result.fpr" } , runner . executions [ 2 ] . parameters )
2020-05-25 19:48:59 +02:00
} )
t . Run ( "pip" , func ( t * testing . T ) {
dir , err := ioutil . TempDir ( "" , "test trigger fortify scan" )
if err != nil {
t . Fatal ( "Failed to create temporary directory" )
}
oldCWD , _ := os . Getwd ( )
_ = os . Chdir ( dir )
// clean up tmp dir
defer func ( ) {
_ = os . Chdir ( oldCWD )
_ = os . RemoveAll ( dir )
} ( )
runner := execRunnerMock { }
config := fortifyExecuteScanOptions { BuildTool : "pip" , PythonVersion : "python2" , AutodetectClasspath : true , BuildDescriptorFile : "./setup.py" , PythonRequirementsFile : "./requirements.txt" , PythonInstallCommand : "pip2 install --user" , Memory : "-Xmx4G -Xms2G" }
2020-05-28 10:45:06 +02:00
triggerFortifyScan ( config , & runner , "test" , "testLabel" , "" )
2020-05-25 19:48:59 +02:00
assert . Equal ( t , 5 , runner . numExecutions )
assert . Equal ( t , "python2" , runner . executions [ 0 ] . executable )
2020-06-02 13:47:07 +02:00
separator := getSeparator ( )
template := fmt . Sprintf ( "import sys;p=sys.path;p.remove('');print('%v'.join(p))" , separator )
assert . Equal ( t , [ ] string { "-c" , template } , runner . executions [ 0 ] . parameters )
2020-05-25 19:48:59 +02:00
assert . Equal ( t , "pip2" , runner . executions [ 1 ] . executable )
assert . Equal ( t , [ ] string { "install" , "--user" , "-r" , "./requirements.txt" , "" } , runner . executions [ 1 ] . parameters )
assert . Equal ( t , "pip2" , runner . executions [ 2 ] . executable )
assert . Equal ( t , [ ] string { "install" , "--user" } , runner . executions [ 2 ] . parameters )
assert . Equal ( t , "sourceanalyzer" , runner . executions [ 3 ] . executable )
2020-06-18 17:30:17 +02:00
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" , "-exclude" , fmt . Sprintf ( "./**/tests/**/*%s./**/setup.py" , separator ) , "./**/*" } , runner . executions [ 3 ] . parameters )
2020-05-25 19:48:59 +02:00
assert . Equal ( t , "sourceanalyzer" , runner . executions [ 4 ] . executable )
assert . Equal ( t , [ ] string { "-verbose" , "-64" , "-b" , "test" , "-scan" , "-Xmx4G" , "-Xms2G" , "-build-label" , "testLabel" , "-logfile" , "target/fortify-scan.log" , "-f" , "target/result.fpr" } , runner . executions [ 4 ] . parameters )
} )
}
func TestGenerateAndDownloadQGateReport ( t * testing . T ) {
ffMock := fortifyMock { Successive : false }
config := fortifyExecuteScanOptions { ReportTemplateID : 18 , ReportType : "PDF" }
name := "test"
projectVersion := models . ProjectVersion { ID : 4711 , Name : & name }
project := models . Project { ID : 815 , Name : & name }
projectVersion . Project = & project
t . Run ( "success" , func ( t * testing . T ) {
data , err := generateAndDownloadQGateReport ( config , & ffMock , & project , & projectVersion )
assert . NoError ( t , err )
assert . Equal ( t , [ ] byte ( "abcd" ) , data )
} )
}
2020-08-11 15:29:00 +02:00
var defaultPollingDelay = 10 * time . Second
var defaultPollingTimeout = 0 * time . Minute
func verifyScanResultsFinishedUploadingDefaults ( config fortifyExecuteScanOptions , sys fortify . System , projectVersionID int64 ) error {
return verifyScanResultsFinishedUploading ( config , sys , projectVersionID , "" , & models . FilterSet { } ,
defaultPollingDelay , defaultPollingTimeout )
}
2020-05-25 19:48:59 +02:00
func TestVerifyScanResultsFinishedUploading ( t * testing . T ) {
2020-08-11 15:29:00 +02:00
t . Parallel ( )
2020-05-25 19:48:59 +02:00
t . Run ( "error no recent upload detected" , func ( t * testing . T ) {
2020-08-11 15:29:00 +02:00
ffMock := fortifyMock { }
2020-05-25 19:48:59 +02:00
config := fortifyExecuteScanOptions { DeltaMinutes : - 1 }
2020-08-11 15:29:00 +02:00
err := verifyScanResultsFinishedUploadingDefaults ( config , & ffMock , 4711 )
assert . EqualError ( t , err , "no recent upload detected on Project Version" )
2020-05-25 19:48:59 +02:00
} )
config := fortifyExecuteScanOptions { DeltaMinutes : 20 }
t . Run ( "success" , func ( t * testing . T ) {
2020-08-11 15:29:00 +02:00
ffMock := fortifyMock { }
err := verifyScanResultsFinishedUploadingDefaults ( config , & ffMock , 4711 )
2020-05-25 19:48:59 +02:00
assert . NoError ( t , err )
} )
t . Run ( "error processing" , func ( t * testing . T ) {
2020-08-11 15:29:00 +02:00
ffMock := fortifyMock { }
err := verifyScanResultsFinishedUploadingDefaults ( config , & ffMock , 4712 )
2020-05-25 19:48:59 +02:00
assert . EqualError ( t , err , "There are artifacts that failed processing for Project Version 4712\n/html/ssc/index.jsp#!/version/4712/artifacts?filterSet=" )
} )
t . Run ( "error required auth" , func ( t * testing . T ) {
2020-08-11 15:29:00 +02:00
ffMock := fortifyMock { }
err := verifyScanResultsFinishedUploadingDefaults ( config , & ffMock , 4713 )
2020-05-25 19:48:59 +02:00
assert . EqualError ( t , err , "There are artifacts that require manual approval for Project Version 4713\n/html/ssc/index.jsp#!/version/4713/artifacts?filterSet=" )
} )
t . Run ( "error polling timeout" , func ( t * testing . T ) {
2020-08-11 15:29:00 +02:00
ffMock := fortifyMock { }
err := verifyScanResultsFinishedUploadingDefaults ( config , & ffMock , 4714 )
assert . EqualError ( t , err , "terminating after 0s since artifact for Project Version 4714 is still in status PROCESSING" )
2020-05-25 19:48:59 +02:00
} )
t . Run ( "success build label" , func ( t * testing . T ) {
2020-08-11 15:29:00 +02:00
ffMock := fortifyMock { }
err := verifyScanResultsFinishedUploading ( config , & ffMock , 4715 , "/commit/test" , & models . FilterSet { } ,
10 * time . Second , time . Duration ( config . PollingMinutes ) * time . Minute )
assert . NoError ( t , err )
} )
t . Run ( "failure after polling" , func ( t * testing . T ) {
config := fortifyExecuteScanOptions { DeltaMinutes : 1 }
ffMock := fortifyMock { }
const pollingDelay = 1 * time . Second
const timeout = 1 * time . Second
err := verifyScanResultsFinishedUploading ( config , & ffMock , 4716 , "" , & models . FilterSet { } , pollingDelay , timeout )
assert . EqualError ( t , err , "terminating after 1s since artifact for Project Version 4716 is still in status PROCESSING" )
} )
t . Run ( "success after polling" , func ( t * testing . T ) {
config := fortifyExecuteScanOptions { DeltaMinutes : 1 }
ffMock := fortifyMock { }
const pollingDelay = 500 * time . Millisecond
const timeout = 1 * time . Second
err := verifyScanResultsFinishedUploading ( config , & ffMock , 4716 , "" , & models . FilterSet { } , pollingDelay , timeout )
2020-05-25 19:48:59 +02:00
assert . NoError ( t , err )
} )
t . Run ( "error no artifacts" , func ( t * testing . T ) {
2020-08-11 15:29:00 +02:00
ffMock := fortifyMock { }
err := verifyScanResultsFinishedUploadingDefaults ( config , & ffMock , 4717 )
assert . EqualError ( t , err , "no uploaded artifacts for assessment detected for project version with ID 4717" )
} )
t . Run ( "warn old artifacts have errors" , func ( t * testing . T ) {
ffMock := fortifyMock { }
logBuffer := new ( bytes . Buffer )
logOutput := log . Entry ( ) . Logger . Out
log . Entry ( ) . Logger . Out = logBuffer
defer func ( ) { log . Entry ( ) . Logger . Out = logOutput } ( )
err := verifyScanResultsFinishedUploadingDefaults ( config , & ffMock , 4718 )
assert . NoError ( t , err )
assert . Contains ( t , logBuffer . String ( ) , "Previous uploads detected that failed processing" )
2020-05-25 19:48:59 +02:00
} )
}
func TestCalculateTimeDifferenceToLastUpload ( t * testing . T ) {
diffSeconds := calculateTimeDifferenceToLastUpload ( models . Iso8601MilliDateTime ( time . Now ( ) . UTC ( ) ) , 1234 )
assert . Equal ( t , true , diffSeconds < 1 )
}
func TestExecuteTemplatedCommand ( t * testing . T ) {
runner := execRunnerMock { }
template := [ ] string { "{{.Executable}}" , "-c" , "{{.Param}}" }
context := map [ string ] string { "Executable" : "test.cmd" , "Param" : "abcd" }
executeTemplatedCommand ( & runner , template , context )
assert . Equal ( t , "test.cmd" , runner . executions [ 0 ] . executable )
assert . Equal ( t , [ ] string { "-c" , "abcd" } , runner . executions [ 0 ] . parameters )
}
func TestDeterminePullRequestMerge ( t * testing . T ) {
config := fortifyExecuteScanOptions { CommitMessage : "Merge pull request #2462 from branch f-test" , PullRequestMessageRegex : ` (?m).*Merge pull request #(\d+) from.* ` , PullRequestMessageRegexGroup : 1 }
t . Run ( "success" , func ( t * testing . T ) {
match := determinePullRequestMerge ( config )
assert . Equal ( t , "2462" , match , "Expected different result" )
} )
t . Run ( "no match" , func ( t * testing . T ) {
config . CommitMessage = "Some test commit"
match := determinePullRequestMerge ( config )
assert . Equal ( t , "" , match , "Expected different result" )
} )
}
func TestDeterminePullRequestMergeGithub ( t * testing . T ) {
prServiceMock := pullRequestServiceMock { }
t . Run ( "success" , func ( t * testing . T ) {
match , err := determinePullRequestMergeGithub ( nil , fortifyExecuteScanOptions { Owner : "A" } , prServiceMock )
assert . NoError ( t , err )
assert . Equal ( t , "17" , match , "Expected different result" )
} )
t . Run ( "no match" , func ( t * testing . T ) {
match , err := determinePullRequestMergeGithub ( nil , fortifyExecuteScanOptions { Owner : "B" } , prServiceMock )
assert . NoError ( t , err )
assert . Equal ( t , "" , match , "Expected different result" )
} )
t . Run ( "error" , func ( t * testing . T ) {
match , err := determinePullRequestMergeGithub ( nil , fortifyExecuteScanOptions { Owner : "C" } , prServiceMock )
assert . EqualError ( t , err , "Test error" )
assert . Equal ( t , "" , match , "Expected different result" )
} )
}
func TestTranslateProject ( t * testing . T ) {
t . Run ( "python" , func ( t * testing . T ) {
execRunner := execRunnerMock { }
2020-06-02 13:47:07 +02:00
config := fortifyExecuteScanOptions { BuildTool : "pip" , Memory : "-Xmx4G" , Translate : ` [ { "pythonPath":"./some/path","src":"./**/*","exclude":"./tests/**/*"}] ` }
2020-05-25 19:48:59 +02:00
translateProject ( & config , & execRunner , "/commit/7267658798797" , "" )
assert . Equal ( t , "sourceanalyzer" , execRunner . executions [ 0 ] . executable , "Expected different executable" )
assert . Equal ( t , [ ] string { "-verbose" , "-64" , "-b" , "/commit/7267658798797" , "-Xmx4G" , "-python-path" , "./some/path" , "-exclude" , "./tests/**/*" , "./**/*" } , execRunner . executions [ 0 ] . parameters , "Expected different parameters" )
} )
t . Run ( "asp" , func ( t * testing . T ) {
execRunner := execRunnerMock { }
config := fortifyExecuteScanOptions { BuildTool : "windows" , Memory : "-Xmx6G" , Translate : ` [ { "aspnetcore":"true","dotNetCoreVersion":"3.5","exclude":"./tests/**/*","libDirs":"tmp/","src":"./**/*"}] ` }
translateProject ( & config , & execRunner , "/commit/7267658798797" , "" )
assert . Equal ( t , "sourceanalyzer" , execRunner . executions [ 0 ] . executable , "Expected different executable" )
2020-06-02 13:47:07 +02:00
assert . Equal ( t , [ ] string { "-verbose" , "-64" , "-b" , "/commit/7267658798797" , "-Xmx6G" , "-aspnetcore" , "-dotnet-core-version" , "3.5" , "-libdirs" , "tmp/" , "-exclude" , "./tests/**/*" , "./**/*" } , execRunner . executions [ 0 ] . parameters , "Expected different parameters" )
2020-05-25 19:48:59 +02:00
} )
t . Run ( "java" , func ( t * testing . T ) {
execRunner := execRunnerMock { }
config := fortifyExecuteScanOptions { BuildTool : "maven" , Memory : "-Xmx2G" , Translate : ` [ { "classpath":"./classes/*.jar","extdirs":"tmp/","jdk":"1.8.0-21","source":"1.8","sourcepath":"src/ext/","src":"./**/*"}] ` }
translateProject ( & config , & execRunner , "/commit/7267658798797" , "" )
assert . Equal ( t , "sourceanalyzer" , execRunner . executions [ 0 ] . executable , "Expected different executable" )
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/" , "./**/*" } , execRunner . executions [ 0 ] . parameters , "Expected different parameters" )
} )
t . Run ( "auto classpath" , func ( t * testing . T ) {
execRunner := execRunnerMock { }
config := fortifyExecuteScanOptions { BuildTool : "maven" , Memory : "-Xmx2G" , Translate : ` [ { "classpath":"./classes/*.jar", "extdirs":"tmp/","jdk":"1.8.0-21","source":"1.8","sourcepath":"src/ext/","src":"./**/*"}] ` }
translateProject ( & config , & execRunner , "/commit/7267658798797" , "./WEB-INF/lib/*.jar" )
assert . Equal ( t , "sourceanalyzer" , execRunner . executions [ 0 ] . executable , "Expected different executable" )
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/" , "./**/*" } , execRunner . executions [ 0 ] . parameters , "Expected different parameters" )
} )
}
func TestScanProject ( t * testing . T ) {
config := fortifyExecuteScanOptions { Memory : "-Xmx4G" }
t . Run ( "normal" , func ( t * testing . T ) {
execRunner := execRunnerMock { }
2020-05-28 10:45:06 +02:00
scanProject ( & config , & execRunner , "/commit/7267658798797" , "label" , "my.group-myartifact" )
2020-05-25 19:48:59 +02:00
assert . Equal ( t , "sourceanalyzer" , execRunner . executions [ 0 ] . executable , "Expected different executable" )
2020-05-28 10:45:06 +02:00
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" } , execRunner . executions [ 0 ] . parameters , "Expected different parameters" )
2020-05-25 19:48:59 +02:00
} )
t . Run ( "quick" , func ( t * testing . T ) {
execRunner := execRunnerMock { }
config . QuickScan = true
2020-05-28 10:45:06 +02:00
scanProject ( & config , & execRunner , "/commit/7267658798797" , "" , "" )
2020-05-25 19:48:59 +02:00
assert . Equal ( t , "sourceanalyzer" , execRunner . executions [ 0 ] . executable , "Expected different executable" )
assert . Equal ( t , [ ] string { "-verbose" , "-64" , "-b" , "/commit/7267658798797" , "-scan" , "-Xmx4G" , "-quick" , "-logfile" , "target/fortify-scan.log" , "-f" , "target/result.fpr" } , execRunner . executions [ 0 ] . parameters , "Expected different parameters" )
} )
}
func TestAutoresolveClasspath ( t * testing . T ) {
t . Run ( "success pip" , func ( t * testing . T ) {
execRunner := execRunnerMock { }
dir , err := ioutil . TempDir ( "" , "classpath" )
assert . NoError ( t , err , "Unexpected error detected" )
defer os . RemoveAll ( dir )
file := filepath . Join ( dir , "cp.txt" )
result := autoresolvePipClasspath ( "python2" , [ ] string { "-c" , "import sys;p=sys.path;p.remove('');print(';'.join(p))" } , file , & execRunner )
assert . Equal ( t , "python2" , execRunner . executions [ 0 ] . executable , "Expected different executable" )
assert . Equal ( t , [ ] string { "-c" , "import sys;p=sys.path;p.remove('');print(';'.join(p))" } , execRunner . executions [ 0 ] . parameters , "Expected different parameters" )
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" )
} )
t . Run ( "success maven" , func ( t * testing . T ) {
execRunner := execRunnerMock { }
dir , err := ioutil . TempDir ( "" , "classpath" )
assert . NoError ( t , err , "Unexpected error detected" )
defer os . RemoveAll ( dir )
file := filepath . Join ( dir , "cp.txt" )
2020-05-29 15:42:35 +02:00
result := autoresolveMavenClasspath ( fortifyExecuteScanOptions { BuildDescriptorFile : "pom.xml" } , file , & execRunner )
2020-05-25 19:48:59 +02:00
assert . Equal ( t , "mvn" , execRunner . executions [ 0 ] . executable , "Expected different executable" )
assert . Equal ( t , [ ] string { "--file" , "pom.xml" , fmt . Sprintf ( "-Dmdep.outputFile=%v" , file ) , "-DincludeScope=compile" , "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" , "--batch-mode" , "dependency:build-classpath" } , execRunner . executions [ 0 ] . parameters , "Expected different parameters" )
assert . Equal ( t , "some.jar;someother.jar" , result , "Expected different result" )
} )
}
2020-05-27 11:45:01 +02:00
func TestPopulateMavenTranslate ( t * testing . T ) {
t . Run ( "src without translate" , func ( t * testing . T ) {
2020-06-02 13:47:07 +02:00
config := fortifyExecuteScanOptions { Src : [ ] string { "./**/*" } }
2020-05-27 11:45:01 +02:00
translate , err := populateMavenTranslate ( & config , "" )
assert . NoError ( t , err )
2020-06-02 13:47:07 +02:00
assert . Equal ( t , ` [ { "classpath":"","src":"./**/*"}] ` , translate )
2020-05-27 11:45:01 +02:00
} )
t . Run ( "exclude without translate" , func ( t * testing . T ) {
2020-06-02 13:47:07 +02:00
config := fortifyExecuteScanOptions { Exclude : [ ] string { "./**/*" } }
2020-05-27 11:45:01 +02:00
translate , err := populateMavenTranslate ( & config , "" )
assert . NoError ( t , err )
2020-06-02 13:47:07 +02:00
assert . Equal ( t , ` [ { "classpath":"","exclude":"./**/*","src":"**/*.xml:**/*.html:**/*.jsp:**/*.js:**/src/main/resources/**/*:**/src/main/java/**/*"}] ` , translate )
2020-05-27 11:45:01 +02:00
} )
t . Run ( "with translate" , func ( t * testing . T ) {
2020-06-02 13:47:07 +02:00
config := fortifyExecuteScanOptions { Translate : ` [ { "classpath":""}] ` , Src : [ ] string { "./**/*" } , Exclude : [ ] string { "./**/*" } }
2020-05-27 11:45:01 +02:00
translate , err := populateMavenTranslate ( & config , "ignored/path" )
assert . NoError ( t , err )
2020-06-02 13:47:07 +02:00
assert . Equal ( t , ` [ { "classpath":""}] ` , translate )
2020-05-27 11:45:01 +02:00
} )
}
func TestPopulatePipTranslate ( t * testing . T ) {
t . Run ( "PythonAdditionalPath without translate" , func ( t * testing . T ) {
2020-06-02 13:47:07 +02:00
config := fortifyExecuteScanOptions { PythonAdditionalPath : [ ] string { "./lib" , "." } }
2020-05-27 11:45:01 +02:00
translate , err := populatePipTranslate ( & config , "" )
2020-06-02 13:47:07 +02:00
separator := getSeparator ( )
expected := fmt . Sprintf ( ` [ { "exclude":"./**/tests/**/*%v./**/setup.py","pythonPath":"%v./lib%v.","src":"./**/*"}] ` ,
separator , separator , separator )
2020-05-27 11:45:01 +02:00
assert . NoError ( t , err )
2020-06-02 13:47:07 +02:00
assert . Equal ( t , expected , translate )
2020-05-27 11:45:01 +02:00
} )
2020-06-02 13:47:07 +02:00
t . Run ( "Src without translate" , func ( t * testing . T ) {
config := fortifyExecuteScanOptions { Src : [ ] string { "./**/*.py" } }
2020-05-27 11:45:01 +02:00
translate , err := populatePipTranslate ( & config , "" )
2020-06-02 13:47:07 +02:00
separator := getSeparator ( )
expected := fmt . Sprintf (
` [ { "exclude":"./**/tests/**/*%v./**/setup.py","pythonPath":"%v","src":"./**/*.py"}] ` ,
separator , separator )
2020-05-27 11:45:01 +02:00
assert . NoError ( t , err )
2020-06-02 13:47:07 +02:00
assert . Equal ( t , expected , translate )
2020-05-27 11:45:01 +02:00
} )
2020-06-02 13:47:07 +02:00
t . Run ( "Exclude without translate" , func ( t * testing . T ) {
config := fortifyExecuteScanOptions { Exclude : [ ] string { "./**/tests/**/*" } }
2020-05-27 11:45:01 +02:00
translate , err := populatePipTranslate ( & config , "" )
2020-06-02 13:47:07 +02:00
separator := getSeparator ( )
expected := fmt . Sprintf (
` [ { "exclude":"./**/tests/**/*","pythonPath":"%v","src":"./**/*"}] ` ,
separator )
2020-05-27 11:45:01 +02:00
assert . NoError ( t , err )
2020-06-02 13:47:07 +02:00
assert . Equal ( t , expected , translate )
2020-05-27 11:45:01 +02:00
} )
t . Run ( "with translate" , func ( t * testing . T ) {
2020-06-02 13:47:07 +02:00
config := fortifyExecuteScanOptions {
Translate : ` [ { "pythonPath":""}] ` ,
Src : [ ] string { "./**/*" } ,
PythonAdditionalPath : [ ] string { "./lib" , "." } }
2020-05-27 11:45:01 +02:00
translate , err := populatePipTranslate ( & config , "ignored/path" )
assert . NoError ( t , err )
assert . Equal ( t , ` [ { "pythonPath":""}] ` , translate , "Expected different parameters" )
} )
}
2020-05-29 15:42:35 +02:00
func TestRemoveDuplicates ( t * testing . T ) {
testData := [ ] struct {
name string
input string
expected string
separator string
} {
{ "empty" , "" , "" , "x" } ,
{ "no duplicates" , ":a::b::" , "a:b" , ":" } ,
{ "duplicates" , "::a:b:a:b::a" , "a:b" , ":" } ,
{ "long separator" , "..a.b....ab..a.b" , "a.b..ab" , ".." } ,
{ "no separator" , "abc" , "abc" , "" } ,
}
for _ , data := range testData {
t . Run ( data . name , func ( t * testing . T ) {
assert . Equal ( t , data . expected , removeDuplicates ( data . input , data . separator ) )
} )
}
}
2020-08-11 15:29:00 +02:00
func toFortifyTime ( time time . Time ) models . Iso8601MilliDateTime {
return models . Iso8601MilliDateTime ( time . UTC ( ) )
}