mirror of
https://github.com/SAP/jenkins-library.git
synced 2026-06-19 22:58:55 +02:00
316 lines
9.1 KiB
Go
316 lines
9.1 KiB
Go
package cmd
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/SAP/jenkins-library/pkg/contrast"
|
|
"github.com/SAP/jenkins-library/pkg/mock"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type contrastExecuteScanMockUtils struct {
|
|
*mock.ExecMockRunner
|
|
*mock.FilesMock
|
|
}
|
|
|
|
func newContrastExecuteScanTestsUtils() contrastExecuteScanMockUtils {
|
|
utils := contrastExecuteScanMockUtils{
|
|
ExecMockRunner: &mock.ExecMockRunner{},
|
|
FilesMock: &mock.FilesMock{},
|
|
}
|
|
return utils
|
|
}
|
|
|
|
func TestGetAuth(t *testing.T) {
|
|
t.Run("Success", func(t *testing.T) {
|
|
config := &contrastExecuteScanOptions{
|
|
UserAPIKey: "user-api-key",
|
|
Username: "username",
|
|
ServiceKey: "service-key",
|
|
}
|
|
authString := getAuth(config)
|
|
assert.NotEmpty(t, authString)
|
|
data, err := base64.StdEncoding.DecodeString(authString)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "username:service-key", string(data))
|
|
})
|
|
}
|
|
|
|
func TestGetApplicationUrls(t *testing.T) {
|
|
t.Run("Success", func(t *testing.T) {
|
|
config := &contrastExecuteScanOptions{
|
|
Server: "https://server.com",
|
|
OrganizationID: "orgId",
|
|
ApplicationID: "appId",
|
|
}
|
|
appUrl, guiUrl := getApplicationUrls(config)
|
|
assert.Equal(t, "https://server.com/api/v4/organizations/orgId/applications/appId", appUrl)
|
|
assert.Equal(t, "https://server.com/Contrast/static/ng/index.html#/orgId/applications/appId", guiUrl)
|
|
})
|
|
}
|
|
|
|
func TestValidateConfigs(t *testing.T) {
|
|
t.Parallel()
|
|
validConfig := contrastExecuteScanOptions{
|
|
UserAPIKey: "user-api-key",
|
|
ServiceKey: "service-key",
|
|
Username: "username",
|
|
Server: "https://server.com",
|
|
OrganizationID: "orgId",
|
|
ApplicationID: "appId",
|
|
}
|
|
|
|
t.Run("Valid config", func(t *testing.T) {
|
|
config := validConfig
|
|
err := validateConfigs(&config)
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("Valid config, server url without https://", func(t *testing.T) {
|
|
config := validConfig
|
|
config.Server = "server.com"
|
|
err := validateConfigs(&config)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, config.Server, "https://server.com")
|
|
})
|
|
|
|
t.Run("Empty config", func(t *testing.T) {
|
|
config := contrastExecuteScanOptions{}
|
|
|
|
err := validateConfigs(&config)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("Empty userAPIKey", func(t *testing.T) {
|
|
config := validConfig
|
|
config.UserAPIKey = ""
|
|
|
|
err := validateConfigs(&config)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("Empty username", func(t *testing.T) {
|
|
config := validConfig
|
|
config.Username = ""
|
|
|
|
err := validateConfigs(&config)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("Empty serviceKey", func(t *testing.T) {
|
|
config := validConfig
|
|
config.ServiceKey = ""
|
|
|
|
err := validateConfigs(&config)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("Empty server", func(t *testing.T) {
|
|
config := validConfig
|
|
config.Server = ""
|
|
|
|
err := validateConfigs(&config)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("Empty organizationId", func(t *testing.T) {
|
|
config := validConfig
|
|
config.OrganizationID = ""
|
|
|
|
err := validateConfigs(&config)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("Empty applicationID", func(t *testing.T) {
|
|
config := validConfig
|
|
config.ApplicationID = ""
|
|
|
|
err := validateConfigs(&config)
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
|
|
// Test constants for mock and end-to-end tests
|
|
const (
|
|
// Mock test constants
|
|
mockContrastAPIKey = "mock-api-key"
|
|
mockContrastServiceKey = "mock-service-key"
|
|
mockContrastUsername = "mock@example.com"
|
|
mockContrastOrgID = "org-mock-123"
|
|
mockContrastServerURL = "https://mock.contrastsecurity.com"
|
|
mockContrastAppID = "app-mock-456"
|
|
|
|
// End-to-end test constants - Fill these with your real values for end-to-end testing
|
|
e2eContrastAPIKey = "YOUR_API_KEY"
|
|
e2eContrastServiceKey = "YOUR_SERVICE_KEY"
|
|
e2eContrastUsername = "YOUR_USERNAME"
|
|
e2eContrastOrgID = "YOUR_ORG_ID"
|
|
e2eContrastServerURL = "https://YOUR_SERVER.contrastsecurity.com"
|
|
e2eContrastAppID = "YOUR_APP_ID"
|
|
)
|
|
|
|
// Mock-based unit tests (no real credentials needed)
|
|
|
|
func TestGenerateSarifReportMockSuccess(t *testing.T) {
|
|
// Setup mock HTTP server
|
|
serverURL := ""
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
if strings.Contains(path, "/sarif/async") {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"success": true, "uuid": "test-sarif-uuid"}`))
|
|
} else if strings.Contains(path, "/status") {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"success": true, "status": "ACTIVE", "downloadUrl": "` + serverURL + `/download"}`))
|
|
} else if strings.Contains(path, "/download") {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"version": "2.1.0"}`))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
serverURL = server.URL
|
|
|
|
mockConfig := &contrastExecuteScanOptions{
|
|
UserAPIKey: mockContrastAPIKey,
|
|
ServiceKey: mockContrastServiceKey,
|
|
Username: mockContrastUsername,
|
|
OrganizationID: mockContrastOrgID,
|
|
Server: server.URL,
|
|
ApplicationID: mockContrastAppID,
|
|
}
|
|
|
|
mockUtils := newContrastExecuteScanTestsUtils()
|
|
mockClient := contrast.NewClient(
|
|
mockContrastAPIKey,
|
|
mockContrastServiceKey,
|
|
mockContrastUsername,
|
|
mockContrastOrgID,
|
|
server.URL,
|
|
server.URL+"/api/v4/organizations/"+mockContrastOrgID+"/applications/"+mockContrastAppID,
|
|
)
|
|
|
|
reports, err := generateSarifReport(mockConfig, mockUtils, mockClient)
|
|
|
|
assert.NoError(t, err, "generateSarifReport should not return error")
|
|
assert.NotEmpty(t, reports, "Expected reports to be generated")
|
|
assert.Equal(t, 1, len(reports))
|
|
assert.Equal(t, "Contrast SARIF Report", reports[0].Name)
|
|
}
|
|
|
|
func TestGeneratePdfReportMockSuccess(t *testing.T) {
|
|
// Setup mock HTTP server
|
|
serverURL := ""
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
if strings.Contains(path, "/attestation") {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"success": true, "uuid": "test-pdf-uuid"}`))
|
|
} else if strings.Contains(path, "/status") {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"success": true, "status": "ACTIVE", "downloadUrl": "` + serverURL + `/download"}`))
|
|
} else if strings.Contains(path, "/download") {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("PDF content"))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
serverURL = server.URL
|
|
|
|
mockConfig := &contrastExecuteScanOptions{
|
|
UserAPIKey: mockContrastAPIKey,
|
|
ServiceKey: mockContrastServiceKey,
|
|
Username: mockContrastUsername,
|
|
OrganizationID: mockContrastOrgID,
|
|
Server: server.URL,
|
|
ApplicationID: mockContrastAppID,
|
|
}
|
|
|
|
mockUtils := newContrastExecuteScanTestsUtils()
|
|
mockClient := contrast.NewClient(
|
|
mockContrastAPIKey,
|
|
mockContrastServiceKey,
|
|
mockContrastUsername,
|
|
mockContrastOrgID,
|
|
server.URL,
|
|
server.URL+"/api/v4/organizations/"+mockContrastOrgID+"/applications/"+mockContrastAppID,
|
|
)
|
|
|
|
reports, err := generatePdfReport(mockConfig, mockUtils, mockClient)
|
|
|
|
assert.NoError(t, err, "generatePdfReport should not return error")
|
|
assert.NotEmpty(t, reports, "Expected reports to be generated")
|
|
assert.Equal(t, 1, len(reports))
|
|
assert.Equal(t, "Contrast PDF Attestation Report", reports[0].Name)
|
|
}
|
|
|
|
// TestContrastExecuteScanEndToEnd performs an end-to-end test of the runContrastExecuteScan function.
|
|
// It requires valid Contrast credentials to be set in the constants above.
|
|
// This test is skipped if the credentials are not filled in.
|
|
func TestContrastExecuteScanEndToEnd(t *testing.T) {
|
|
if e2eContrastAPIKey == "YOUR_API_KEY" {
|
|
t.Skip("Skipping end-to-end test: Contrast credentials not provided.")
|
|
}
|
|
|
|
outputDir := "./contrast-e2e-output"
|
|
_ = os.RemoveAll(outputDir) // Best-effort cleanup
|
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create output directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(outputDir)
|
|
|
|
oldCWD, _ := os.Getwd()
|
|
err := os.Chdir(outputDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to change directory: %v", err)
|
|
}
|
|
defer os.Chdir(oldCWD)
|
|
|
|
config := contrastExecuteScanOptions{
|
|
Server: e2eContrastServerURL,
|
|
OrganizationID: e2eContrastOrgID,
|
|
ApplicationID: e2eContrastAppID,
|
|
UserAPIKey: e2eContrastAPIKey,
|
|
Username: e2eContrastUsername,
|
|
ServiceKey: e2eContrastServiceKey,
|
|
GenerateSarif: true,
|
|
GeneratePdf: true,
|
|
}
|
|
|
|
utils := newContrastExecuteScanUtils()
|
|
|
|
reports, err := runContrastExecuteScan(&config, nil, utils)
|
|
|
|
assert.NoError(t, err, "runContrastExecuteScan should not return an error")
|
|
assert.NotEmpty(t, reports, "Expected reports to be generated")
|
|
|
|
// Verify SARIF report
|
|
sarifPath := filepath.Join(".", "contrast", "piper_contrast.sarif")
|
|
assert.FileExists(t, sarifPath, "SARIF report file should exist")
|
|
foundSarif := false
|
|
for _, report := range reports {
|
|
if filepath.Clean(report.Target) == filepath.Clean(sarifPath) {
|
|
foundSarif = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, foundSarif, "SARIF report should be in the returned reports list")
|
|
|
|
// Verify PDF report
|
|
pdfPath := filepath.Join(".", "contrast", "piper_contrast_attestation.pdf")
|
|
assert.FileExists(t, pdfPath, "PDF report file should exist")
|
|
foundPdf := false
|
|
for _, report := range reports {
|
|
if filepath.Clean(report.Target) == filepath.Clean(pdfPath) {
|
|
foundPdf = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, foundPdf, "PDF report should be in the returned reports list")
|
|
}
|