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 }