1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/cmd/malwareExecuteScan_test.go
Marcus Holl 0ad38b8621
Timeout for malwarescan (#1623)
Provide a timeout parameters to malwarescan step. This is forwarded to the piper http layer. The default used there is 10 seconds with is not useable for that use case for larger files.
2020-06-03 11:08:34 +02:00

249 lines
8.6 KiB
Go

package cmd
import (
"fmt"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/stretchr/testify/assert"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
)
var malwareScanConfig = malwareExecuteScanOptions{
Host: "https://example.org/malwarescanner",
Username: "me",
Password: "********",
File: "target/myFile",
Timeout: "60",
}
func TestMalwareScanTests(t *testing.T) {
open = openFileMock()
getSHA256 = func(path string) (string, error) {
return "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", nil
}
defer func() {
open = _open
getSHA256 = _getSHA256
}()
t.Run("No malware, no encrypted content", func(t *testing.T) {
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":false,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
if assert.NoError(t, error) {
t.Run("check url", func(t *testing.T) {
assert.Equal(t, "https://example.org/malwarescanner/scan", httpClient.URL)
})
t.Run("check method", func(t *testing.T) {
assert.Equal(t, "POST", httpClient.Method)
})
t.Run("check user", func(t *testing.T) {
assert.Equal(t, "me", httpClient.Options.Username)
})
t.Run("check password", func(t *testing.T) {
assert.Equal(t, "********", httpClient.Options.Password)
})
}
})
t.Run("Malware detected", func(t *testing.T) {
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":true,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: true, encrypted content detected: false")
})
t.Run("Encrypted content detected", func(t *testing.T) {
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\": false, \"encryptedContentDetected\": true, \"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: false, encrypted content detected: true")
})
t.Run("Malware and encrypted content detected", func(t *testing.T) {
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\": true, \"encryptedContentDetected\": true, \"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: true, encrypted content detected: true")
})
}
func TestMalwareScanRequest(t *testing.T) {
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":false,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
candidate := readCloserMock{Content: "HELLO"}
malWareScanResponse, err := sendMalwareScanRequest(&httpClient, "POST", "https://example.org/malwarescanner", candidate)
t.Run("Malware - scanRequest - check response body closed", func(t *testing.T) {
if assert.NoError(t, err) {
assert.True(t, httpClient.Body.Closed)
}
})
t.Run("Malware - scanRequest - check scan response", func(t *testing.T) {
if assert.NoError(t, err) {
assert.False(t, malWareScanResponse.MalwareDetected)
assert.False(t, malWareScanResponse.EncryptedContentDetected)
assert.Equal(t, 298782, malWareScanResponse.ScanSize)
assert.Equal(t, "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", malWareScanResponse.SHA256)
}
})
}
func TestMalwareMarshalBody(t *testing.T) {
t.Run("Malware - marshalResponse - body uninitialized", func(t *testing.T) {
var body []byte
r, error := marshalResponse(body)
assert.Nil(t, r)
assert.EqualError(t, error, "Unmarshalling of response body failed. Body: '': unexpected end of JSON input")
})
}
func TestMalwareValidateResponse(t *testing.T) {
t.Run("Malware - validateResponse - HTTP error", func(t *testing.T) {
r, e := validateResponse(nil, fmt.Errorf("request error"))
assert.Nil(t, r)
assert.EqualError(t, e, "HTTP request failed with error: request error. Details: \"\"")
})
t.Run("Malware - validateResponse - empty response", func(t *testing.T) {
r, e := validateResponse(&http.Response{}, fmt.Errorf("request error"))
assert.Nil(t, r)
assert.EqualError(t, e, "HTTP request failed with error: request error. Details: \"\"")
})
t.Run("Malware - validateResponse - status code 500", func(t *testing.T) {
mock := responseMock(500, true)
r, e := validateResponse(mock, nil)
assert.Nil(t, r)
assert.True(t, strings.HasPrefix(e.Error(), "Unexpected response code (500)"))
})
t.Run("Malware - validateResponse - malwareDetected", func(t *testing.T) {
mock := responseMock(200, true)
r, e := validateResponse(mock, nil)
assert.NotNil(t, r)
assert.Nil(t, e)
})
}
func responseMock(code int, malwareDetected bool) *http.Response {
if malwareDetected {
return &http.Response{StatusCode: code, Body: &readCloserMock{Content: "{\"malwareDetected\":true,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}}
}
return &http.Response{StatusCode: code, Body: &readCloserMock{Content: "{\"malwareDetected\":false,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}}
}
func TestMalwareFileNotFound(t *testing.T) {
httpClient := httpMock{StatusCode: 200}
open = func(path string) (io.ReadCloser, error) {
return nil, fmt.Errorf("open %s: no such file or directory", path)
}
defer func() { open = _open }()
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
assert.Error(t, error, "open target/myFile: no such file or directory")
}
func TestMalwareInternalServerError(t *testing.T) {
httpClient := httpMock{StatusCode: 500, ResponseBody: "internal server error"}
open = openFileMock()
defer func() { open = _open }()
err := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
assert.Error(t, err, "Unexpected response code (500). 200 expected. Details: \"internal server error\"")
}
func TestMalwareRetrieveHash(t *testing.T) {
open = openFileMock()
defer func() {
open = _open
}()
hash, err := _getSHA256("target/myFile")
if assert.NoError(t, err) {
assert.Equal(t, "3733cd977ff8eb18b987357e22ced99f46097f31ecb239e878ae63760e83e4d5", hash)
}
}
func openFileMock() func(path string) (io.ReadCloser, error) {
return func(path string) (io.ReadCloser, error) {
if path == "target/myFile" {
return &readCloserMock{Content: "HELLO"}, nil // the content does not matter
}
return nil, fmt.Errorf("open %s: no such file or directory", path)
}
}
type httpMock struct {
Method string // is set during test execution
URL string // is set before test execution
ResponseBody string // is set before test execution
Options piperhttp.ClientOptions // is set during test
StatusCode int // is set during test
Body readCloserMock // is set during test
}
func (c *httpMock) SetOptions(options piperhttp.ClientOptions) {
c.Options = options
}
func (c *httpMock) SendRequest(method string, url string, r io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) {
c.Method = method
c.URL = url
_, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
c.Body = readCloserMock{Content: c.ResponseBody}
res := http.Response{StatusCode: c.StatusCode, Body: &c.Body}
return &res, nil
}
type readCloserMock struct {
Content string
Closed bool
}
func (rc readCloserMock) Read(b []byte) (n int, err error) {
if len(b) < len(rc.Content) {
// in real life we would fill the buffer according to buffer size ...
return 0, fmt.Errorf("Buffer size (%d) not sufficient, need: %d", len(b), len(rc.Content))
}
copy(b, rc.Content)
return len(rc.Content), io.EOF
}
func (rc *readCloserMock) Close() error {
rc.Closed = true
return nil
}