Files
QSfera/Server/services/antivirus/pkg/scanners/icap_test.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

188 lines
7.0 KiB
Go

package scanners_test
import (
"bytes"
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
ic "github.com/opencloud-eu/icap-client"
"github.com/qsfera/server/services/antivirus/pkg/scanners"
"github.com/qsfera/server/services/antivirus/pkg/scanners/mocks"
)
func TestICAP_Scan(t *testing.T) {
var (
earlyExitErr = errors.New("stop here")
testUrl = "icap://test"
client = mocks.NewScanner(t)
scanner = &scanners.ICAP{Client: client, URL: testUrl}
)
t.Run("it sends a OPTIONS request to determine details", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, ic.MethodOPTIONS, request.Method)
assert.Equal(t, testUrl, request.URL.String())
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{})
assert.ErrorIs(t, earlyExitErr, err) // we can exit early, just in case check the error to be identical to the early exit error
})
t.Run("it sends a REQMOD request with all the details", func(t *testing.T) {
t.Run("request with ContentLength", func(t *testing.T) {
t.Run("with size", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, ic.MethodREQMOD, request.Method)
assert.Equal(t, testUrl, request.URL.String())
assert.EqualValues(t, 999, request.HTTPRequest.ContentLength)
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{Size: 999})
assert.ErrorIs(t, earlyExitErr, err)
})
t.Run("without size", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, ic.MethodREQMOD, request.Method)
assert.Equal(t, testUrl, request.URL.String())
assert.EqualValues(t, 0, request.HTTPRequest.ContentLength)
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{})
assert.ErrorIs(t, earlyExitErr, err)
})
})
t.Run("request with Content-Type header", func(t *testing.T) {
t.Run("name contains known extension", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, "application/pdf", request.HTTPRequest.Header.Get("Content-Type"))
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{Name: "report.pdf"})
assert.ErrorIs(t, earlyExitErr, err)
})
t.Run("name with unknown extension", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, "application/octet-stream", request.HTTPRequest.Header.Get("Content-Type"))
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{Name: "report.unknown"})
assert.ErrorIs(t, earlyExitErr, err)
})
t.Run("name without extension", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, "httpd/unix-directory", request.HTTPRequest.Header.Get("Content-Type"))
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{Name: "report"})
assert.ErrorIs(t, earlyExitErr, err)
})
})
t.Run("request with the OPTIONS response preview size ", func(t *testing.T) {
t.Run("with PreviewBytes set", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{PreviewBytes: 444}, nil).Once()
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, 444, request.PreviewBytes)
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
assert.ErrorIs(t, earlyExitErr, err)
})
t.Run("without PreviewBytes set", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, 0, request.PreviewBytes)
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
assert.ErrorIs(t, earlyExitErr, err)
})
})
})
t.Run("request with the OPTIONS response preview size ", func(t *testing.T) {
t.Run("with PreviewBytes set", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{PreviewBytes: 444}, nil).Once()
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
assert.Equal(t, 444, request.PreviewBytes)
return ic.Response{}, earlyExitErr
}).Once()
_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
assert.ErrorIs(t, earlyExitErr, err)
})
t.Run("it handles virus scan results", func(t *testing.T) {
t.Run("no virus", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
result, err := scanner.Scan(scanners.Input{})
assert.Nil(t, err)
assert.False(t, result.Infected)
})
// clamav returns an X-Infection-Found header with the threat description
t.Run("X-Infection-Found header ", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).Return(ic.Response{Header: http.Header{"X-Infection-Found": []string{"Threat=bad threat;"}}}, nil).Once()
result, err := scanner.Scan(scanners.Input{})
assert.Nil(t, err)
assert.True(t, result.Infected)
assert.Equal(t, "bad threat", result.Description)
})
// skyhigh returns the information via the content response
t.Run("X-Infection-Found header", func(t *testing.T) {
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).Return(ic.Response{ContentResponse: &http.Response{StatusCode: http.StatusForbidden, Status: "some status"}}, nil).Once()
result, err := scanner.Scan(scanners.Input{})
assert.Nil(t, err)
assert.True(t, result.Infected)
assert.Equal(t, "some status", result.Description)
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
client.EXPECT().Do(mock.Anything).Return(ic.Response{ContentResponse: &http.Response{StatusCode: http.StatusOK}}, nil).Once()
result, err = scanner.Scan(scanners.Input{})
assert.Nil(t, err)
assert.False(t, result.Infected)
})
})
})
}