From 48ad56a2ef2995427be138087cf3f68867e6f0f4 Mon Sep 17 00:00:00 2001 From: ttimonen Date: Sun, 7 Jul 2024 19:21:18 +0000 Subject: [PATCH] Implement unit-test for scan handler. It plays wtih channels and goroutines, so having it behave correctly is not completely trivial and test worthy. Signed-off-by: ttimonen --- .../handlerequests/v1/requestshandler.go | 13 ++--- .../handlerequests/v1/requestshandler_test.go | 56 +++++++++++++++++++ .../handlerequests/v1/requestshandlerutils.go | 4 +- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/httphandler/handlerequests/v1/requestshandler.go b/httphandler/handlerequests/v1/requestshandler.go index 01952b77..1e0099b8 100644 --- a/httphandler/handlerequests/v1/requestshandler.go +++ b/httphandler/handlerequests/v1/requestshandler.go @@ -90,7 +90,6 @@ func (handler *HTTPHandler) Status(w http.ResponseWriter, r *http.Request) { // ============================================== SCAN ======================================================== // Scan API func (handler *HTTPHandler) Scan(w http.ResponseWriter, r *http.Request) { - // generate id scanID := uuid.NewString() @@ -101,7 +100,6 @@ func (handler *HTTPHandler) Scan(w http.ResponseWriter, r *http.Request) { return } w.Header().Set("Content-Type", "application/json") - scanRequestParams, err := getScanParamsFromRequest(r, scanID) if err != nil { handler.writeError(w, err, "") @@ -110,12 +108,6 @@ func (handler *HTTPHandler) Scan(w http.ResponseWriter, r *http.Request) { scanRequestParams.ctx = trace.ContextWithSpanContext(context.Background(), trace.SpanContextFromContext(r.Context())) handler.state.setBusy(scanID) - - response := &utilsmetav1.Response{} - response.ID = scanID - response.Type = utilsapisv1.BusyScanResponseType - response.Response = fmt.Sprintf("scanning '%s' is in progress", scanID) - handler.scanResponseChan.set(scanID) // add channel defer handler.scanResponseChan.delete(scanID) @@ -126,6 +118,11 @@ func (handler *HTTPHandler) Scan(w http.ResponseWriter, r *http.Request) { handler.scanRequestChan <- scanRequestParams }() + response := &utilsmetav1.Response{ + ID: scanID, + Type: utilsapisv1.BusyScanResponseType, + Response: fmt.Sprintf("scanning '%s' is in progress", scanID), + } if scanRequestParams.scanQueryParams.ReturnResults { // wait for scan to complete response = <-handler.scanResponseChan.get(scanID) diff --git a/httphandler/handlerequests/v1/requestshandler_test.go b/httphandler/handlerequests/v1/requestshandler_test.go index a1a30765..b1ee47a9 100644 --- a/httphandler/handlerequests/v1/requestshandler_test.go +++ b/httphandler/handlerequests/v1/requestshandler_test.go @@ -1,5 +1,61 @@ package v1 +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http/httptest" + "testing" + + "github.com/kubescape/kubescape/v3/core/cautils" + utilsmetav1 "github.com/kubescape/opa-utils/httpserver/meta/v1" + reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2" +) + +func testBody(t *testing.T) io.Reader { + t.Helper() + b, err := json.Marshal(utilsmetav1.PostScanRequest{Account: "fakeFoobar"}) + if err != nil { + t.Fatal("Can not marshal") + } + return bytes.NewReader(b) +} + +type scanner func(_ context.Context, _ *cautils.ScanInfo, _ string) (*reporthandlingv2.PostureReport, error) + +// TestScan tests that the scan handler passes the scan requests correctly to the underlying scan engine. +func TestScan(t *testing.T) { + + // Our scanner is not setting up the k8s connection; the test is covering the rest of the wiring + // that the signaling from the http handler goes all the way to the scanner implementation. + defer func(o scanner) { scanImpl = o }(scanImpl) + scanImpl = func(context.Context, *cautils.ScanInfo, string) (*reporthandlingv2.PostureReport, error) { + return nil, nil + } + + var ( + h = NewHTTPHandler() + rq = httptest.NewRequest("POST", "/scan?wait=true", testBody(t)) + w = httptest.NewRecorder() + ) + h.Scan(w, rq) + rs := w.Result() + body, _ := io.ReadAll(rs.Body) + + type out struct { + code int + ctype string + body string + } + want := out{200, "application/json", `{"id":"","type":"v1results"}`} + got := out{rs.StatusCode, rs.Header.Get("Content-type"), string(body)} + + if got != want { + t.Errorf("Scan result: %v, want %v", got, want) + } +} + // ============================================== STATUS ======================================================== // Status API // func TestStatus(t *testing.T) { diff --git a/httphandler/handlerequests/v1/requestshandlerutils.go b/httphandler/handlerequests/v1/requestshandlerutils.go index d081930d..efd59876 100644 --- a/httphandler/handlerequests/v1/requestshandlerutils.go +++ b/httphandler/handlerequests/v1/requestshandlerutils.go @@ -25,11 +25,12 @@ import ( "go.opentelemetry.io/otel/trace" ) +var scanImpl = scan // Override for testing func (handler *HTTPHandler) executeScan(scanReq *scanRequestParams) { response := &utilsmetav1.Response{} logger.L().Info("scan triggered", helpers.String("ID", scanReq.scanID)) - _, err := scan(scanReq.ctx, scanReq.scanInfo, scanReq.scanID) + _, err := scanImpl(scanReq.ctx, scanReq.scanInfo, scanReq.scanID) if err != nil { logger.L().Ctx(scanReq.ctx).Error("scanning failed", helpers.String("ID", scanReq.scanID), helpers.Error(err)) if scanReq.scanQueryParams.ReturnResults { @@ -39,6 +40,7 @@ func (handler *HTTPHandler) executeScan(scanReq *scanRequestParams) { } else { logger.L().Ctx(scanReq.ctx).Success("done scanning", helpers.String("ID", scanReq.scanID)) if scanReq.scanQueryParams.ReturnResults { + //TODO(ttimonen) should we actually pass the PostureReport here somehow? response.Type = utilsapisv1.ResultsV1ScanResponseType } }