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

     1  //go:build unit
     2  // +build unit
     3  
     4  package protecode
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"os"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/stretchr/testify/assert"
    21  )
    22  
    23  func TestMapResponse(t *testing.T) {
    24  
    25  	cases := []struct {
    26  		give  string
    27  		input interface{}
    28  		want  interface{}
    29  	}{
    30  		{`"{}"`, new(Result), &Result{ProductID: 0}},
    31  		{`{"product_id": 1}`, new(Result), &Result{ProductID: 1}},
    32  		{`"{\"product_id\": 4711}"`, new(Result), &Result{ProductID: 4711}},
    33  		{"{\"results\": {\"product_id\": 1}}", new(ResultData), &ResultData{Result: Result{ProductID: 1}}},
    34  		{`{"results": {"status": "B", "id": 209396, "product_id": 209396, "report_url": "https://protecode.c.eu-de-2.cloud.sap/products/209396/"}}`, new(ResultData), &ResultData{Result: Result{ProductID: 209396, Status: statusBusy, ReportURL: "https://protecode.c.eu-de-2.cloud.sap/products/209396/"}}},
    35  		{`{"products": [{"product_id": 1}]}`, new(ProductData), &ProductData{Products: []Product{{ProductID: 1}}}},
    36  	}
    37  	pc := makeProtecode(Options{})
    38  	for _, c := range cases {
    39  
    40  		r := io.NopCloser(bytes.NewReader([]byte(c.give)))
    41  		pc.mapResponse(r, c.input)
    42  		assert.Equal(t, c.want, c.input)
    43  	}
    44  }
    45  
    46  func TestParseResultSuccess(t *testing.T) {
    47  
    48  	var result Result = Result{
    49  		ProductID: 4712,
    50  		ReportURL: "ReportUrl",
    51  		Status:    statusBusy,
    52  		Components: []Component{
    53  			{Vulns: []Vulnerability{
    54  				{Exact: true, Triage: []Triage{}, Vuln: Vuln{Cve: "Cve1", Cvss: "7.2", Cvss3Score: "0.0"}},
    55  				{Exact: true, Triage: []Triage{{ID: 1}}, Vuln: Vuln{Cve: "Cve2", Cvss: "2.2", Cvss3Score: "2.3"}},
    56  				{Exact: true, Triage: []Triage{}, Vuln: Vuln{Cve: "Cve2b", Cvss: "0.0", Cvss3Score: "0.0"}},
    57  			},
    58  			},
    59  			{Vulns: []Vulnerability{
    60  				{Exact: true, Triage: []Triage{}, Vuln: Vuln{Cve: "Cve3", Cvss: "3.2", Cvss3Score: "7.3"}},
    61  				{Exact: true, Triage: []Triage{}, Vuln: Vuln{Cve: "Cve4", Cvss: "8.0", Cvss3Score: "8.0"}},
    62  				{Exact: false, Triage: []Triage{}, Vuln: Vuln{Cve: "Cve4b", Cvss: "8.0", Cvss3Score: "8.0"}},
    63  			},
    64  			},
    65  		},
    66  	}
    67  	pc := makeProtecode(Options{})
    68  	m, vulns := pc.ParseResultForInflux(result, "Excluded CVES: Cve4,")
    69  	t.Run("Parse Protecode Results", func(t *testing.T) {
    70  		assert.Equal(t, 1, m["historical_vulnerabilities"])
    71  		assert.Equal(t, 1, m["triaged_vulnerabilities"])
    72  		assert.Equal(t, 1, m["excluded_vulnerabilities"])
    73  		assert.Equal(t, 1, m["minor_vulnerabilities"])
    74  		assert.Equal(t, 2, m["major_vulnerabilities"])
    75  		assert.Equal(t, 3, m["vulnerabilities"])
    76  
    77  		assert.Equal(t, 3, len(vulns))
    78  	})
    79  }
    80  
    81  func TestParseResultViolations(t *testing.T) {
    82  
    83  	violations := filepath.Join("testdata", "protecode_result_violations.json")
    84  	byteContent, err := os.ReadFile(violations)
    85  	if err != nil {
    86  		t.Fatalf("failed reading %v", violations)
    87  	}
    88  	pc := makeProtecode(Options{})
    89  
    90  	resultData := new(ResultData)
    91  	pc.mapResponse(io.NopCloser(strings.NewReader(string(byteContent))), resultData)
    92  
    93  	m, vulns := pc.ParseResultForInflux(resultData.Result, "CVE-2018-1, CVE-2017-1000382")
    94  	t.Run("Parse Protecode Results", func(t *testing.T) {
    95  		assert.Equal(t, 1125, m["historical_vulnerabilities"])
    96  		assert.Equal(t, 0, m["triaged_vulnerabilities"])
    97  		assert.Equal(t, 1, m["excluded_vulnerabilities"])
    98  		assert.Equal(t, 129, m["cvss3GreaterOrEqualSeven"])
    99  		assert.Equal(t, 13, m["cvss2GreaterOrEqualSeven"])
   100  		assert.Equal(t, 226, m["vulnerabilities"])
   101  
   102  		assert.Equal(t, 226, len(vulns))
   103  	})
   104  }
   105  
   106  func TestParseResultNoViolations(t *testing.T) {
   107  
   108  	noViolations := filepath.Join("testdata", "protecode_result_no_violations.json")
   109  	byteContent, err := os.ReadFile(noViolations)
   110  	if err != nil {
   111  		t.Fatalf("failed reading %v", noViolations)
   112  	}
   113  
   114  	pc := makeProtecode(Options{})
   115  	resultData := new(ResultData)
   116  	pc.mapResponse(io.NopCloser(strings.NewReader(string(byteContent))), resultData)
   117  
   118  	m, vulns := pc.ParseResultForInflux(resultData.Result, "CVE-2018-1, CVE-2017-1000382")
   119  	t.Run("Parse Protecode Results", func(t *testing.T) {
   120  		assert.Equal(t, 27, m["historical_vulnerabilities"])
   121  		assert.Equal(t, 0, m["triaged_vulnerabilities"])
   122  		assert.Equal(t, 0, m["excluded_vulnerabilities"])
   123  		assert.Equal(t, 0, m["cvss3GreaterOrEqualSeven"])
   124  		assert.Equal(t, 0, m["cvss2GreaterOrEqualSeven"])
   125  		assert.Equal(t, 0, m["vulnerabilities"])
   126  
   127  		assert.Equal(t, 0, len(vulns))
   128  	})
   129  }
   130  
   131  func TestParseResultTriaged(t *testing.T) {
   132  
   133  	triaged := filepath.Join("testdata", "protecode_result_triaging.json")
   134  	byteContent, err := os.ReadFile(triaged)
   135  	if err != nil {
   136  		t.Fatalf("failed reading %v", triaged)
   137  	}
   138  
   139  	pc := makeProtecode(Options{})
   140  	resultData := new(ResultData)
   141  	pc.mapResponse(io.NopCloser(strings.NewReader(string(byteContent))), resultData)
   142  
   143  	m, vulns := pc.ParseResultForInflux(resultData.Result, "")
   144  	t.Run("Parse Protecode Results", func(t *testing.T) {
   145  		assert.Equal(t, 1132, m["historical_vulnerabilities"])
   146  		assert.Equal(t, 187, m["triaged_vulnerabilities"])
   147  		assert.Equal(t, 0, m["excluded_vulnerabilities"])
   148  		assert.Equal(t, 15, m["cvss3GreaterOrEqualSeven"])
   149  		assert.Equal(t, 0, m["cvss2GreaterOrEqualSeven"])
   150  		assert.Equal(t, 36, m["vulnerabilities"])
   151  
   152  		assert.Equal(t, 36, len(vulns))
   153  	})
   154  }
   155  
   156  func TestLoadExistingProductSuccess(t *testing.T) {
   157  
   158  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   159  
   160  		response := ProductData{
   161  			Products: []Product{
   162  				{ProductID: 1, FileName: "file_1.zip"},
   163  			},
   164  		}
   165  
   166  		var b bytes.Buffer
   167  		json.NewEncoder(&b).Encode(&response)
   168  		rw.Write([]byte(b.Bytes()))
   169  	}))
   170  	// Close the server when test finishes
   171  	defer server.Close()
   172  
   173  	cases := []struct {
   174  		pc             Protecode
   175  		protecodeGroup string
   176  		fileName       string
   177  		want           int
   178  	}{
   179  		{makeProtecode(Options{ServerURL: server.URL}), "group", "file_1.zip", 1},
   180  	}
   181  	for _, c := range cases {
   182  
   183  		got := c.pc.LoadExistingProduct(c.protecodeGroup, c.fileName)
   184  		assert.Equal(t, c.want, got)
   185  	}
   186  }
   187  
   188  func TestPollForResultSuccess(t *testing.T) {
   189  	requestURI := ""
   190  	var response ResultData = ResultData{}
   191  
   192  	protecodePollInterval = time.Nanosecond
   193  
   194  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   195  		requestURI = req.RequestURI
   196  		productID := 111
   197  
   198  		// 2021-04-20 d :
   199  		// Added case '333' to test proper handling of case where Component list is empty.
   200  		if strings.Contains(requestURI, "222") {
   201  			productID = 222
   202  		} else if strings.Contains(requestURI, "333") {
   203  			productID = 333
   204  		}
   205  
   206  		var cmpnts []Component
   207  		if productID != 333 {
   208  			cmpnts = []Component{
   209  				{Vulns: []Vulnerability{
   210  					{Triage: []Triage{{ID: 1}}}},
   211  				}}
   212  		}
   213  
   214  		response = ResultData{Result: Result{ProductID: productID, ReportURL: requestURI, Status: "D", Components: cmpnts}}
   215  
   216  		var b bytes.Buffer
   217  		json.NewEncoder(&b).Encode(&response)
   218  		rw.Write([]byte(b.Bytes()))
   219  
   220  	}))
   221  
   222  	cases := []struct {
   223  		productID int
   224  		want      ResultData
   225  	}{
   226  		{111, ResultData{Result: Result{ProductID: 111, ReportURL: "/api/product/111/", Status: "D", Components: []Component{
   227  			{Vulns: []Vulnerability{
   228  				{Triage: []Triage{{ID: 1}}}},
   229  			}},
   230  		}}},
   231  		{222, ResultData{Result: Result{ProductID: 222, ReportURL: "/api/product/222/", Status: "D", Components: []Component{
   232  			{Vulns: []Vulnerability{
   233  				{Triage: []Triage{{ID: 1}}}},
   234  			}},
   235  		}}},
   236  		{333, ResultData{Result: Result{ProductID: 333, ReportURL: "/api/product/333/", Status: "D"}}},
   237  	}
   238  	// Close the server when test finishes
   239  	defer server.Close()
   240  
   241  	pc := makeProtecode(Options{ServerURL: server.URL, Duration: (time.Minute * 1)})
   242  
   243  	for _, c := range cases {
   244  		got := pc.PollForResult(c.productID, "1")
   245  		assert.Equal(t, c.want, got)
   246  		assert.Equal(t, fmt.Sprintf("/api/product/%v/", c.productID), requestURI)
   247  	}
   248  }
   249  
   250  func TestPullResultSuccess(t *testing.T) {
   251  
   252  	requestURI := ""
   253  
   254  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   255  
   256  		requestURI = req.RequestURI
   257  
   258  		var response ResultData = ResultData{}
   259  
   260  		if strings.Contains(requestURI, "111") {
   261  			response = ResultData{
   262  				Result: Result{ProductID: 111, ReportURL: requestURI}}
   263  		} else {
   264  			response = ResultData{
   265  				Result: Result{ProductID: 222, ReportURL: requestURI}}
   266  		}
   267  
   268  		var b bytes.Buffer
   269  		json.NewEncoder(&b).Encode(&response)
   270  		rw.Write([]byte(b.Bytes()))
   271  	}))
   272  	// Close the server when test finishes
   273  	defer server.Close()
   274  
   275  	cases := []struct {
   276  		pc        Protecode
   277  		productID int
   278  		want      ResultData
   279  	}{
   280  		{makeProtecode(Options{ServerURL: server.URL}), 111, ResultData{Result: Result{ProductID: 111, ReportURL: "/api/product/111/"}}},
   281  		{makeProtecode(Options{ServerURL: server.URL}), 222, ResultData{Result: Result{ProductID: 222, ReportURL: "/api/product/222/"}}},
   282  	}
   283  	for _, c := range cases {
   284  
   285  		got, _ := c.pc.pullResult(c.productID)
   286  		assert.Equal(t, c.want, got)
   287  		assert.Equal(t, fmt.Sprintf("/api/product/%v/", c.productID), requestURI)
   288  	}
   289  }
   290  
   291  func TestDeclareFetchURLSuccess(t *testing.T) {
   292  
   293  	requestURI := ""
   294  	var passedHeaders = map[string][]string{}
   295  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   296  
   297  		requestURI = req.RequestURI
   298  
   299  		passedHeaders = map[string][]string{}
   300  		if req.Header != nil {
   301  			for name, headers := range req.Header {
   302  				passedHeaders[name] = headers
   303  			}
   304  		}
   305  
   306  		response := ResultData{Result: Result{ProductID: 111, ReportURL: requestURI}}
   307  
   308  		var b bytes.Buffer
   309  		json.NewEncoder(&b).Encode(&response)
   310  		rw.Write([]byte(b.Bytes()))
   311  	}))
   312  	// Close the server when test finishes
   313  	defer server.Close()
   314  	pc := makeProtecode(Options{ServerURL: server.URL})
   315  
   316  	cases := []struct {
   317  		cleanupMode       string
   318  		protecodeGroup    string
   319  		customDataJSONMap string
   320  		fetchURL          string
   321  		version           string
   322  		productID         int
   323  		replaceBinary     bool
   324  		want              int
   325  	}{
   326  		{"binary", "group1", `{"custom-header": "custom-value"}`, "/api/fetch/", "", 1, true, 111},
   327  		{"binary", "group1", "", "/api/fetch/", "custom-test-version", -1, true, 111},
   328  		{"binary", "group1", "", "/api/fetch/", "1.2.3", 0, true, 111},
   329  
   330  		{"binary", "group1", "", "/api/fetch/", "", 1, false, 111},
   331  		{"binary", "group1", "", "/api/fetch/", "custom-test-version", -1, false, 111},
   332  		{"binary", "group1", "", "/api/fetch/", "1.2.3", 0, false, 111},
   333  	}
   334  	for _, c := range cases {
   335  
   336  		// pc.DeclareFetchURL(c.cleanupMode, c.protecodeGroup, c.fetchURL)
   337  		got := pc.DeclareFetchURL(c.cleanupMode, c.protecodeGroup, c.customDataJSONMap, c.fetchURL, c.version, c.productID, c.replaceBinary)
   338  
   339  		assert.Equal(t, requestURI, "/api/fetch/")
   340  		assert.Equal(t, got.Result.ProductID, c.want)
   341  		assert.Equal(t, got.Result.Status, "")
   342  
   343  		assert.Contains(t, passedHeaders, "Group")
   344  		assert.Contains(t, passedHeaders, "Delete-Binary")
   345  		assert.Contains(t, passedHeaders, "Url")
   346  
   347  		if c.replaceBinary {
   348  			assert.Contains(t, passedHeaders, "Replace")
   349  		}
   350  
   351  	}
   352  }
   353  
   354  func TestUploadScanFileSuccess(t *testing.T) {
   355  
   356  	requestURI := ""
   357  	var passedHeaders = map[string][]string{}
   358  	var reader io.Reader
   359  	var passedFileContents []byte
   360  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   361  
   362  		requestURI = req.RequestURI
   363  
   364  		passedHeaders = map[string][]string{}
   365  		if req.Header != nil {
   366  			for name, headers := range req.Header {
   367  				passedHeaders[name] = headers
   368  			}
   369  		}
   370  
   371  		response := new(ResultData)
   372  
   373  		err := req.ParseMultipartForm(4096)
   374  		if err == nil {
   375  			reader, _, err = req.FormFile("file")
   376  			if err != nil {
   377  				t.FailNow()
   378  			}
   379  		} else {
   380  			reader = req.Body
   381  		}
   382  
   383  		defer req.Body.Close()
   384  		passedFileContents, err = io.ReadAll(reader)
   385  		if err != nil {
   386  			t.FailNow()
   387  		}
   388  
   389  		// When replace binary option is true then mock server should return same product id with http status code 201 (not 200)
   390  		if strReplaceID, isExist := passedHeaders["Replace"]; isExist {
   391  			// convert string to int
   392  			intReplaceID, _ := strconv.Atoi(strReplaceID[0])
   393  
   394  			response.Result.ProductID = intReplaceID
   395  			response.Result.ReportURL = requestURI
   396  
   397  			rw.WriteHeader(201)
   398  
   399  		} else {
   400  			response.Result.ProductID = 112
   401  			response.Result.ReportURL = requestURI
   402  
   403  			rw.WriteHeader(200)
   404  		}
   405  
   406  		var b bytes.Buffer
   407  		json.NewEncoder(&b).Encode(&response)
   408  		rw.Write([]byte(b.Bytes()))
   409  
   410  	}))
   411  	// Close the server when test finishes
   412  	defer server.Close()
   413  	pc := makeProtecode(Options{ServerURL: server.URL})
   414  
   415  	testFile, err := os.CreateTemp("", "testFileUpload")
   416  	if err != nil {
   417  		t.FailNow()
   418  	}
   419  	defer os.RemoveAll(testFile.Name()) // clean up
   420  
   421  	fileContents, err := os.ReadFile(testFile.Name())
   422  	if err != nil {
   423  		t.FailNow()
   424  	}
   425  
   426  	cases := []struct {
   427  		cleanupMode       string
   428  		protecodeGroup    string
   429  		customDataJSONMap string
   430  		filePath          string
   431  		version           string
   432  		productID         int
   433  		replaceBinary     bool
   434  		want              int
   435  	}{
   436  		{"binary", "group1", `{"custom-header": "custom-value"}`, testFile.Name(), "", 1, true, 1},
   437  		{"binary", "group1", "", testFile.Name(), "custom-test-version", 0, true, 0},
   438  		{"binary", "group1", "", testFile.Name(), "1.2.3", -1, true, -1},
   439  
   440  		{"binary", "group1", "", testFile.Name(), "", 1, false, 112},
   441  		{"binary", "group1", "", testFile.Name(), "custom-test-version", 0, false, 112},
   442  		{"binary", "group1", "", testFile.Name(), "1.2.3", -1, false, 112},
   443  
   444  		// {"binary", "group1", testFile.Name(), "/api/upload/dummy"},
   445  		// {"Test", "group2", testFile.Name(), "/api/upload/dummy"},
   446  	}
   447  	for _, c := range cases {
   448  
   449  		got := pc.UploadScanFile(c.cleanupMode, c.protecodeGroup, c.customDataJSONMap, c.filePath, "dummy.tar", c.version, c.productID, c.replaceBinary)
   450  
   451  		assert.Equal(t, requestURI, "/api/upload/dummy.tar")
   452  		assert.Contains(t, passedHeaders, "Group")
   453  		assert.Contains(t, passedHeaders, "Delete-Binary")
   454  		assert.Equal(t, fileContents, passedFileContents, "Uploaded file incorrect")
   455  		assert.Equal(t, c.want, got.Result.ProductID)
   456  		assert.Equal(t, "", got.Result.Status)
   457  
   458  		if c.replaceBinary {
   459  			assert.Contains(t, passedHeaders, "Replace")
   460  		}
   461  	}
   462  }
   463  
   464  func TestLoadReportSuccess(t *testing.T) {
   465  
   466  	requestURI := ""
   467  	var passedHeaders = map[string][]string{}
   468  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   469  
   470  		requestURI = req.RequestURI
   471  
   472  		passedHeaders = map[string][]string{}
   473  		if req.Header != nil {
   474  			for name, headers := range req.Header {
   475  				passedHeaders[name] = headers
   476  			}
   477  		}
   478  
   479  		rw.Write([]byte("OK"))
   480  	}))
   481  	// Close the server when test finishes
   482  	defer server.Close()
   483  
   484  	pc := makeProtecode(Options{ServerURL: server.URL})
   485  
   486  	cases := []struct {
   487  		productID      int
   488  		reportFileName string
   489  		want           string
   490  	}{
   491  		{1, "fileName", "/api/product/1/pdf-report"},
   492  		{2, "fileName", "/api/product/2/pdf-report"},
   493  	}
   494  	for _, c := range cases {
   495  
   496  		pc.LoadReport(c.reportFileName, c.productID)
   497  		assert.Equal(t, requestURI, c.want)
   498  		assert.Contains(t, passedHeaders, "Outputfile")
   499  		assert.Contains(t, passedHeaders, "Pragma")
   500  		assert.Contains(t, passedHeaders, "Cache-Control")
   501  	}
   502  }
   503  
   504  func TestDeleteScanSuccess(t *testing.T) {
   505  
   506  	requestURI := ""
   507  	var passedHeaders = map[string][]string{}
   508  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   509  
   510  		requestURI = req.RequestURI
   511  
   512  		passedHeaders = map[string][]string{}
   513  		if req.Header != nil {
   514  			for name, headers := range req.Header {
   515  				passedHeaders[name] = headers
   516  			}
   517  		}
   518  
   519  		rw.Write([]byte("OK"))
   520  	}))
   521  	// Close the server when test finishes
   522  	defer server.Close()
   523  
   524  	pc := makeProtecode(Options{})
   525  	po := Options{ServerURL: server.URL}
   526  	pc.SetOptions(po)
   527  
   528  	cases := []struct {
   529  		cleanupMode string
   530  		productID   int
   531  		want        string
   532  	}{
   533  		{"binary", 1, ""},
   534  		{"complete", 2, "/api/product/2/"},
   535  	}
   536  	for _, c := range cases {
   537  
   538  		pc.DeleteScan(c.cleanupMode, c.productID)
   539  		assert.Equal(t, requestURI, c.want)
   540  		if c.cleanupMode == "complete" {
   541  			assert.Contains(t, requestURI, fmt.Sprintf("%v", c.productID))
   542  		}
   543  	}
   544  }