github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/malwarescan/malwarescan_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package malwarescan
     5  
     6  import (
     7  	"fmt"
     8  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
     9  	"github.com/stretchr/testify/assert"
    10  	"io"
    11  	"net/http"
    12  	"testing"
    13  )
    14  
    15  func TestMalwareServiceScan(t *testing.T) {
    16  	t.Run("Scan without finding", func(t *testing.T) {
    17  		httpClient := &httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":false,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
    18  
    19  		malwareService := ClientImpl{
    20  			HTTPClient: httpClient,
    21  			Host:       "https://example.org/malwarescanner",
    22  		}
    23  
    24  		candidate := readCloserMock{Content: "HELLO"}
    25  		scanResult, err := malwareService.Scan(candidate)
    26  
    27  		if assert.NoError(t, err) {
    28  			assert.True(t, httpClient.Body.Closed)
    29  
    30  			assert.Equal(t, "https://example.org/malwarescanner/scan", httpClient.URL)
    31  			assert.Equal(t, "POST", httpClient.Method)
    32  
    33  			if assert.NotNil(t, httpClient.Header) {
    34  				assert.Equal(t, "application/octet-stream", httpClient.Header.Get("Content-Type"))
    35  			}
    36  
    37  			assert.Equal(t, "application/octet-stream", scanResult.MimeType)
    38  			assert.Equal(t, 298782, scanResult.ScanSize)
    39  			assert.Equal(t, "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", scanResult.SHA256)
    40  			assert.Equal(t, "", scanResult.Finding)
    41  			assert.False(t, scanResult.MalwareDetected)
    42  			assert.False(t, scanResult.EncryptedContentDetected)
    43  		}
    44  	})
    45  
    46  	t.Run("Scan without finding", func(t *testing.T) {
    47  		httpClient := &httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":true,\"encryptedContentDetected\":true,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\", \"finding\": \"Description of the finding\"}"}
    48  
    49  		malwareService := ClientImpl{
    50  			HTTPClient: httpClient,
    51  			Host:       "https://example.org/malwarescanner",
    52  		}
    53  
    54  		candidate := readCloserMock{Content: "HELLO"}
    55  		scanResult, err := malwareService.Scan(candidate)
    56  
    57  		if assert.NoError(t, err) {
    58  			assert.True(t, httpClient.Body.Closed)
    59  
    60  			assert.Equal(t, "https://example.org/malwarescanner/scan", httpClient.URL)
    61  			assert.Equal(t, "POST", httpClient.Method)
    62  
    63  			if assert.NotNil(t, httpClient.Header) {
    64  				assert.Equal(t, "application/octet-stream", httpClient.Header.Get("Content-Type"))
    65  			}
    66  
    67  			assert.Equal(t, "application/octet-stream", scanResult.MimeType)
    68  			assert.Equal(t, 298782, scanResult.ScanSize)
    69  			assert.Equal(t, "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", scanResult.SHA256)
    70  			assert.Equal(t, "Description of the finding", scanResult.Finding)
    71  			assert.True(t, scanResult.MalwareDetected)
    72  			assert.True(t, scanResult.EncryptedContentDetected)
    73  		}
    74  	})
    75  
    76  	t.Run("Scan results in error - file to large", func(t *testing.T) {
    77  		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.\"}"}
    78  
    79  		malwareService := ClientImpl{
    80  			HTTPClient: httpClient,
    81  			Host:       "https://example.org/malwarescanner",
    82  		}
    83  
    84  		candidate := readCloserMock{Content: "HELLO"}
    85  		scanResult, err := malwareService.Scan(candidate)
    86  
    87  		assert.Nil(t, scanResult)
    88  		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.")
    89  	})
    90  
    91  	t.Run("Scan results in error - unexpected error", func(t *testing.T) {
    92  		httpClient := &httpMock{StatusCode: 500, ResponseBody: ""}
    93  
    94  		malwareService := ClientImpl{
    95  			HTTPClient: httpClient,
    96  			Host:       "https://example.org/malwarescanner",
    97  		}
    98  
    99  		candidate := readCloserMock{Content: "HELLO"}
   100  		scanResult, err := malwareService.Scan(candidate)
   101  
   102  		assert.Nil(t, scanResult)
   103  		assert.EqualError(t, err, "MalwareService returned with status code 500, no further information available")
   104  	})
   105  }
   106  
   107  func TestMalwareServiceInfo(t *testing.T) {
   108  	t.Run("Receives engine info", func(t *testing.T) {
   109  		httpClient := &httpMock{StatusCode: 200, ResponseBody: "{\"engineVersion\": \"Malware Service Mock\", \"signatureTimestamp\": \"2022-01-12T09:26:28.000Z\", \"maxScanSize\": 666}"}
   110  
   111  		malwareService := ClientImpl{
   112  			HTTPClient: httpClient,
   113  			Host:       "https://example.org/malwarescanner",
   114  		}
   115  
   116  		info, err := malwareService.Info()
   117  
   118  		if assert.NoError(t, err) {
   119  			assert.True(t, httpClient.Body.Closed)
   120  
   121  			assert.Equal(t, "https://example.org/malwarescanner/info", httpClient.URL)
   122  			assert.Equal(t, "GET", httpClient.Method)
   123  			assert.Equal(t, "Malware Service Mock", info.EngineVersion)
   124  			assert.Equal(t, "2022-01-12T09:26:28.000Z", info.SignatureTimestamp)
   125  			assert.Equal(t, 666, info.MaxScanSize)
   126  		}
   127  	})
   128  }
   129  
   130  type httpMock struct {
   131  	Method       string                  // is set during test execution
   132  	URL          string                  // is set before test execution
   133  	ResponseBody string                  // is set before test execution
   134  	Options      piperhttp.ClientOptions // is set during test
   135  	StatusCode   int                     // is set during test
   136  	Body         readCloserMock          // is set during test
   137  	Header       http.Header             // is set during test
   138  }
   139  
   140  func (c *httpMock) SetOptions(options piperhttp.ClientOptions) {
   141  	c.Options = options
   142  }
   143  
   144  func (c *httpMock) SendRequest(method string, url string, r io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) {
   145  	c.Method = method
   146  	c.URL = url
   147  	c.Header = header
   148  
   149  	if r != nil {
   150  		_, err := io.ReadAll(r)
   151  
   152  		if err != nil {
   153  			return nil, err
   154  		}
   155  	}
   156  
   157  	c.Body = readCloserMock{Content: c.ResponseBody}
   158  	res := http.Response{StatusCode: c.StatusCode, Body: &c.Body}
   159  
   160  	return &res, nil
   161  }
   162  
   163  type readCloserMock struct {
   164  	Content string
   165  	Closed  bool
   166  }
   167  
   168  func (rc readCloserMock) Read(b []byte) (n int, err error) {
   169  
   170  	if len(b) < len(rc.Content) {
   171  		// in real life we would fill the buffer according to buffer size ...
   172  		return 0, fmt.Errorf("Buffer size (%d) not sufficient, need: %d", len(b), len(rc.Content))
   173  	}
   174  	copy(b, rc.Content)
   175  	return len(rc.Content), io.EOF
   176  }
   177  
   178  func (rc *readCloserMock) Close() error {
   179  	rc.Closed = true
   180  	return nil
   181  }