harbor/src/pkg/scan/job_test.go
Steven Zou 0c19eba8c2 [stage2]support pluggable scanner
- add scanner rest API v1 spec
- implement v1 client which is used to talk to scanner adapter
- adjust data/orm models
- adjust code package structure

Signed-off-by: Steven Zou <szou@vmware.com>

- implement scan client which is used to talk to scanner adapter
- implement scan job which take the work of communicating with scanner
- update scanner mgmt API routes
- add corresponding UT cases
2019-09-23 09:37:54 +08:00

307 lines
6.8 KiB
Go

// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package scan
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/jobservice/logger"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/pkg/scan/vuln"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
// JobTestSuite is a test suite to test the scan job.
type JobTestSuite struct {
suite.Suite
defaultClientPool v1.ClientPool
mcp *MockClientPool
}
// TestJob is the entry of JobTestSuite.
func TestJob(t *testing.T) {
suite.Run(t, &JobTestSuite{})
}
// SetupSuite sets up test env for JobTestSuite.
func (suite *JobTestSuite) SetupSuite() {
mcp := &MockClientPool{}
suite.defaultClientPool = v1.DefaultClientPool
v1.DefaultClientPool = mcp
suite.mcp = mcp
}
// TeraDownSuite clears test env for TeraDownSuite.
func (suite *JobTestSuite) TeraDownSuite() {
v1.DefaultClientPool = suite.defaultClientPool
}
// TestJob tests the scan job
func (suite *JobTestSuite) TestJob() {
ctx := &MockJobContext{}
lg := &MockJobLogger{}
ctx.On("GetLogger").Return(lg)
r := &scanner.Registration{
ID: 0,
UUID: "uuid",
Name: "TestJob",
URL: "https://clair.com:8080",
}
rData, err := r.ToJSON()
require.NoError(suite.T(), err)
sr := &v1.ScanRequest{
Registry: &v1.Registry{
URL: "http://localhost:5000",
Authorization: "the_token",
},
Artifact: &v1.Artifact{
Repository: "library/test_job",
Digest: "sha256:data",
MimeType: v1.MimeTypeDockerArtifact,
},
}
sData, err := sr.ToJSON()
require.NoError(suite.T(), err)
mimeTypes := []string{v1.MimeTypeNativeReport}
jp := make(job.Parameters)
jp[JobParamRegistration] = rData
jp[JobParameterRequest] = sData
jp[JobParameterMimes] = mimeTypes
mc := &MockClient{}
sre := &v1.ScanResponse{
ID: "scan_id",
}
mc.On("SubmitScan", sr).Return(sre, nil)
rp := vuln.Report{
GeneratedAt: time.Now().UTC().String(),
Scanner: &v1.Scanner{
Name: "Clair",
Vendor: "Harbor",
Version: "0.1.0",
},
Severity: vuln.High,
Vulnerabilities: []*vuln.VulnerabilityItem{
{
ID: "2019-0980-0909",
Package: "dpkg",
Version: "0.9.1",
FixVersion: "0.9.2",
Severity: vuln.High,
Description: "mock one",
Links: []string{"https://vuln.com"},
},
},
}
jRep, err := json.Marshal(rp)
require.NoError(suite.T(), err)
mc.On("GetScanReport", "scan_id", v1.MimeTypeNativeReport).Return(string(jRep), nil)
suite.mcp.On("Get", r).Return(mc, nil)
crp := &CheckInReport{
Digest: sr.Artifact.Digest,
RegistrationUUID: r.UUID,
MimeType: v1.MimeTypeNativeReport,
RawReport: string(jRep),
}
jsonData, err := crp.ToJSON()
require.NoError(suite.T(), err)
ctx.On("Checkin", string(jsonData)).Return(nil)
j := &Job{}
err = j.Run(ctx, jp)
require.NoError(suite.T(), err)
}
// MockJobContext mocks job context interface.
// TODO: Maybe moved to a separate `mock` pkg for sharing in future.
type MockJobContext struct {
mock.Mock
}
// Build ...
func (mjc *MockJobContext) Build(tracker job.Tracker) (job.Context, error) {
args := mjc.Called(tracker)
c := args.Get(0)
if c != nil {
return c.(job.Context), nil
}
return nil, args.Error(1)
}
// Get ...
func (mjc *MockJobContext) Get(prop string) (interface{}, bool) {
args := mjc.Called(prop)
return args.Get(0), args.Bool(1)
}
// SystemContext ...
func (mjc *MockJobContext) SystemContext() context.Context {
return context.TODO()
}
// Checkin ...
func (mjc *MockJobContext) Checkin(status string) error {
args := mjc.Called(status)
return args.Error(0)
}
// OPCommand ...
func (mjc *MockJobContext) OPCommand() (job.OPCommand, bool) {
args := mjc.Called()
return (job.OPCommand)(args.String(0)), args.Bool(1)
}
// GetLogger ...
func (mjc *MockJobContext) GetLogger() logger.Interface {
return &MockJobLogger{}
}
// Tracker ...
func (mjc *MockJobContext) Tracker() job.Tracker {
args := mjc.Called()
if t := args.Get(0); t != nil {
return t.(job.Tracker)
}
return nil
}
// MockJobLogger mocks the job logger interface.
// TODO: Maybe moved to a separate `mock` pkg for sharing in future.
type MockJobLogger struct {
mock.Mock
}
// Debug ...
func (mjl *MockJobLogger) Debug(v ...interface{}) {
logger.Debug(v...)
}
// Debugf ...
func (mjl *MockJobLogger) Debugf(format string, v ...interface{}) {
logger.Debugf(format, v...)
}
// Info ...
func (mjl *MockJobLogger) Info(v ...interface{}) {
logger.Info(v...)
}
// Infof ...
func (mjl *MockJobLogger) Infof(format string, v ...interface{}) {
logger.Infof(format, v...)
}
// Warning ...
func (mjl *MockJobLogger) Warning(v ...interface{}) {
logger.Warning(v...)
}
// Warningf ...
func (mjl *MockJobLogger) Warningf(format string, v ...interface{}) {
logger.Warningf(format, v...)
}
// Error ...
func (mjl *MockJobLogger) Error(v ...interface{}) {
logger.Error(v...)
}
// Errorf ...
func (mjl *MockJobLogger) Errorf(format string, v ...interface{}) {
logger.Errorf(format, v...)
}
// Fatal ...
func (mjl *MockJobLogger) Fatal(v ...interface{}) {
logger.Fatal(v...)
}
// Fatalf ...
func (mjl *MockJobLogger) Fatalf(format string, v ...interface{}) {
logger.Fatalf(format, v...)
}
// MockClientPool mocks the client pool
type MockClientPool struct {
mock.Mock
}
// Get v1 client
func (mcp *MockClientPool) Get(r *scanner.Registration) (v1.Client, error) {
args := mcp.Called(r)
c := args.Get(0)
if c != nil {
return c.(v1.Client), nil
}
return nil, args.Error(1)
}
// MockClient mocks the v1 client
type MockClient struct {
mock.Mock
}
// GetMetadata ...
func (mc *MockClient) GetMetadata() (*v1.ScannerAdapterMetadata, error) {
args := mc.Called()
s := args.Get(0)
if s != nil {
return s.(*v1.ScannerAdapterMetadata), nil
}
return nil, args.Error(1)
}
// SubmitScan ...
func (mc *MockClient) SubmitScan(req *v1.ScanRequest) (*v1.ScanResponse, error) {
args := mc.Called(req)
sr := args.Get(0)
if sr != nil {
return sr.(*v1.ScanResponse), nil
}
return nil, args.Error(1)
}
// GetScanReport ...
func (mc *MockClient) GetScanReport(scanRequestID, reportMIMEType string) (string, error) {
args := mc.Called(scanRequestID, reportMIMEType)
return args.String(0), args.Error(1)
}