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 }