github.com/adevinta/lava@v0.7.2/internal/engine/engine_test.go (about)

     1  // Copyright 2023 Adevinta
     2  
     3  package engine
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"flag"
     9  	"fmt"
    10  	"log/slog"
    11  	"math/rand"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"os"
    15  	"strings"
    16  	"testing"
    17  
    18  	agentconfig "github.com/adevinta/vulcan-agent/config"
    19  	report "github.com/adevinta/vulcan-report"
    20  	types "github.com/adevinta/vulcan-types"
    21  	"github.com/docker/docker/api/types/image"
    22  	"github.com/jroimartin/clilog"
    23  
    24  	"github.com/adevinta/lava/internal/assettypes"
    25  	"github.com/adevinta/lava/internal/config"
    26  	"github.com/adevinta/lava/internal/containers"
    27  )
    28  
    29  var testRuntime containers.Runtime
    30  
    31  func TestMain(m *testing.M) {
    32  	flag.Parse()
    33  
    34  	level := slog.LevelError
    35  	if testing.Verbose() {
    36  		level = slog.LevelDebug
    37  	}
    38  
    39  	h := clilog.NewCLIHandler(os.Stderr, &clilog.HandlerOptions{Level: level})
    40  	slog.SetDefault(slog.New(h))
    41  
    42  	rt, err := containers.GetenvRuntime()
    43  	if err != nil {
    44  		fmt.Fprintf(os.Stderr, "error: get env runtime: %v", err)
    45  		os.Exit(2)
    46  	}
    47  	testRuntime = rt
    48  
    49  	os.Exit(m.Run())
    50  }
    51  
    52  func TestEngine_Run(t *testing.T) {
    53  	cli, err := containers.NewDockerdClient(testRuntime)
    54  	if err != nil {
    55  		t.Fatalf("could not create dockerd client: %v", err)
    56  	}
    57  	defer cli.Close()
    58  
    59  	const imgRef = "lava-internal-engine-test:go-test"
    60  
    61  	if _, err := cli.ImageBuild(context.Background(), "testdata/engine/lava-engine-test", "Dockerfile", imgRef); err != nil {
    62  		t.Fatalf("could build Docker image: %v", err)
    63  	}
    64  	defer func() {
    65  		rmOpts := image.RemoveOptions{Force: true, PruneChildren: true}
    66  		if _, err := cli.ImageRemove(context.Background(), imgRef, rmOpts); err != nil {
    67  			t.Logf("could not delete test Docker image %q: %v", imgRef, err)
    68  		}
    69  	}()
    70  
    71  	wantDetails := fmt.Sprintf("lava engine test response %v", rand.Uint64())
    72  
    73  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    74  		fmt.Fprint(w, wantDetails)
    75  	}))
    76  	defer srv.Close()
    77  
    78  	t.Logf("test server listening at %v", srv.URL)
    79  
    80  	var (
    81  		checktypeURLs = []string{"testdata/engine/checktypes_lava_engine_test.json"}
    82  		targets       = []config.Target{
    83  			{
    84  				Identifier: srv.URL,
    85  				AssetType:  types.WebAddress,
    86  			},
    87  		}
    88  		agentConfig = config.AgentConfig{
    89  			PullPolicy: agentconfig.PullPolicyNever,
    90  		}
    91  	)
    92  
    93  	eng, err := New(agentConfig, checktypeURLs)
    94  	if err != nil {
    95  		t.Fatalf("engine initialization error: %v", err)
    96  	}
    97  	defer eng.Close()
    98  
    99  	engineReport, err := eng.Run(targets)
   100  	if err != nil {
   101  		t.Fatalf("engine run error: %v", err)
   102  	}
   103  
   104  	checkReportTarget(t, engineReport, eng.cli.HostGatewayHostname())
   105  
   106  	var checkReports []report.Report
   107  	for _, v := range engineReport {
   108  		checkReports = append(checkReports, v)
   109  	}
   110  
   111  	if len(checkReports) != 1 {
   112  		t.Fatalf("unexpected number of reports: %v", len(checkReports))
   113  	}
   114  
   115  	gotReport := checkReports[0]
   116  
   117  	if gotReport.Status != "FINISHED" {
   118  		t.Errorf("unexpected status: %v", gotReport.Status)
   119  	}
   120  
   121  	if gotReport.Target != srv.URL {
   122  		t.Errorf("unexpected target: got: %v, want: %v", gotReport.Target, srv.URL)
   123  	}
   124  
   125  	if len(gotReport.Vulnerabilities) != 1 {
   126  		t.Fatalf("unexpected number of vulnerabilities: %v", len(gotReport.Vulnerabilities))
   127  	}
   128  
   129  	gotDetails := gotReport.Vulnerabilities[0].Details
   130  
   131  	if gotDetails != wantDetails {
   132  		t.Errorf("unexpected details: got: %#q, want: %#q", gotDetails, wantDetails)
   133  	}
   134  }
   135  
   136  func TestEngine_Run_docker_image(t *testing.T) {
   137  	var (
   138  		checktypeURLs = []string{"testdata/engine/checktypes_trivy.json"}
   139  		targets       = []config.Target{
   140  			{
   141  				Identifier: "python:3.4-alpine",
   142  				AssetType:  types.DockerImage,
   143  			},
   144  		}
   145  		agentConfig = config.AgentConfig{
   146  			PullPolicy: agentconfig.PullPolicyAlways,
   147  		}
   148  	)
   149  
   150  	eng, err := New(agentConfig, checktypeURLs)
   151  	if err != nil {
   152  		t.Fatalf("engine initialization error: %v", err)
   153  	}
   154  	defer eng.Close()
   155  
   156  	engineReport, err := eng.Run(targets)
   157  	if err != nil {
   158  		t.Fatalf("engine run error: %v", err)
   159  	}
   160  
   161  	checkReportTarget(t, engineReport, eng.cli.HostGatewayHostname())
   162  
   163  	var checkReports []report.Report
   164  	for _, v := range engineReport {
   165  		checkReports = append(checkReports, v)
   166  	}
   167  
   168  	if len(checkReports) != 1 {
   169  		t.Fatalf("unexpected number of reports: %v", len(checkReports))
   170  	}
   171  
   172  	gotReport := checkReports[0]
   173  
   174  	if gotReport.Status != "FINISHED" {
   175  		t.Errorf("unexpected status: %v", gotReport.Status)
   176  	}
   177  
   178  	if len(gotReport.Vulnerabilities) == 0 {
   179  		t.Errorf("no vulnerabilities found")
   180  	}
   181  
   182  	t.Logf("found %v vulnerabilities", len(gotReport.Vulnerabilities))
   183  }
   184  
   185  func TestEngine_Run_path(t *testing.T) {
   186  	var (
   187  		checktypeURLs = []string{"testdata/engine/checktypes_trivy.json"}
   188  		agentConfig   = config.AgentConfig{
   189  			PullPolicy: agentconfig.PullPolicyAlways,
   190  		}
   191  	)
   192  
   193  	tests := []struct {
   194  		name       string
   195  		target     config.Target
   196  		wantStatus string
   197  		wantVulns  bool
   198  	}{
   199  		{
   200  			name: "dir",
   201  			target: config.Target{
   202  				Identifier: "testdata/engine/vulnpath",
   203  				AssetType:  assettypes.Path,
   204  			},
   205  			wantStatus: "FINISHED",
   206  			wantVulns:  true,
   207  		},
   208  		{
   209  			name: "file",
   210  			target: config.Target{
   211  				Identifier: "testdata/engine/vulnpath/Dockerfile",
   212  				AssetType:  assettypes.Path,
   213  			},
   214  			wantStatus: "FINISHED",
   215  			wantVulns:  true,
   216  		},
   217  	}
   218  
   219  	for _, tt := range tests {
   220  		t.Run(tt.name, func(t *testing.T) {
   221  			eng, err := New(agentConfig, checktypeURLs)
   222  			if err != nil {
   223  				t.Fatalf("engine initialization error: %v", err)
   224  			}
   225  			defer eng.Close()
   226  
   227  			engineReport, err := eng.Run([]config.Target{tt.target})
   228  			if err != nil {
   229  				t.Fatalf("engine run error: %v", err)
   230  			}
   231  
   232  			checkReportTarget(t, engineReport, eng.cli.HostGatewayHostname())
   233  
   234  			var checkReports []report.Report
   235  			for _, v := range engineReport {
   236  				checkReports = append(checkReports, v)
   237  			}
   238  
   239  			if len(checkReports) != 1 {
   240  				t.Fatalf("unexpected number of reports: %v", len(checkReports))
   241  			}
   242  
   243  			gotReport := checkReports[0]
   244  
   245  			if gotReport.Status != tt.wantStatus {
   246  				t.Errorf("unexpected status: %v", gotReport.Status)
   247  			}
   248  
   249  			if (len(gotReport.Vulnerabilities) > 0) != tt.wantVulns {
   250  				t.Errorf("unexpected number of vulnerabilities: %v", len(gotReport.Vulnerabilities))
   251  			}
   252  
   253  			t.Logf("found %v vulnerabilities", len(gotReport.Vulnerabilities))
   254  		})
   255  	}
   256  }
   257  
   258  func TestEngine_Run_unreachable_target(t *testing.T) {
   259  	var (
   260  		checktypeURLs = []string{"testdata/engine/checktypes_trivy.json"}
   261  		agentConfig   = config.AgentConfig{
   262  			PullPolicy: agentconfig.PullPolicyAlways,
   263  		}
   264  		target = config.Target{
   265  			Identifier: "testdata/engine/notexist",
   266  			AssetType:  assettypes.Path,
   267  		}
   268  	)
   269  
   270  	eng, err := New(agentConfig, checktypeURLs)
   271  	if err != nil {
   272  		t.Fatalf("engine initialization error: %v", err)
   273  	}
   274  	defer eng.Close()
   275  
   276  	if _, err := eng.Run([]config.Target{target}); err == nil {
   277  		t.Fatal("unexpected nil error")
   278  	}
   279  }
   280  
   281  func TestEngine_Run_not_repo(t *testing.T) {
   282  	var (
   283  		checktypeURLs = []string{"testdata/engine/checktypes_trivy.json"}
   284  		agentConfig   = config.AgentConfig{
   285  			PullPolicy: agentconfig.PullPolicyAlways,
   286  		}
   287  		target = config.Target{
   288  			Identifier: "testdata/engine/vulnpath",
   289  			AssetType:  types.GitRepository,
   290  		}
   291  	)
   292  
   293  	eng, err := New(agentConfig, checktypeURLs)
   294  	if err != nil {
   295  		t.Fatalf("engine initialization error: %v", err)
   296  	}
   297  	defer eng.Close()
   298  
   299  	engineReport, err := eng.Run([]config.Target{target})
   300  	if err != nil {
   301  		t.Fatalf("engine run error: %v", err)
   302  	}
   303  
   304  	checkReportTarget(t, engineReport, eng.cli.HostGatewayHostname())
   305  
   306  	var checkReports []report.Report
   307  	for _, v := range engineReport {
   308  		checkReports = append(checkReports, v)
   309  	}
   310  
   311  	if len(checkReports) != 0 {
   312  		t.Fatalf("unexpected number of reports: %v", len(checkReports))
   313  	}
   314  }
   315  
   316  func TestEngine_Run_no_jobs(t *testing.T) {
   317  	var (
   318  		checktypeURLs = []string{"testdata/engine/checktypes_lava_engine_test.json"}
   319  		agentConfig   = config.AgentConfig{
   320  			PullPolicy: agentconfig.PullPolicyNever,
   321  		}
   322  	)
   323  
   324  	eng, err := New(agentConfig, checktypeURLs)
   325  	if err != nil {
   326  		t.Fatalf("engine initialization error: %v", err)
   327  	}
   328  	defer eng.Close()
   329  
   330  	engineReport, err := eng.Run(nil)
   331  	if err != nil {
   332  		t.Fatalf("engine run error: %v", err)
   333  	}
   334  
   335  	if len(engineReport) != 0 {
   336  		t.Fatalf("unexpected number of reports: %v", len(engineReport))
   337  	}
   338  }
   339  
   340  // checkReportTarget encodes report as JSON and looks for substr in
   341  // the output. If substr is not found, checkReportTarget calls
   342  // t.Errorf.
   343  func checkReportTarget(t *testing.T, report Report, substr string) {
   344  	doc, err := json.MarshalIndent(report, "", "  ")
   345  	if err != nil {
   346  		t.Fatalf("marshal error: %v", err)
   347  	}
   348  
   349  	if strings.Contains(string(doc), substr) {
   350  		t.Errorf("report contains %q:\n%s", substr, doc)
   351  	}
   352  }