mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-03-27 21:49:15 +02:00
feat: Introduce new npmExecuteTests step (#5124)
This commit is contained in:
parent
85dfe3e18d
commit
531a0b8cfe
@ -138,6 +138,9 @@ func GetStageConfig() (config.StepConfig, error) {
|
||||
|
||||
defaultConfig := []io.ReadCloser{}
|
||||
for _, f := range GeneralConfig.DefaultConfig {
|
||||
if configOptions.OpenFile == nil {
|
||||
return stepConfig, errors.New("config: open file function not set")
|
||||
}
|
||||
fc, err := configOptions.OpenFile(f, GeneralConfig.GitHubAccessTokens)
|
||||
// only create error for non-default values
|
||||
if err != nil && f != ".pipeline/defaults.yaml" {
|
||||
|
@ -103,6 +103,7 @@ func GetAllStepMetadata() map[string]config.StepData {
|
||||
"nexusUpload": nexusUploadMetadata(),
|
||||
"npmExecuteLint": npmExecuteLintMetadata(),
|
||||
"npmExecuteScripts": npmExecuteScriptsMetadata(),
|
||||
"npmExecuteTests": npmExecuteTestsMetadata(),
|
||||
"pipelineCreateScanSummary": pipelineCreateScanSummaryMetadata(),
|
||||
"protecodeExecuteScan": protecodeExecuteScanMetadata(),
|
||||
"pythonBuild": pythonBuildMetadata(),
|
||||
|
@ -87,6 +87,7 @@ func TestMtaBuild(t *testing.T) {
|
||||
SetConfigOptions(ConfigCommandOptions{
|
||||
OpenFile: config.OpenPiperFile,
|
||||
})
|
||||
|
||||
t.Run("Application name not set", func(t *testing.T) {
|
||||
utilsMock := newMtaBuildTestUtilsBundle()
|
||||
options := mtaBuildOptions{}
|
||||
|
115
cmd/npmExecuteTests.go
Normal file
115
cmd/npmExecuteTests.go
Normal file
@ -0,0 +1,115 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
)
|
||||
|
||||
type vaultUrl struct {
|
||||
URL string `json:"url"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
func npmExecuteTests(config npmExecuteTestsOptions, _ *telemetry.CustomData) {
|
||||
c := command.Command{}
|
||||
|
||||
c.Stdout(log.Writer())
|
||||
c.Stderr(log.Writer())
|
||||
err := runNpmExecuteTests(&config, &c)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("Step execution failed")
|
||||
}
|
||||
}
|
||||
|
||||
func runNpmExecuteTests(config *npmExecuteTestsOptions, c command.ExecRunner) error {
|
||||
if len(config.Envs) > 0 {
|
||||
c.SetEnv(config.Envs)
|
||||
}
|
||||
|
||||
if len(config.Paths) > 0 {
|
||||
path := fmt.Sprintf("PATH=%s:%s", os.Getenv("PATH"), strings.Join(config.Paths, ":"))
|
||||
c.SetEnv([]string{path})
|
||||
}
|
||||
|
||||
if config.WorkingDirectory != "" {
|
||||
if err := os.Chdir(config.WorkingDirectory); err != nil {
|
||||
return fmt.Errorf("failed to change directory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
installCommandTokens := strings.Fields(config.InstallCommand)
|
||||
if err := c.RunExecutable(installCommandTokens[0], installCommandTokens[1:]...); err != nil {
|
||||
return fmt.Errorf("failed to execute install command: %w", err)
|
||||
}
|
||||
|
||||
parsedURLs, err := parseURLs(config.VaultURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, app := range parsedURLs {
|
||||
if err := runTestForUrl(app.URL, app.Username, app.Password, config, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := runTestForUrl(config.BaseURL, config.VaultUsername, config.VaultPassword, config, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runTestForUrl(url, username, password string, config *npmExecuteTestsOptions, command command.ExecRunner) error {
|
||||
credentialsToEnv(username, password, config.UsernameEnvVar, config.PasswordEnvVar, command)
|
||||
// we need to reset the env vars as the next test might not have any credentials
|
||||
defer resetCredentials(config.UsernameEnvVar, config.PasswordEnvVar, command)
|
||||
|
||||
runScriptTokens := strings.Fields(config.RunCommand)
|
||||
if config.UrlOptionPrefix != "" {
|
||||
runScriptTokens = append(runScriptTokens, config.UrlOptionPrefix+url)
|
||||
}
|
||||
if err := command.RunExecutable(runScriptTokens[0], runScriptTokens[1:]...); err != nil {
|
||||
return fmt.Errorf("failed to execute npm script: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseURLs(urls []map[string]interface{}) ([]vaultUrl, error) {
|
||||
parsedUrls := []vaultUrl{}
|
||||
|
||||
for _, url := range urls {
|
||||
parsedUrl := vaultUrl{}
|
||||
urlStr, ok := url["url"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("url field is not a string")
|
||||
}
|
||||
parsedUrl.URL = urlStr
|
||||
if username, ok := url["username"].(string); ok {
|
||||
parsedUrl.Username = username
|
||||
}
|
||||
|
||||
if password, ok := url["password"].(string); ok {
|
||||
parsedUrl.Password = password
|
||||
}
|
||||
parsedUrls = append(parsedUrls, parsedUrl)
|
||||
}
|
||||
return parsedUrls, nil
|
||||
}
|
||||
|
||||
func credentialsToEnv(username, password, usernameEnv, passwordEnv string, c command.ExecRunner) {
|
||||
if username == "" || password == "" {
|
||||
return
|
||||
}
|
||||
c.SetEnv([]string{usernameEnv + "=" + username, passwordEnv + "=" + password})
|
||||
}
|
||||
|
||||
func resetCredentials(usernameEnv, passwordEnv string, c command.ExecRunner) {
|
||||
c.SetEnv([]string{usernameEnv + "=", passwordEnv + "="})
|
||||
}
|
376
cmd/npmExecuteTests_generated.go
Normal file
376
cmd/npmExecuteTests_generated.go
Normal file
@ -0,0 +1,376 @@
|
||||
// Code generated by piper's step-generator. DO NOT EDIT.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/SAP/jenkins-library/pkg/gcp"
|
||||
"github.com/SAP/jenkins-library/pkg/gcs"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/splunk"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/SAP/jenkins-library/pkg/validation"
|
||||
"github.com/bmatcuk/doublestar"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type npmExecuteTestsOptions struct {
|
||||
InstallCommand string `json:"installCommand,omitempty"`
|
||||
RunCommand string `json:"runCommand,omitempty"`
|
||||
VaultURLs []map[string]interface{} `json:"vaultURLs,omitempty"`
|
||||
VaultUsername string `json:"vaultUsername,omitempty"`
|
||||
VaultPassword string `json:"vaultPassword,omitempty"`
|
||||
BaseURL string `json:"baseUrl,omitempty"`
|
||||
UsernameEnvVar string `json:"usernameEnvVar,omitempty"`
|
||||
PasswordEnvVar string `json:"passwordEnvVar,omitempty"`
|
||||
UrlOptionPrefix string `json:"urlOptionPrefix,omitempty"`
|
||||
Envs []string `json:"envs,omitempty"`
|
||||
Paths []string `json:"paths,omitempty"`
|
||||
WorkingDirectory string `json:"workingDirectory,omitempty"`
|
||||
}
|
||||
|
||||
type npmExecuteTestsReports struct {
|
||||
}
|
||||
|
||||
func (p *npmExecuteTestsReports) persist(stepConfig npmExecuteTestsOptions, gcpJsonKeyFilePath string, gcsBucketId string, gcsFolderPath string, gcsSubFolder string) {
|
||||
if gcsBucketId == "" {
|
||||
log.Entry().Info("persisting reports to GCS is disabled, because gcsBucketId is empty")
|
||||
return
|
||||
}
|
||||
log.Entry().Info("Uploading reports to Google Cloud Storage...")
|
||||
content := []gcs.ReportOutputParam{
|
||||
{FilePattern: "**/e2e-results.xml", ParamRef: "", StepResultType: "end-to-end-test"},
|
||||
}
|
||||
envVars := []gcs.EnvVar{
|
||||
{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: gcpJsonKeyFilePath, Modified: false},
|
||||
}
|
||||
gcsClient, err := gcs.NewClient(gcs.WithEnvVars(envVars))
|
||||
if err != nil {
|
||||
log.Entry().Errorf("creation of GCS client failed: %v", err)
|
||||
return
|
||||
}
|
||||
defer gcsClient.Close()
|
||||
structVal := reflect.ValueOf(&stepConfig).Elem()
|
||||
inputParameters := map[string]string{}
|
||||
for i := 0; i < structVal.NumField(); i++ {
|
||||
field := structVal.Type().Field(i)
|
||||
if field.Type.String() == "string" {
|
||||
paramName := strings.Split(field.Tag.Get("json"), ",")
|
||||
paramValue, _ := structVal.Field(i).Interface().(string)
|
||||
inputParameters[paramName[0]] = paramValue
|
||||
}
|
||||
}
|
||||
if err := gcs.PersistReportsToGCS(gcsClient, content, inputParameters, gcsFolderPath, gcsBucketId, gcsSubFolder, doublestar.Glob, os.Stat); err != nil {
|
||||
log.Entry().Errorf("failed to persist reports: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// NpmExecuteTestsCommand Executes end-to-end tests using npm
|
||||
func NpmExecuteTestsCommand() *cobra.Command {
|
||||
const STEP_NAME = "npmExecuteTests"
|
||||
|
||||
metadata := npmExecuteTestsMetadata()
|
||||
var stepConfig npmExecuteTestsOptions
|
||||
var startTime time.Time
|
||||
var reports npmExecuteTestsReports
|
||||
var logCollector *log.CollectorHook
|
||||
var splunkClient *splunk.Splunk
|
||||
telemetryClient := &telemetry.Telemetry{}
|
||||
|
||||
var createNpmExecuteTestsCmd = &cobra.Command{
|
||||
Use: STEP_NAME,
|
||||
Short: "Executes end-to-end tests using npm",
|
||||
Long: `This step executes end-to-end tests in a Docker environment using npm.
|
||||
|
||||
The step spins up a Docker container based on the specified ` + "`" + `dockerImage` + "`" + ` and executes the ` + "`" + `installScript` + "`" + ` and ` + "`" + `runScript` + "`" + ` from ` + "`" + `package.json` + "`" + `.
|
||||
|
||||
The application URLs and credentials can be specified in ` + "`" + `appUrls` + "`" + ` and ` + "`" + `credentialsId` + "`" + ` respectively. If ` + "`" + `wdi5` + "`" + ` is set to ` + "`" + `true` + "`" + `, the step uses ` + "`" + `wdi5_username` + "`" + ` and ` + "`" + `wdi5_password` + "`" + ` for authentication.
|
||||
|
||||
The tests can be restricted to run only on the productive branch by setting ` + "`" + `onlyRunInProductiveBranch` + "`" + ` to ` + "`" + `true` + "`" + `.`,
|
||||
PreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
startTime = time.Now()
|
||||
log.SetStepName(STEP_NAME)
|
||||
log.SetVerbose(GeneralConfig.Verbose)
|
||||
|
||||
GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens)
|
||||
|
||||
path, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path}
|
||||
log.RegisterHook(fatalHook)
|
||||
|
||||
err = PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
|
||||
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
|
||||
log.RegisterHook(&sentryHook)
|
||||
}
|
||||
|
||||
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 || len(GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint) > 0 {
|
||||
splunkClient = &splunk.Splunk{}
|
||||
logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID}
|
||||
log.RegisterHook(logCollector)
|
||||
}
|
||||
|
||||
if err = log.RegisterANSHookIfConfigured(GeneralConfig.CorrelationID); err != nil {
|
||||
log.Entry().WithError(err).Warn("failed to set up SAP Alert Notification Service log hook")
|
||||
}
|
||||
|
||||
validation, err := validation.New(validation.WithJSONNamesForStructFields(), validation.WithPredefinedErrorMessages())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = validation.ValidateStruct(stepConfig); err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
vaultClient := config.GlobalVaultClient()
|
||||
if vaultClient != nil {
|
||||
defer vaultClient.MustRevokeToken()
|
||||
}
|
||||
|
||||
stepTelemetryData := telemetry.CustomData{}
|
||||
stepTelemetryData.ErrorCode = "1"
|
||||
handler := func() {
|
||||
reports.persist(stepConfig, GeneralConfig.GCPJsonKeyFilePath, GeneralConfig.GCSBucketId, GeneralConfig.GCSFolderPath, GeneralConfig.GCSSubFolder)
|
||||
config.RemoveVaultSecretFiles()
|
||||
stepTelemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
|
||||
stepTelemetryData.ErrorCategory = log.GetErrorCategory().String()
|
||||
stepTelemetryData.PiperCommitHash = GitCommit
|
||||
telemetryClient.SetData(&stepTelemetryData)
|
||||
telemetryClient.Send()
|
||||
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
|
||||
splunkClient.Initialize(GeneralConfig.CorrelationID,
|
||||
GeneralConfig.HookConfig.SplunkConfig.Dsn,
|
||||
GeneralConfig.HookConfig.SplunkConfig.Token,
|
||||
GeneralConfig.HookConfig.SplunkConfig.Index,
|
||||
GeneralConfig.HookConfig.SplunkConfig.SendLogs)
|
||||
splunkClient.Send(telemetryClient.GetData(), logCollector)
|
||||
}
|
||||
if len(GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint) > 0 {
|
||||
splunkClient.Initialize(GeneralConfig.CorrelationID,
|
||||
GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint,
|
||||
GeneralConfig.HookConfig.SplunkConfig.ProdCriblToken,
|
||||
GeneralConfig.HookConfig.SplunkConfig.ProdCriblIndex,
|
||||
GeneralConfig.HookConfig.SplunkConfig.SendLogs)
|
||||
splunkClient.Send(telemetryClient.GetData(), logCollector)
|
||||
}
|
||||
if GeneralConfig.HookConfig.GCPPubSubConfig.Enabled {
|
||||
err := gcp.NewGcpPubsubClient(
|
||||
vaultClient,
|
||||
GeneralConfig.HookConfig.GCPPubSubConfig.ProjectNumber,
|
||||
GeneralConfig.HookConfig.GCPPubSubConfig.IdentityPool,
|
||||
GeneralConfig.HookConfig.GCPPubSubConfig.IdentityProvider,
|
||||
GeneralConfig.CorrelationID,
|
||||
GeneralConfig.HookConfig.OIDCConfig.RoleID,
|
||||
).Publish(GeneralConfig.HookConfig.GCPPubSubConfig.Topic, telemetryClient.GetDataBytes())
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Warn("event publish failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
log.DeferExitHandler(handler)
|
||||
defer handler()
|
||||
telemetryClient.Initialize(GeneralConfig.NoTelemetry, STEP_NAME, GeneralConfig.HookConfig.PendoConfig.Token)
|
||||
npmExecuteTests(stepConfig, &stepTelemetryData)
|
||||
stepTelemetryData.ErrorCode = "0"
|
||||
log.Entry().Info("SUCCESS")
|
||||
},
|
||||
}
|
||||
|
||||
addNpmExecuteTestsFlags(createNpmExecuteTestsCmd, &stepConfig)
|
||||
return createNpmExecuteTestsCmd
|
||||
}
|
||||
|
||||
func addNpmExecuteTestsFlags(cmd *cobra.Command, stepConfig *npmExecuteTestsOptions) {
|
||||
cmd.Flags().StringVar(&stepConfig.InstallCommand, "installCommand", `npm ci`, "Command to be executed for installation`.")
|
||||
cmd.Flags().StringVar(&stepConfig.RunCommand, "runCommand", `npm run wdi5`, "Command to be executed for running tests`.")
|
||||
|
||||
cmd.Flags().StringVar(&stepConfig.VaultUsername, "vaultUsername", os.Getenv("PIPER_vaultUsername"), "The base URL username.")
|
||||
cmd.Flags().StringVar(&stepConfig.VaultPassword, "vaultPassword", os.Getenv("PIPER_vaultPassword"), "The base URL password.")
|
||||
cmd.Flags().StringVar(&stepConfig.BaseURL, "baseUrl", `http://localhost:8080/index.html`, "Base URL of the application to be tested.")
|
||||
cmd.Flags().StringVar(&stepConfig.UsernameEnvVar, "usernameEnvVar", `wdi5_username`, "Env var for username.")
|
||||
cmd.Flags().StringVar(&stepConfig.PasswordEnvVar, "passwordEnvVar", `wdi5_password`, "Env var for password.")
|
||||
cmd.Flags().StringVar(&stepConfig.UrlOptionPrefix, "urlOptionPrefix", os.Getenv("PIPER_urlOptionPrefix"), "If you want to specify an extra option that the tested url it appended to.\nFor example if the test URL is `http://localhost and urlOptionPrefix is `--base-url=`,\nwe'll add `--base-url=http://localhost` to your runScript.\n")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.Envs, "envs", []string{}, "List of environment variables to be set")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.Paths, "paths", []string{}, "List of paths to be added to $PATH")
|
||||
cmd.Flags().StringVar(&stepConfig.WorkingDirectory, "workingDirectory", `.`, "Directory where your tests are located relative to the root of your project")
|
||||
|
||||
cmd.MarkFlagRequired("runCommand")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func npmExecuteTestsMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "npmExecuteTests",
|
||||
Aliases: []config.Alias{},
|
||||
Description: "Executes end-to-end tests using npm",
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "installCommand",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: `npm ci`,
|
||||
},
|
||||
{
|
||||
Name: "runCommand",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
Default: `npm run wdi5`,
|
||||
},
|
||||
{
|
||||
Name: "vaultURLs",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "appMetadataVaultSecretName",
|
||||
Type: "vaultSecret",
|
||||
Default: "appMetadata",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "[]map[string]interface{}",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "vaultUsername",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "appMetadataVaultSecretName",
|
||||
Type: "vaultSecret",
|
||||
Default: "appMetadata",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: os.Getenv("PIPER_vaultUsername"),
|
||||
},
|
||||
{
|
||||
Name: "vaultPassword",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "appMetadataVaultSecretName",
|
||||
Type: "vaultSecret",
|
||||
Default: "appMetadata",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: os.Getenv("PIPER_vaultPassword"),
|
||||
},
|
||||
{
|
||||
Name: "baseUrl",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: `http://localhost:8080/index.html`,
|
||||
},
|
||||
{
|
||||
Name: "usernameEnvVar",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: `wdi5_username`,
|
||||
},
|
||||
{
|
||||
Name: "passwordEnvVar",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: `wdi5_password`,
|
||||
},
|
||||
{
|
||||
Name: "urlOptionPrefix",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: os.Getenv("PIPER_urlOptionPrefix"),
|
||||
},
|
||||
{
|
||||
Name: "envs",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "[]string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: []string{},
|
||||
},
|
||||
{
|
||||
Name: "paths",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "[]string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: []string{},
|
||||
},
|
||||
{
|
||||
Name: "workingDirectory",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: `.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []config.Container{
|
||||
{Name: "node", Image: "node:lts-bookworm", EnvVars: []config.EnvVar{{Name: "BASE_URL", Value: "${{params.baseUrl}}"}, {Name: "CREDENTIALS_ID", Value: "${{params.credentialsId}}"}, {Name: "no_proxy", Value: "localhost,selenium,$no_proxy"}, {Name: "NO_PROXY", Value: "localhost,selenium,$NO_PROXY"}}, WorkingDir: "/home/node"},
|
||||
},
|
||||
Sidecars: []config.Container{
|
||||
{Name: "selenium", Image: "selenium/standalone-chrome", EnvVars: []config.EnvVar{{Name: "NO_PROXY", Value: "localhost,selenium,$NO_PROXY"}, {Name: "no_proxy", Value: "localhost,selenium,$no_proxy"}}},
|
||||
},
|
||||
Outputs: config.StepOutputs{
|
||||
Resources: []config.StepResources{
|
||||
{
|
||||
Name: "reports",
|
||||
Type: "reports",
|
||||
Parameters: []map[string]interface{}{
|
||||
{"filePattern": "**/e2e-results.xml", "type": "end-to-end-test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
20
cmd/npmExecuteTests_generated_test.go
Normal file
20
cmd/npmExecuteTests_generated_test.go
Normal file
@ -0,0 +1,20 @@
|
||||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNpmExecuteTestsCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCmd := NpmExecuteTestsCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procedure
|
||||
assert.Equal(t, "npmExecuteTests", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
88
cmd/npmExecuteTests_test.go
Normal file
88
cmd/npmExecuteTests_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRunNpmExecuteTests(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCmd := NpmExecuteTestsCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procedure
|
||||
assert.Equal(t, "npmExecuteTests", testCmd.Use, "command name incorrect")
|
||||
}
|
||||
|
||||
func TestParseURLs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []map[string]interface{}
|
||||
expected []vaultUrl
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid URLs",
|
||||
input: []map[string]interface{}{
|
||||
{
|
||||
"url": "http://example.com",
|
||||
"username": "user1",
|
||||
"password": "pass1",
|
||||
},
|
||||
{
|
||||
"url": "http://example2.com",
|
||||
},
|
||||
},
|
||||
expected: []vaultUrl{
|
||||
{
|
||||
URL: "http://example.com",
|
||||
Username: "user1",
|
||||
Password: "pass1",
|
||||
},
|
||||
{
|
||||
URL: "http://example2.com",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid URL entry",
|
||||
input: []map[string]interface{}{
|
||||
{
|
||||
"username": "user1",
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid URL field type",
|
||||
input: []map[string]interface{}{
|
||||
{
|
||||
"url": 123,
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Empty URLs",
|
||||
input: []map[string]interface{}{},
|
||||
expected: []vaultUrl{},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseURLs(tt.input)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
10
cmd/piper.go
10
cmd/piper.go
@ -24,14 +24,14 @@ type GeneralConfigOptions struct {
|
||||
CorrelationID string
|
||||
CustomConfig string
|
||||
GitHubTokens []string // list of entries in form of <server>:<token> to allow token authentication for downloading config / defaults
|
||||
DefaultConfig []string //ordered list of Piper default configurations. Can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
|
||||
DefaultConfig []string // ordered list of Piper default configurations. Can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
|
||||
IgnoreCustomDefaults bool
|
||||
ParametersJSON string
|
||||
EnvRootPath string
|
||||
NoTelemetry bool
|
||||
StageName string
|
||||
StepConfigJSON string
|
||||
StepMetadata string //metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
|
||||
StepMetadata string // metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
|
||||
StepName string
|
||||
Verbose bool
|
||||
LogFormat string
|
||||
@ -161,6 +161,7 @@ func Execute() {
|
||||
rootCmd.AddCommand(AbapEnvironmentRunATCCheckCommand())
|
||||
rootCmd.AddCommand(NpmExecuteScriptsCommand())
|
||||
rootCmd.AddCommand(NpmExecuteLintCommand())
|
||||
rootCmd.AddCommand(NpmExecuteTestsCommand())
|
||||
rootCmd.AddCommand(GctsCreateRepositoryCommand())
|
||||
rootCmd.AddCommand(GctsExecuteABAPQualityChecksCommand())
|
||||
rootCmd.AddCommand(GctsExecuteABAPUnitTestsCommand())
|
||||
@ -269,7 +270,6 @@ func addRootFlags(rootCmd *cobra.Command) {
|
||||
rootCmd.PersistentFlags().StringVar(&GeneralConfig.GCSFolderPath, "gcsFolderPath", "", "GCS folder path. One of the components of GCS target folder")
|
||||
rootCmd.PersistentFlags().StringVar(&GeneralConfig.GCSBucketId, "gcsBucketId", "", "Bucket name for Google Cloud Storage")
|
||||
rootCmd.PersistentFlags().StringVar(&GeneralConfig.GCSSubFolder, "gcsSubFolder", "", "Used to logically separate results of the same step result type")
|
||||
|
||||
}
|
||||
|
||||
// ResolveAccessTokens reads a list of tokens in format host:token passed via command line
|
||||
@ -353,7 +353,6 @@ func initStageName(outputToLog bool) {
|
||||
|
||||
// PrepareConfig reads step configuration from various sources and merges it (defaults, config file, flags, ...)
|
||||
func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName string, options interface{}, openFile func(s string, t map[string]string) (io.ReadCloser, error)) error {
|
||||
|
||||
log.SetFormatter(GeneralConfig.LogFormat)
|
||||
|
||||
initStageName(true)
|
||||
@ -398,7 +397,7 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin
|
||||
// use config & defaults
|
||||
var customConfig io.ReadCloser
|
||||
var err error
|
||||
//accept that config file and defaults cannot be loaded since both are not mandatory here
|
||||
// accept that config file and defaults cannot be loaded since both are not mandatory here
|
||||
{
|
||||
projectConfigFile := getProjectConfigFile(GeneralConfig.CustomConfig)
|
||||
if exists, err := piperutils.FileExists(projectConfigFile); exists {
|
||||
@ -625,7 +624,6 @@ func getStepOptionsStructType(stepOptions interface{}) reflect.Type {
|
||||
}
|
||||
|
||||
func getProjectConfigFile(name string) string {
|
||||
|
||||
var altName string
|
||||
if ext := filepath.Ext(name); ext == ".yml" {
|
||||
altName = fmt.Sprintf("%v.yaml", strings.TrimSuffix(name, ext))
|
||||
|
114
documentation/docs/steps/npmExecuteTests.md
Normal file
114
documentation/docs/steps/npmExecuteTests.md
Normal file
@ -0,0 +1,114 @@
|
||||
# ${docGenStepName} (Beta)
|
||||
|
||||
[!WARNING]
|
||||
Please note, that the npmExecuteTests step is in beta state, and there could be breaking changes before we remove the beta notice.
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
## ${docGenConfiguration}
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple example using wdi5
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- name: Test
|
||||
steps:
|
||||
- name: npmExecuteTests
|
||||
type: npmExecuteTests
|
||||
params:
|
||||
baseUrl: "http://example.com/index.html"
|
||||
```
|
||||
|
||||
This will run your wdi5 tests with the given baseUrl.
|
||||
|
||||
### Advanced example using custom test script with credentials using Vault
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- name: Test
|
||||
steps:
|
||||
- name: npmExecuteTests
|
||||
type: npmExecuteTests
|
||||
params:
|
||||
installCommand: "npm install"
|
||||
runCommand: "npm run custom-e2e-test"
|
||||
usernameEnvVar: "e2e_username"
|
||||
passwordEnvVar: "e2e_password"
|
||||
baseUrl: "http://example.com/index.html"
|
||||
urlOptionPrefix: "--base-url="
|
||||
```
|
||||
|
||||
and Vault configuration in PIPELINE-GROUP-<id>/PIPELINE-<id>/appMetadata
|
||||
|
||||
```json
|
||||
{
|
||||
"vaultURLs": [
|
||||
{
|
||||
"url": "http://one.example.com/index.html",
|
||||
"username": "some-username1",
|
||||
"password": "some-password1"
|
||||
},
|
||||
{
|
||||
"url": "http://two.example.com/index.html",
|
||||
"username": "some-username2",
|
||||
"password": "some-password2"
|
||||
}
|
||||
],
|
||||
"vaultUsername": "base-url-username",
|
||||
"vaultPassword": "base-url-password"
|
||||
}
|
||||
```
|
||||
|
||||
This will run your custom install and run script for each URL from secrets and use the given URL like so:
|
||||
|
||||
```shell
|
||||
npm run custom-e2e-test --base-url=http://one.example.com/index.html
|
||||
```
|
||||
|
||||
Each test run will have their own environment variables set:
|
||||
|
||||
```shell
|
||||
e2e_username=some-username1
|
||||
e2e_password=some-password1
|
||||
```
|
||||
|
||||
Environment variables are reset before each test run with their corresponding values from the secrets
|
||||
|
||||
### Custom environment variables and $PATH
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- name: Test
|
||||
steps:
|
||||
- name: npmExecuteTests
|
||||
type: npmExecuteTests
|
||||
params:
|
||||
envs:
|
||||
- "MY_ENV_VAR=value"
|
||||
paths:
|
||||
- "/path/to/add"
|
||||
```
|
||||
|
||||
If you're running uiVeri5 tests, you might need to set additional environment variables or add paths to the $PATH variable. This can be done using the `envs` and `paths` parameters:
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- name: Test
|
||||
steps:
|
||||
- name: npmExecuteTests
|
||||
type: npmExecuteTests
|
||||
params:
|
||||
runCommand: "/home/node/.npm-global/bin/uiveri5"
|
||||
installCommand: "npm install @ui5/uiveri5 --global --quiet"
|
||||
runOptions: ["--seleniumAddress=http://localhost:4444/wd/hub"]
|
||||
usernameEnvVar: "PIPER_SELENIUM_HUB_USER"
|
||||
passwordEnvVar: "PIPER_SELENIUM_HUB_PASSWORD"
|
||||
envs:
|
||||
- "NPM_CONFIG_PREFIX=~/.npm-global"
|
||||
paths:
|
||||
- "~/.npm-global/bin"
|
||||
```
|
@ -155,6 +155,7 @@ nav:
|
||||
- npmExecuteEndToEndTests: steps/npmExecuteEndToEndTests.md
|
||||
- npmExecuteLint: steps/npmExecuteLint.md
|
||||
- npmExecuteScripts: steps/npmExecuteScripts.md
|
||||
- npmExecuteTests: steps/npmExecuteTests.md
|
||||
- pipelineExecute: steps/pipelineExecute.md
|
||||
- pipelineRestartSteps: steps/pipelineRestartSteps.md
|
||||
- pipelineStashFiles: steps/pipelineStashFiles.md
|
||||
|
159
resources/metadata/npmExecuteTests.yaml
Normal file
159
resources/metadata/npmExecuteTests.yaml
Normal file
@ -0,0 +1,159 @@
|
||||
metadata:
|
||||
name: npmExecuteTests
|
||||
description: Executes end-to-end tests using npm
|
||||
longDescription: |
|
||||
This step executes end-to-end tests in a Docker environment using npm.
|
||||
|
||||
The step spins up a Docker container based on the specified `dockerImage` and executes the `installScript` and `runScript` from `package.json`.
|
||||
|
||||
The application URLs and credentials can be specified in `appUrls` and `credentialsId` respectively. If `wdi5` is set to `true`, the step uses `wdi5_username` and `wdi5_password` for authentication.
|
||||
|
||||
The tests can be restricted to run only on the productive branch by setting `onlyRunInProductiveBranch` to `true`.
|
||||
|
||||
spec:
|
||||
inputs:
|
||||
params:
|
||||
- name: installCommand
|
||||
type: string
|
||||
description: Command to be executed for installation`.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: "npm ci"
|
||||
- name: runCommand
|
||||
type: string
|
||||
description: Command to be executed for running tests`.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
default: "npm run wdi5"
|
||||
- name: vaultURLs
|
||||
type: "[]map[string]interface{}"
|
||||
description: |
|
||||
An array of objects, each representing an application URL with associated credentials.
|
||||
Each object must have the following properties:
|
||||
- `url`: The URL of the application.
|
||||
- `username`: The username for accessing the application.
|
||||
- `password`: The password for accessing the application.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
resourceRef:
|
||||
- type: vaultSecret
|
||||
default: appMetadata
|
||||
name: appMetadataVaultSecretName
|
||||
- name: vaultUsername
|
||||
type: "string"
|
||||
description: The base URL username.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
resourceRef:
|
||||
- type: vaultSecret
|
||||
default: appMetadata
|
||||
name: appMetadataVaultSecretName
|
||||
- name: vaultPassword
|
||||
type: "string"
|
||||
description: The base URL password.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
resourceRef:
|
||||
- type: vaultSecret
|
||||
default: appMetadata
|
||||
name: appMetadataVaultSecretName
|
||||
- name: baseUrl
|
||||
type: string
|
||||
default: "http://localhost:8080/index.html"
|
||||
description: Base URL of the application to be tested.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: usernameEnvVar
|
||||
type: string
|
||||
default: "wdi5_username"
|
||||
description: Env var for username.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: passwordEnvVar
|
||||
type: string
|
||||
default: "wdi5_password"
|
||||
description: Env var for password.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: urlOptionPrefix
|
||||
type: string
|
||||
description: |
|
||||
If you want to specify an extra option that the tested url it appended to.
|
||||
For example if the test URL is `http://localhost and urlOptionPrefix is `--base-url=`,
|
||||
we'll add `--base-url=http://localhost` to your runScript.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: envs
|
||||
type: "[]string"
|
||||
description: List of environment variables to be set
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: paths
|
||||
type: "[]string"
|
||||
description: List of paths to be added to $PATH
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: workingDirectory
|
||||
type: string
|
||||
default: "."
|
||||
description: Directory where your tests are located relative to the root of your project
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
outputs:
|
||||
resources:
|
||||
- name: reports
|
||||
type: reports
|
||||
params:
|
||||
- filePattern: "**/e2e-results.xml"
|
||||
type: end-to-end-test
|
||||
containers:
|
||||
- name: node
|
||||
image: node:lts-bookworm
|
||||
env:
|
||||
- name: BASE_URL
|
||||
value: ${{params.baseUrl}}
|
||||
- name: CREDENTIALS_ID
|
||||
value: ${{params.credentialsId}}
|
||||
- name: no_proxy
|
||||
value: localhost,selenium,$no_proxy
|
||||
- name: NO_PROXY
|
||||
value: localhost,selenium,$NO_PROXY
|
||||
workingDir: /home/node
|
||||
sidecars:
|
||||
- image: selenium/standalone-chrome
|
||||
name: selenium
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- mountPath: /dev/shm
|
||||
name: dev-shm
|
||||
env:
|
||||
- name: "NO_PROXY"
|
||||
value: "localhost,selenium,$NO_PROXY"
|
||||
- name: "no_proxy"
|
||||
value: "localhost,selenium,$no_proxy"
|
15
vars/npmExecuteTests.groovy
Normal file
15
vars/npmExecuteTests.groovy
Normal file
@ -0,0 +1,15 @@
|
||||
import groovy.transform.Field
|
||||
|
||||
@Field String STEP_NAME = getClass().getName()
|
||||
@Field String METADATA_FILE = 'metadata/npmExecuteTests.yaml'
|
||||
|
||||
@Field Set GENERAL_CONFIG_KEYS = []
|
||||
|
||||
@Field Set STEP_CONFIG_KEYS = []
|
||||
|
||||
@Field Set PARAMETER_KEYS = []
|
||||
|
||||
void call(Map parameters = [:]) {
|
||||
List credentials = []
|
||||
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user