package malwarescan import ( "fmt" piperhttp "github.com/SAP/jenkins-library/pkg/http" "github.com/stretchr/testify/assert" "io" "io/ioutil" "net/http" "testing" ) func TestMalwareServiceScan(t *testing.T) { t.Run("Scan without finding", func(t *testing.T) { httpClient := &httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":false,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"} malwareService := ClientImpl{ HTTPClient: httpClient, Host: "https://example.org/malwarescanner", } candidate := readCloserMock{Content: "HELLO"} scanResult, err := malwareService.Scan(candidate) if assert.NoError(t, err) { assert.True(t, httpClient.Body.Closed) assert.Equal(t, "https://example.org/malwarescanner/scan", httpClient.URL) assert.Equal(t, "POST", httpClient.Method) if assert.NotNil(t, httpClient.Header) { assert.Equal(t, "application/octet-stream", httpClient.Header.Get("Content-Type")) } assert.Equal(t, "application/octet-stream", scanResult.MimeType) assert.Equal(t, 298782, scanResult.ScanSize) assert.Equal(t, "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", scanResult.SHA256) assert.Equal(t, "", scanResult.Finding) assert.False(t, scanResult.MalwareDetected) assert.False(t, scanResult.EncryptedContentDetected) } }) t.Run("Scan without finding", func(t *testing.T) { httpClient := &httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":true,\"encryptedContentDetected\":true,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\", \"finding\": \"Description of the finding\"}"} malwareService := ClientImpl{ HTTPClient: httpClient, Host: "https://example.org/malwarescanner", } candidate := readCloserMock{Content: "HELLO"} scanResult, err := malwareService.Scan(candidate) if assert.NoError(t, err) { assert.True(t, httpClient.Body.Closed) assert.Equal(t, "https://example.org/malwarescanner/scan", httpClient.URL) assert.Equal(t, "POST", httpClient.Method) if assert.NotNil(t, httpClient.Header) { assert.Equal(t, "application/octet-stream", httpClient.Header.Get("Content-Type")) } assert.Equal(t, "application/octet-stream", scanResult.MimeType) assert.Equal(t, 298782, scanResult.ScanSize) assert.Equal(t, "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", scanResult.SHA256) assert.Equal(t, "Description of the finding", scanResult.Finding) assert.True(t, scanResult.MalwareDetected) assert.True(t, scanResult.EncryptedContentDetected) } }) t.Run("Scan results in error - file to large", func(t *testing.T) { httpClient := &httpMock{StatusCode: 413, ResponseBody: "{\"message\":\"Payload too large - The file is too large and cannot be scanned or the archive structure is too complex.\"}"} malwareService := ClientImpl{ HTTPClient: httpClient, Host: "https://example.org/malwarescanner", } candidate := readCloserMock{Content: "HELLO"} scanResult, err := malwareService.Scan(candidate) assert.Nil(t, scanResult) assert.EqualError(t, err, "MalwareService returned with status code 413: Payload too large - The file is too large and cannot be scanned or the archive structure is too complex.") }) t.Run("Scan results in error - unexpected error", func(t *testing.T) { httpClient := &httpMock{StatusCode: 500, ResponseBody: ""} malwareService := ClientImpl{ HTTPClient: httpClient, Host: "https://example.org/malwarescanner", } candidate := readCloserMock{Content: "HELLO"} scanResult, err := malwareService.Scan(candidate) assert.Nil(t, scanResult) assert.EqualError(t, err, "MalwareService returned with status code 500, no further information available") }) } func TestMalwareServiceInfo(t *testing.T) { t.Run("Receives engine info", func(t *testing.T) { httpClient := &httpMock{StatusCode: 200, ResponseBody: "{\"engineVersion\": \"Malware Service Mock\", \"signatureTimestamp\": \"2022-01-12T09:26:28.000Z\", \"maxScanSize\": 666}"} malwareService := ClientImpl{ HTTPClient: httpClient, Host: "https://example.org/malwarescanner", } info, err := malwareService.Info() if assert.NoError(t, err) { assert.True(t, httpClient.Body.Closed) assert.Equal(t, "https://example.org/malwarescanner/info", httpClient.URL) assert.Equal(t, "GET", httpClient.Method) assert.Equal(t, "Malware Service Mock", info.EngineVersion) assert.Equal(t, "2022-01-12T09:26:28.000Z", info.SignatureTimestamp) assert.Equal(t, 666, info.MaxScanSize) } }) } 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 Header http.Header // 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 c.Header = header if r != nil { _, 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 }