diff --git a/_tests/integration/version_test.go b/_tests/integration/version_test.go index 3cf9735f1..93344ad0e 100644 --- a/_tests/integration/version_test.go +++ b/_tests/integration/version_test.go @@ -8,6 +8,8 @@ import ( "runtime" "testing" + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/version" "github.com/bitrise-io/go-utils/command" "github.com/stretchr/testify/require" @@ -21,11 +23,11 @@ func Test_VersionOutput(t *testing.T) { expectedOSVersion := fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH) expectedVersionOut := fmt.Sprintf(`version: %s -format version: 13 +format version: %s os: %s go: %s build number: -commit: (devel)`, version.VERSION, expectedOSVersion, runtime.Version()) +commit: (devel)`, version.VERSION, models.FormatVersion, expectedOSVersion, runtime.Version()) require.Equal(t, expectedVersionOut, out) } diff --git a/models/models.go b/models/models.go index be37a437e..3d353ae69 100644 --- a/models/models.go +++ b/models/models.go @@ -11,7 +11,7 @@ import ( const ( // FormatVersion ... - FormatVersion = "13" + FormatVersion = "14" ) // StepListItemModel ... diff --git a/models/models_methods.go b/models/models_methods.go index ca3c13f7a..4a232f33d 100644 --- a/models/models_methods.go +++ b/models/models_methods.go @@ -142,6 +142,12 @@ func (config *BitriseDataModel) Normalize() error { return err } + normalizedTriggerMap, err := config.TriggerMap.Normalized() + if err != nil { + return err + } + config.TriggerMap = normalizedTriggerMap + for _, workflow := range config.Workflows { if err := workflow.Normalize(); err != nil { return err diff --git a/models/models_methods_test.go b/models/models_methods_test.go index 4b7401b6d..5d0afbdc5 100644 --- a/models/models_methods_test.go +++ b/models/models_methods_test.go @@ -1,6 +1,7 @@ package models import ( + "encoding/json" "os" "strings" "testing" @@ -16,6 +17,78 @@ import ( // ---------------------------- // --- Validate +func TestJSONMarshal(t *testing.T) { + tests := []struct { + name string + config string + wantWarns []string + wantErr string + }{ + { + name: "Step inputs are normalized", + config: `format_version: "13" +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + test: + steps: + - script: + inputs: + - content: 'echo "Hello $NAME!"' + opts: + is_expand: false`, + }, + { + name: "Trigger map items are normalized", + config: `format_version: 13 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +trigger_map: +- push_branch: main + enabled: false + workflow: deploy +- push_branch: main + commit_message: "[workflow: test]" + workflow: test +- push_branch: main + changed_files: + regex: '^ios\/.*' + workflow: ios-deploy +- pull_request_target_branch: main + draft_pull_request_enabled: false + workflow: test +- pull_request_target_branch: '*' + pull_request_label: "[workflow: check]" + workflow: check +- tag: '*' + workflow: deploy + +workflows: + wip-deploy: + ios-deploy: + deploy: + test: + check:`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var config BitriseDataModel + require.NoError(t, yaml.Unmarshal([]byte(tt.config), &config)) + + warns, err := config.Validate() + require.Empty(t, warns) + require.NoError(t, err) + + err = config.Normalize() + require.NoError(t, err) + + _, err = json.Marshal(config) + require.NoError(t, err) + }) + } +} + // Config func TestValidateConfig(t *testing.T) { t.Log("Valid bitriseData") @@ -385,6 +458,109 @@ workflows: } // Trigger map +func TestValidateTriggerMap(t *testing.T) { + tests := []struct { + name string + config string + wantWarns []string + wantErr string + }{ + { + name: "valid legacy trigger map", + config: ` +format_version: 13 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +trigger_map: +- pattern: main + workflow: deploy +- pattern: "*" + is_pull_request_allowed: true + workflow: test + +workflows: + deploy: + test: +`, + }, + { + name: "valid trigger map", + config: ` +format_version: 13 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +trigger_map: +- push_branch: main + workflow: deploy +- pull_request_target_branch: main + workflow: test +- pull_request_target_branch: '*' + workflow: check +- tag: '*' + workflow: deploy + +workflows: + deploy: + test: + check: +`, + }, + { + name: "valid advanced trigger map", + config: ` +format_version: 13 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +trigger_map: +- push_branch: main + enabled: false + workflow: deploy +- push_branch: main + commit_message: "[workflow: test]" + workflow: test +- push_branch: main + changed_files: + regex: '^ios\/.*' + workflow: ios-deploy +- pull_request_target_branch: main + draft_pull_request_enabled: false + workflow: test +- pull_request_target_branch: '*' + pull_request_label: "[workflow: check]" + workflow: check +- tag: '*' + workflow: deploy + +workflows: + wip-deploy: + ios-deploy: + deploy: + test: + check: +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var config BitriseDataModel + require.NoError(t, yaml.Unmarshal([]byte(tt.config), &config)) + + warns, err := config.Validate() + if len(tt.wantWarns) > 0 { + require.Equal(t, tt.wantWarns, warns) + } else { + require.Empty(t, warns) + } + + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + } else { + require.NoError(t, err) + } + }) + } +} + func TestTriggerMapItemValidate(t *testing.T) { t.Log("utility workflow triggered - Warning") { @@ -405,7 +581,7 @@ workflows: warnings, err := config.Validate() require.NoError(t, err) - require.Equal(t, []string{"workflow (_deps-update) defined in trigger item (push_branch: /release -> workflow: _deps-update), but utility workflows can't be triggered directly"}, warnings) + require.Equal(t, []string{"trigger item #1: utility workflow (_deps-update) defined as trigger target, but utility workflows can't be triggered directly"}, warnings) } t.Log("pipeline not exists") @@ -436,7 +612,7 @@ workflows: require.NoError(t, err) _, err = config.Validate() - require.EqualError(t, err, "pipeline (release) defined in trigger item (push_branch: /release -> pipeline: release), but does not exist") + require.EqualError(t, err, "trigger item #1: non-existent pipeline defined as trigger target: release") } t.Log("workflow not exists") @@ -457,7 +633,7 @@ workflows: require.NoError(t, err) _, err = config.Validate() - require.EqualError(t, err, "workflow (release) defined in trigger item (push_branch: /release -> workflow: release), but does not exist") + require.EqualError(t, err, "trigger item #1: non-existent workflow defined as trigger target: release") } } diff --git a/models/trigger_map.go b/models/trigger_map.go index b58df02fa..21d87fc8c 100644 --- a/models/trigger_map.go +++ b/models/trigger_map.go @@ -6,20 +6,33 @@ import ( type TriggerMapModel []TriggerMapItemModel -func (triggerMap TriggerMapModel) Validate(workflows, pipelines []string) ([]string, error) { - var warnings []string - for _, item := range triggerMap { - warns, err := item.Validate(workflows, pipelines) - warnings = append(warnings, warns...) +func (triggerMap TriggerMapModel) Normalized() ([]TriggerMapItemModel, error) { + var items []TriggerMapItemModel + for idx, item := range triggerMap { + normalizedItem, err := item.Normalized(idx) if err != nil { - return warnings, err + return nil, err } + items = append(items, normalizedItem) } + return items, nil +} + +func (triggerMap TriggerMapModel) Validate(workflows, pipelines []string) ([]string, error) { + var warnings []string if err := triggerMap.checkDuplicatedTriggerMapItems(); err != nil { return warnings, err } + for idx, item := range triggerMap { + warns, err := item.Validate(idx, workflows, pipelines) + warnings = append(warnings, warns...) + if err != nil { + return warnings, err + } + } + return warnings, nil } @@ -38,17 +51,17 @@ func (triggerMap TriggerMapModel) FirstMatchingTarget(pushBranch, prSourceBranch } func (triggerMap TriggerMapModel) checkDuplicatedTriggerMapItems() error { - items := make(map[string]struct{}) + items := make(map[string]int) - for _, triggerItem := range triggerMap { - content := triggerItem.String(false) + for idx, triggerItem := range triggerMap { + itemStr := triggerItem.conditionsString() - _, ok := items[content] + storedItemIdx, ok := items[itemStr] if ok { - return fmt.Errorf("duplicated trigger item found (%s)", content) + return fmt.Errorf("the %d. trigger item duplicates the %d. trigger item", idx+1, storedItemIdx+1) } - items[content] = struct{}{} + items[itemStr] = idx } return nil diff --git a/models/trigger_map_item.go b/models/trigger_map_item.go index d2100315f..9afa3d3f3 100644 --- a/models/trigger_map_item.go +++ b/models/trigger_map_item.go @@ -2,8 +2,10 @@ package models import ( "fmt" + "reflect" "strings" + "github.com/bitrise-io/go-utils/sliceutil" "github.com/ryanuber/go-glob" ) @@ -26,91 +28,56 @@ const ( const defaultDraftPullRequestEnabled = true +type TriggerItemType string + +const ( + CodePushType TriggerItemType = "push" + PullRequestType TriggerItemType = "pull_request" + TagPushType TriggerItemType = "tag" +) + type TriggerMapItemModel struct { - // Trigger target - PipelineID string `json:"pipeline,omitempty" yaml:"pipeline,omitempty"` - WorkflowID string `json:"workflow,omitempty" yaml:"workflow,omitempty"` - // Commit push event criteria - PushBranch string `json:"push_branch,omitempty" yaml:"push_branch,omitempty"` - // Tag push event criteria - Tag string `json:"tag,omitempty" yaml:"tag,omitempty"` - // Pull Request event criteria - PullRequestSourceBranch string `json:"pull_request_source_branch,omitempty" yaml:"pull_request_source_branch,omitempty"` - PullRequestTargetBranch string `json:"pull_request_target_branch,omitempty" yaml:"pull_request_target_branch,omitempty"` - DraftPullRequestEnabled *bool `json:"draft_pull_request_enabled,omitempty" yaml:"draft_pull_request_enabled,omitempty"` - - // Deprecated + // Trigger Item shared properties + Type TriggerItemType `json:"type,omitempty" yaml:"type,omitempty"` + Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + PipelineID string `json:"pipeline,omitempty" yaml:"pipeline,omitempty"` + WorkflowID string `json:"workflow,omitempty" yaml:"workflow,omitempty"` + + // Code Push Item conditions + PushBranch interface{} `json:"push_branch,omitempty" yaml:"push_branch,omitempty"` + CommitMessage interface{} `json:"commit_message,omitempty" yaml:"commit_message,omitempty"` + ChangedFiles interface{} `json:"changed_files,omitempty" yaml:"changed_files,omitempty"` + + // Tag Push Item conditions + Tag interface{} `json:"tag,omitempty" yaml:"tag,omitempty"` + + // Pull Request Item conditions + PullRequestSourceBranch interface{} `json:"pull_request_source_branch,omitempty" yaml:"pull_request_source_branch,omitempty"` + PullRequestTargetBranch interface{} `json:"pull_request_target_branch,omitempty" yaml:"pull_request_target_branch,omitempty"` + DraftPullRequestEnabled *bool `json:"draft_pull_request_enabled,omitempty" yaml:"draft_pull_request_enabled,omitempty"` + PullRequestLabel interface{} `json:"pull_request_label,omitempty" yaml:"pull_request_label,omitempty"` + + // Deprecated properties Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` IsPullRequestAllowed bool `json:"is_pull_request_allowed,omitempty" yaml:"is_pull_request_allowed,omitempty"` } -func (triggerItem TriggerMapItemModel) Validate(workflows, pipelines []string) ([]string, error) { - var warnings []string - - // Validate target - if triggerItem.PipelineID != "" && triggerItem.WorkflowID != "" { - return warnings, fmt.Errorf("both pipeline and workflow are defined as trigger target: %s", triggerItem.String(false)) - } - if triggerItem.PipelineID == "" && triggerItem.WorkflowID == "" { - return warnings, fmt.Errorf("no pipeline nor workflow is defined as a trigger target: %s", triggerItem.String(false)) - } - - if strings.HasPrefix(triggerItem.WorkflowID, "_") { - warnings = append(warnings, fmt.Sprintf("workflow (%s) defined in trigger item (%s), but utility workflows can't be triggered directly", triggerItem.WorkflowID, triggerItem.String(true))) - } - - found := false - if triggerItem.PipelineID != "" { - for _, pipelineID := range pipelines { - if pipelineID == triggerItem.PipelineID { - found = true - break - } - } - - if !found { - return warnings, fmt.Errorf("pipeline (%s) defined in trigger item (%s), but does not exist", triggerItem.PipelineID, triggerItem.String(true)) - } - } else { - for _, workflowID := range workflows { - if workflowID == triggerItem.WorkflowID { - found = true - break - } - } - - if !found { - return warnings, fmt.Errorf("workflow (%s) defined in trigger item (%s), but does not exist", triggerItem.WorkflowID, triggerItem.String(true)) - } - } - - // Validate match criteria - if triggerItem.Pattern == "" { - _, err := triggerEventType(triggerItem.PushBranch, triggerItem.PullRequestSourceBranch, triggerItem.PullRequestTargetBranch, triggerItem.Tag) - if err != nil { - return warnings, fmt.Errorf("trigger map item (%s) validate failed, error: %s", triggerItem.String(true), err) - } - } else if triggerItem.PushBranch != "" || - triggerItem.PullRequestSourceBranch != "" || triggerItem.PullRequestTargetBranch != "" || triggerItem.Tag != "" { - return warnings, fmt.Errorf("deprecated trigger item (pattern defined), mixed with trigger params (push_branch: %s, pull_request_source_branch: %s, pull_request_target_branch: %s, tag: %s)", triggerItem.PushBranch, triggerItem.PullRequestSourceBranch, triggerItem.PullRequestTargetBranch, triggerItem.Tag) - } - - return warnings, nil -} - -func (triggerItem TriggerMapItemModel) MatchWithParams(pushBranch, prSourceBranch, prTargetBranch string, prReadyState PullRequestReadyState, tag string) (bool, error) { +func (item TriggerMapItemModel) MatchWithParams(pushBranch, prSourceBranch, prTargetBranch string, prReadyState PullRequestReadyState, tag string) (bool, error) { paramsEventType, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) if err != nil { return false, err } - migratedTriggerItems := []TriggerMapItemModel{triggerItem} - if triggerItem.Pattern != "" { - migratedTriggerItems = migrateDeprecatedTriggerItem(triggerItem) + migratedTriggerItems := []TriggerMapItemModel{item} + if item.Pattern != "" { + migratedTriggerItems = migrateDeprecatedTriggerItem(item) } for _, migratedTriggerItem := range migratedTriggerItems { - itemEventType, err := triggerEventType(migratedTriggerItem.PushBranch, migratedTriggerItem.PullRequestSourceBranch, migratedTriggerItem.PullRequestTargetBranch, migratedTriggerItem.Tag) + itemEventType, err := triggerEventType(stringFromTriggerCondition(migratedTriggerItem.PushBranch), + stringFromTriggerCondition(migratedTriggerItem.PullRequestSourceBranch), + stringFromTriggerCondition(migratedTriggerItem.PullRequestTargetBranch), + stringFromTriggerCondition(migratedTriggerItem.Tag)) if err != nil { return false, err } @@ -121,21 +88,21 @@ func (triggerItem TriggerMapItemModel) MatchWithParams(pushBranch, prSourceBranc switch itemEventType { case TriggerEventTypeCodePush: - match := glob.Glob(migratedTriggerItem.PushBranch, pushBranch) + match := glob.Glob(stringFromTriggerCondition(migratedTriggerItem.PushBranch), pushBranch) return match, nil case TriggerEventTypePullRequest: sourceMatch := false - if migratedTriggerItem.PullRequestSourceBranch == "" { + if stringFromTriggerCondition(migratedTriggerItem.PullRequestSourceBranch) == "" { sourceMatch = true } else { - sourceMatch = glob.Glob(migratedTriggerItem.PullRequestSourceBranch, prSourceBranch) + sourceMatch = glob.Glob(stringFromTriggerCondition(migratedTriggerItem.PullRequestSourceBranch), prSourceBranch) } targetMatch := false - if migratedTriggerItem.PullRequestTargetBranch == "" { + if stringFromTriggerCondition(migratedTriggerItem.PullRequestTargetBranch) == "" { targetMatch = true } else { - targetMatch = glob.Glob(migratedTriggerItem.PullRequestTargetBranch, prTargetBranch) + targetMatch = glob.Glob(stringFromTriggerCondition(migratedTriggerItem.PullRequestTargetBranch), prTargetBranch) } // When a PR is converted to ready for review: @@ -155,7 +122,7 @@ func (triggerItem TriggerMapItemModel) MatchWithParams(pushBranch, prSourceBranc return sourceMatch && targetMatch && !stateMismatch, nil case TriggerEventTypeTag: - match := glob.Glob(migratedTriggerItem.Tag, tag) + match := glob.Glob(stringFromTriggerCondition(migratedTriggerItem.Tag), tag) return match, nil } } @@ -163,67 +130,438 @@ func (triggerItem TriggerMapItemModel) MatchWithParams(pushBranch, prSourceBranc return false, nil } -func (triggerItem TriggerMapItemModel) IsDraftPullRequestEnabled() bool { +func (item TriggerMapItemModel) IsDraftPullRequestEnabled() bool { draftPullRequestEnabled := defaultDraftPullRequestEnabled - if triggerItem.DraftPullRequestEnabled != nil { - draftPullRequestEnabled = *triggerItem.DraftPullRequestEnabled + if item.DraftPullRequestEnabled != nil { + draftPullRequestEnabled = *item.DraftPullRequestEnabled } return draftPullRequestEnabled } -func (triggerItem TriggerMapItemModel) String(printTarget bool) string { - str := "" +// Normalized casts trigger item values from map[interface{}]interface{} to map[string]interface{} +// to support JSON marshalling of the bitrise.yml. +func (item TriggerMapItemModel) Normalized(idx int) (TriggerMapItemModel, error) { + mapInterface, ok := item.PushBranch.(map[interface{}]interface{}) + if ok { + value, err := castInterfaceKeysToStringKeys(idx, "push_branch", mapInterface) + if err != nil { + return TriggerMapItemModel{}, err + } + item.PushBranch = value + } - if triggerItem.PushBranch != "" { - str = fmt.Sprintf("push_branch: %s", triggerItem.PushBranch) + mapInterface, ok = item.CommitMessage.(map[interface{}]interface{}) + if ok { + value, err := castInterfaceKeysToStringKeys(idx, "commit_message", mapInterface) + if err != nil { + return TriggerMapItemModel{}, err + } + item.CommitMessage = value } - if triggerItem.PullRequestSourceBranch != "" || triggerItem.PullRequestTargetBranch != "" { - if str != "" { - str += " " + mapInterface, ok = item.ChangedFiles.(map[interface{}]interface{}) + if ok { + value, err := castInterfaceKeysToStringKeys(idx, "changed_files", mapInterface) + if err != nil { + return TriggerMapItemModel{}, err } + item.ChangedFiles = value + } - if triggerItem.PullRequestSourceBranch != "" { - str += fmt.Sprintf("pull_request_source_branch: %s", triggerItem.PullRequestSourceBranch) + mapInterface, ok = item.Tag.(map[interface{}]interface{}) + if ok { + value, err := castInterfaceKeysToStringKeys(idx, "tag", mapInterface) + if err != nil { + return TriggerMapItemModel{}, err } - if triggerItem.PullRequestTargetBranch != "" { - if triggerItem.PullRequestSourceBranch != "" { - str += " && " - } + item.Tag = value + } - str += fmt.Sprintf("pull_request_target_branch: %s", triggerItem.PullRequestTargetBranch) + mapInterface, ok = item.PullRequestSourceBranch.(map[interface{}]interface{}) + if ok { + value, err := castInterfaceKeysToStringKeys(idx, "pull_request_source_branch", mapInterface) + if err != nil { + return TriggerMapItemModel{}, err } + item.PullRequestSourceBranch = value + } - str += fmt.Sprintf(" && draft_pull_request_enabled: %v", triggerItem.IsDraftPullRequestEnabled()) + mapInterface, ok = item.PullRequestTargetBranch.(map[interface{}]interface{}) + if ok { + value, err := castInterfaceKeysToStringKeys(idx, "pull_request_target_branch", mapInterface) + if err != nil { + return TriggerMapItemModel{}, err + } + item.PullRequestTargetBranch = value } - if triggerItem.Tag != "" { - if str != "" { - str += " " + mapInterface, ok = item.PullRequestLabel.(map[interface{}]interface{}) + if ok { + value, err := castInterfaceKeysToStringKeys(idx, "pull_request_label", mapInterface) + if err != nil { + return TriggerMapItemModel{}, err + } + item.PullRequestLabel = value + } + + return item, nil +} + +func (item TriggerMapItemModel) Validate(idx int, workflows, pipelines []string) ([]string, error) { + warnings, err := item.validateTarget(idx, workflows, pipelines) + if err != nil { + return warnings, err + } + + if item.Pattern != "" { + if err := item.validateLegacyItemType(idx); err != nil { + return warnings, err } + } else { + if err := item.validateType(idx); err != nil { + return warnings, err + } + if err := item.validateConditionValues(idx); err != nil { + return warnings, err + } + } + + return warnings, nil +} - str += fmt.Sprintf("tag: %s", triggerItem.Tag) +func (item TriggerMapItemModel) validateTarget(idx int, workflows, pipelines []string) ([]string, error) { + var warnings []string + + // Validate target + if item.PipelineID != "" && item.WorkflowID != "" { + return warnings, fmt.Errorf("trigger item #%d: both pipeline and workflow are defined as trigger target", idx+1) + } + if item.PipelineID == "" && item.WorkflowID == "" { + return warnings, fmt.Errorf("trigger item #%d: no pipeline nor workflow is defined as trigger target", idx+1) } - if triggerItem.Pattern != "" { - if str != "" { - str += " " + if strings.HasPrefix(item.WorkflowID, "_") { + warnings = append(warnings, fmt.Sprintf("trigger item #%d: utility workflow (%s) defined as trigger target, but utility workflows can't be triggered directly", idx+1, item.WorkflowID)) + } + + if item.PipelineID != "" { + if !sliceutil.IsStringInSlice(item.PipelineID, pipelines) { + return warnings, fmt.Errorf("trigger item #%d: non-existent pipeline defined as trigger target: %s", idx+1, item.PipelineID) + } + } else { + if !sliceutil.IsStringInSlice(item.WorkflowID, workflows) { + return warnings, fmt.Errorf("trigger item #%d: non-existent workflow defined as trigger target: %s", idx+1, item.WorkflowID) } + } + + return warnings, nil +} - str += fmt.Sprintf("pattern: %s && is_pull_request_allowed: %v", triggerItem.Pattern, triggerItem.IsPullRequestAllowed) +func (item TriggerMapItemModel) validateLegacyItemType(idx int) error { + if err := item.validateNoCodePushConditionsSet(idx, "pattern"); err != nil { + return err + } + if err := item.validateNoTagPushConditionsSet(idx, "pattern"); err != nil { + return err + } + if err := item.validateNoPullRequestConditionsSet(idx, "pattern"); err != nil { + return err + } + return nil +} + +func (item TriggerMapItemModel) validateType(idx int) error { + if item.Type != "" { + if !sliceutil.IsStringInSlice(string(item.Type), []string{string(CodePushType), string(PullRequestType), string(TagPushType)}) { + return fmt.Errorf("trigger item #%d: invalid type (%s) defined, valid types are: push, pull_request and tag", idx+1, item.Type) + } } - if printTarget { - if triggerItem.PipelineID != "" { - str += fmt.Sprintf(" -> pipeline: %s", triggerItem.PipelineID) + if isStringLiteralOrRegexSet(item.PushBranch) || item.Type == CodePushType { + var field string + if item.Type != "" { + field = fmt.Sprintf("type: %s", item.Type) + } else { + field = "push_branch" + } + + if err := item.validateNoTagPushConditionsSet(idx, field); err != nil { + return err + } + if err := item.validateNoPullRequestConditionsSet(idx, field); err != nil { + return err + } + + return nil + } else if isStringLiteralOrRegexSet(item.PullRequestSourceBranch) || isStringLiteralOrRegexSet(item.PullRequestTargetBranch) || item.Type == PullRequestType { + var field string + if item.Type != "" { + field = fmt.Sprintf("type: %s", item.Type) } else { - str += fmt.Sprintf(" -> workflow: %s", triggerItem.WorkflowID) + if isStringLiteralOrRegexSet(item.PullRequestSourceBranch) { + field = "pull_request_source_branch" + } + if isStringLiteralOrRegexSet(item.PullRequestTargetBranch) { + if field != "" { + field += " and " + } + field += "pull_request_target_branch" + } + } + + if err := item.validateNoCodePushConditionsSet(idx, field); err != nil { + return err + } + if err := item.validateNoTagPushConditionsSet(idx, field); err != nil { + return err + } + + return nil + } else if isStringLiteralOrRegexSet(item.Tag) || item.Type == TagPushType { + var field string + if item.Type != "" { + field = fmt.Sprintf("type: %s", item.Type) + } else { + field = "tag" + } + + if err := item.validateNoCodePushConditionsSet(idx, field); err != nil { + return err + } + if err := item.validateNoPullRequestConditionsSet(idx, field); err != nil { + return err } + + return nil + } + + return fmt.Errorf("trigger item #%d: no type or relevant trigger condition defined", idx+1) +} + +func (item TriggerMapItemModel) validateConditionValues(idx int) error { + if err := validateStringOrRegexType(idx, "push_branch", item.PushBranch); err != nil { + return err + } + if err := validateStringOrRegexType(idx, "commit_message", item.CommitMessage); err != nil { + return err + } + if err := validateStringOrRegexType(idx, "changed_files", item.ChangedFiles); err != nil { + return err + } + + if err := validateStringOrRegexType(idx, "tag", item.Tag); err != nil { + return err + } + + if err := validateStringOrRegexType(idx, "pull_request_source_branch", item.PullRequestSourceBranch); err != nil { + return err + } + if err := validateStringOrRegexType(idx, "pull_request_target_branch", item.PullRequestTargetBranch); err != nil { + return err + } + if err := validateStringOrRegexType(idx, "pull_request_label", item.PullRequestLabel); err != nil { + return err + } + return nil +} + +func (item TriggerMapItemModel) validateNoCodePushConditionsSet(idx int, field string) error { + if isStringLiteralOrRegexSet(item.PushBranch) { + return fmt.Errorf("trigger item #%d: both %s and push_branch defined", idx+1, field) + } + if isStringLiteralOrRegexSet(item.CommitMessage) { + return fmt.Errorf("trigger item #%d: both %s and commit_message defined", idx+1, field) + } + if isStringLiteralOrRegexSet(item.ChangedFiles) { + return fmt.Errorf("trigger item #%d: both %s and changed_files defined", idx+1, field) + } + return nil +} + +func (item TriggerMapItemModel) validateNoTagPushConditionsSet(idx int, field string) error { + if isStringLiteralOrRegexSet(item.Tag) { + return fmt.Errorf("trigger item #%d: both %s and tag defined", idx+1, field) + } + return nil +} + +func (item TriggerMapItemModel) validateNoPullRequestConditionsSet(idx int, field string) error { + if isStringLiteralOrRegexSet(item.PullRequestSourceBranch) { + return fmt.Errorf("trigger item #%d: both %s and pull_request_source_branch defined", idx+1, field) + } + if isStringLiteralOrRegexSet(item.PullRequestTargetBranch) { + return fmt.Errorf("trigger item #%d: both %s and pull_request_target_branch defined", idx+1, field) + } + //nolint:gosimple + if item.IsDraftPullRequestEnabled() != defaultDraftPullRequestEnabled { + return fmt.Errorf("trigger item #%d: both %s and draft_pull_request_enabled defined", idx+1, field) + } + if isStringLiteralOrRegexSet(item.PullRequestLabel) { + return fmt.Errorf("trigger item #%d: both %s and pull_request_label defined", idx+1, field) + } + return nil +} + +func (item TriggerMapItemModel) conditionsString() string { + str := "" + + rv := reflect.Indirect(reflect.ValueOf(&item)) + rt := rv.Type() + for i := 0; i < rt.NumField(); i++ { + field := rt.Field(i) + tag := field.Tag.Get("yaml") + tag = strings.TrimSuffix(tag, ",omitempty") + if tag == "pipeline" || tag == "workflow" || tag == "type" || tag == "enabled" { + continue + } + + value := rv.FieldByName(field.Name).Interface() + if value == nil { + continue + } + + if tag == "draft_pull_request_enabled" { + if boolPtrValue, ok := value.(*bool); ok { + //nolint:gosimple + if boolPtrValue == nil || *boolPtrValue == defaultDraftPullRequestEnabled { + continue + } + value = *boolPtrValue + } + } + + if strValue, ok := value.(string); ok { + if strValue == "" { + continue + } + } + + if tag == "is_pull_request_allowed" { + if boolPtrValue, ok := value.(bool); ok { + if !boolPtrValue { + continue + } + } + } + + if str != "" { + str += " & " + } + str += fmt.Sprintf("%s: %v", tag, value) + } + + if str == "" && item.Type != "" { + // Trigger Item without any condition is valid, + // this case we use the type to differentiate push, pull-request and tag items + str = "type: " + string(item.Type) } return str } +func validateStringOrRegexType(idx int, field string, value interface{}) error { + if value == nil { + return nil + } + _, ok := value.(string) + if ok { + return nil + } + + valueMap, ok := value.(map[interface{}]interface{}) + if ok { + if len(valueMap) != 1 { + return fmt.Errorf("trigger item #%d: single 'regex' key is expected for regex condition in %s field", idx+1, field) + } + + _, ok := valueMap["regex"] + if !ok { + return fmt.Errorf("trigger item #%d: 'regex' key is expected for regex condition in %s field", idx+1, field) + } + + return nil + } + + valueInterfaceMap, ok := value.(map[string]interface{}) + if ok { + if len(valueInterfaceMap) != 1 { + return fmt.Errorf("trigger item #%d: single 'regex' key is expected for regex condition in %s field", idx+1, field) + } + + regex, ok := valueInterfaceMap["regex"] + if !ok { + return fmt.Errorf("trigger item #%d: 'regex' key is expected for regex condition in %s field", idx+1, field) + } + + _, ok = regex.(string) + if !ok { + return fmt.Errorf("trigger item #%d: 'regex' key is expected to have a string value in %s field", idx+1, field) + } + + return nil + } + + valueStringMap, ok := value.(map[string]string) + if ok { + if len(valueStringMap) != 1 { + return fmt.Errorf("trigger item #%d: single 'regex' key is expected for regex condition in %s field", idx+1, field) + } + + _, ok := valueStringMap["regex"] + if !ok { + return fmt.Errorf("trigger item #%d: 'regex' key is expected for regex condition in %s field", idx+1, field) + } + + return nil + } + + return fmt.Errorf("trigger item #%d: string literal or regex value is expected for %s field", idx+1, field) +} + +func stringFromTriggerCondition(value interface{}) string { + if value == nil { + return "" + } + return value.(string) +} + +func stringLiteralOrRegex(value interface{}) string { + if value == nil { + return "" + } + str, ok := value.(string) + if ok { + return string(str) + } + + valueMap, ok := value.(map[interface{}]interface{}) + if ok { + regex, ok := valueMap["regex"] + if ok { + return regex.(string) + } + } + + valueInterfaceMap, ok := value.(map[string]interface{}) + if ok { + regex, ok := valueInterfaceMap["regex"] + if ok { + return regex.(string) + } + } + + valueStringMap, ok := value.(map[string]string) + if ok { + return valueStringMap["regex"] + } + + return "" +} + +func isStringLiteralOrRegexSet(value interface{}) bool { + return stringLiteralOrRegex(value) != "" +} + func triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag string) (TriggerEventType, error) { if pushBranch != "" { // Ensure not mixed with code-push event @@ -269,3 +607,15 @@ func migrateDeprecatedTriggerItem(triggerItem TriggerMapItemModel) []TriggerMapI } return migratedItems } + +func castInterfaceKeysToStringKeys(idx int, field string, value map[interface{}]interface{}) (map[string]interface{}, error) { + mapString := map[string]interface{}{} + for key, value := range value { + keyStr, ok := key.(string) + if !ok { + return nil, fmt.Errorf("trigger item #%d: %s field should be a string literal or a hash with a single 'regex' key", idx+1, field) + } + mapString[keyStr] = value + } + return mapString, nil +} diff --git a/models/trigger_map_item_test.go b/models/trigger_map_item_test.go index 577971a22..d0add2833 100644 --- a/models/trigger_map_item_test.go +++ b/models/trigger_map_item_test.go @@ -340,10 +340,9 @@ func TestTriggerMapItemModel_MatchWithParams_TagParams(t *testing.T) { func TestTriggerMapItemModel_String(t *testing.T) { tests := []struct { - name string - triggerMapItem TriggerMapItemModel - want string - wantWithPrintTarget string + name string + triggerMapItem TriggerMapItemModel + want string }{ { name: "triggering pipeline", @@ -351,8 +350,7 @@ func TestTriggerMapItemModel_String(t *testing.T) { PushBranch: "master", PipelineID: "pipeline-1", }, - want: "push_branch: master", - wantWithPrintTarget: "push_branch: master -> pipeline: pipeline-1", + want: "push_branch: master", }, { name: "push event", @@ -360,8 +358,15 @@ func TestTriggerMapItemModel_String(t *testing.T) { PushBranch: "master", WorkflowID: "ci", }, - want: "push_branch: master", - wantWithPrintTarget: "push_branch: master -> workflow: ci", + want: "push_branch: master", + }, + { + name: "push event - type only", + triggerMapItem: TriggerMapItemModel{ + Type: "push", + WorkflowID: "ci", + }, + want: "type: push", }, { name: "pull request event - pr source branch", @@ -369,8 +374,7 @@ func TestTriggerMapItemModel_String(t *testing.T) { PullRequestSourceBranch: "develop", WorkflowID: "ci", }, - want: "pull_request_source_branch: develop && draft_pull_request_enabled: true", - wantWithPrintTarget: "pull_request_source_branch: develop && draft_pull_request_enabled: true -> workflow: ci", + want: "pull_request_source_branch: develop", }, { name: "pull request event - pr target branch", @@ -378,8 +382,7 @@ func TestTriggerMapItemModel_String(t *testing.T) { PullRequestTargetBranch: "master", WorkflowID: "ci", }, - want: "pull_request_target_branch: master && draft_pull_request_enabled: true", - wantWithPrintTarget: "pull_request_target_branch: master && draft_pull_request_enabled: true -> workflow: ci", + want: "pull_request_target_branch: master", }, { name: "pull request event - pr target and source branch", @@ -388,8 +391,7 @@ func TestTriggerMapItemModel_String(t *testing.T) { PullRequestTargetBranch: "master", WorkflowID: "ci", }, - want: "pull_request_source_branch: develop && pull_request_target_branch: master && draft_pull_request_enabled: true", - wantWithPrintTarget: "pull_request_source_branch: develop && pull_request_target_branch: master && draft_pull_request_enabled: true -> workflow: ci", + want: "pull_request_source_branch: develop & pull_request_target_branch: master", }, { name: "pull request event - pr target and source branch and disable draft prs", @@ -399,8 +401,7 @@ func TestTriggerMapItemModel_String(t *testing.T) { DraftPullRequestEnabled: pointers.NewBoolPtr(false), WorkflowID: "ci", }, - want: "pull_request_source_branch: develop && pull_request_target_branch: master && draft_pull_request_enabled: false", - wantWithPrintTarget: "pull_request_source_branch: develop && pull_request_target_branch: master && draft_pull_request_enabled: false -> workflow: ci", + want: "pull_request_source_branch: develop & pull_request_target_branch: master & draft_pull_request_enabled: false", }, { name: "tag event", @@ -408,8 +409,7 @@ func TestTriggerMapItemModel_String(t *testing.T) { Tag: "0.9.0", WorkflowID: "release", }, - want: "tag: 0.9.0", - wantWithPrintTarget: "tag: 0.9.0 -> workflow: release", + want: "tag: 0.9.0", }, { name: "deprecated type - pr disabled", @@ -418,8 +418,7 @@ func TestTriggerMapItemModel_String(t *testing.T) { IsPullRequestAllowed: false, WorkflowID: "ci", }, - want: "pattern: master && is_pull_request_allowed: false", - wantWithPrintTarget: "pattern: master && is_pull_request_allowed: false -> workflow: ci", + want: "pattern: master", }, { name: "deprecated type - pr enabled", @@ -428,8 +427,7 @@ func TestTriggerMapItemModel_String(t *testing.T) { IsPullRequestAllowed: true, WorkflowID: "ci", }, - want: "pattern: master && is_pull_request_allowed: true", - wantWithPrintTarget: "pattern: master && is_pull_request_allowed: true -> workflow: ci", + want: "pattern: master & is_pull_request_allowed: true", }, { name: "mixed", @@ -442,19 +440,17 @@ func TestTriggerMapItemModel_String(t *testing.T) { IsPullRequestAllowed: true, WorkflowID: "ci", }, - want: "push_branch: master pull_request_source_branch: develop && pull_request_target_branch: master && draft_pull_request_enabled: true tag: 0.9.0 pattern: * && is_pull_request_allowed: true", - wantWithPrintTarget: "push_branch: master pull_request_source_branch: develop && pull_request_target_branch: master && draft_pull_request_enabled: true tag: 0.9.0 pattern: * && is_pull_request_allowed: true -> workflow: ci", + want: "push_branch: master & tag: 0.9.0 & pull_request_source_branch: develop & pull_request_target_branch: master & pattern: * & is_pull_request_allowed: true", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.want, tt.triggerMapItem.String(false)) - require.Equal(t, tt.wantWithPrintTarget, tt.triggerMapItem.String(true)) + require.Equal(t, tt.want, tt.triggerMapItem.conditionsString()) }) } } -func TestTriggerMapItemModel_Validate(t *testing.T) { +func TestTriggerMapItemModel_Validate_LegacyItem(t *testing.T) { tests := []struct { name string triggerMapItem TriggerMapItemModel @@ -487,14 +483,14 @@ func TestTriggerMapItemModel_Validate(t *testing.T) { WorkflowID: "workflow-1", }, workflows: []string{"pipeline-1", "workflow-1"}, - wantErr: "both pipeline and workflow are defined as trigger target: pattern: * && is_pull_request_allowed: false", + wantErr: "trigger item #1: both pipeline and workflow are defined as trigger target", }, { name: "it fails for invalid deprecated trigger item - missing pipeline & workflow", triggerMapItem: TriggerMapItemModel{ Pattern: "*", }, - wantErr: "no pipeline nor workflow is defined as a trigger target: pattern: * && is_pull_request_allowed: false", + wantErr: "trigger item #1: no pipeline nor workflow is defined as trigger target", }, { name: "it fails for invalid deprecated trigger item - missing pattern", @@ -503,8 +499,41 @@ func TestTriggerMapItemModel_Validate(t *testing.T) { WorkflowID: "primary", }, workflows: []string{"primary"}, - wantErr: "trigger map item ( -> workflow: primary) validate failed, error: failed to determin trigger event from params: push-branch: , pr-source-branch: , pr-target-branch: , tag: ", + wantErr: "trigger item #1: no type or relevant trigger condition defined", }, + { + name: "it fails for mixed (mixed new and legacy properties) trigger item", + triggerMapItem: TriggerMapItemModel{ + PushBranch: "master", + Pattern: "*", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: both pattern and push_branch defined", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + warns, err := tt.triggerMapItem.Validate(0, tt.workflows, tt.pipelines) + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.wantWarns, warns) + }) + } +} + +func TestTriggerMapItemModel_Validate_CodePushItem(t *testing.T) { + tests := []struct { + name string + triggerMapItem TriggerMapItemModel + workflows []string + pipelines []string + wantWarns []string + wantErr string + }{ { name: "it validates code-push trigger item with triggered pipeline", triggerMapItem: TriggerMapItemModel{ @@ -522,21 +551,220 @@ func TestTriggerMapItemModel_Validate(t *testing.T) { workflows: []string{"primary"}, }, { - name: "it fails for invalid code-push trigger item - missing push-branch", + name: "type is required, when no condition defined", + triggerMapItem: TriggerMapItemModel{ + Type: CodePushType, + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + }, + { + name: "type is required, when no push_branch defined", triggerMapItem: TriggerMapItemModel{ PushBranch: "", WorkflowID: "primary", }, workflows: []string{"primary"}, - wantErr: "trigger map item ( -> workflow: primary) validate failed, error: failed to determin trigger event from params: push-branch: , pr-source-branch: , pr-target-branch: , tag: ", + wantErr: "trigger item #1: no type or relevant trigger condition defined", + }, + { + name: "type is required, when no push_branch defined (commit_message)", + triggerMapItem: TriggerMapItemModel{ + CommitMessage: "CI", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: no type or relevant trigger condition defined", + }, + { + name: "type is required, when no push_branch defined (changed_files)", + triggerMapItem: TriggerMapItemModel{ + ChangedFiles: "./ios/", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: no type or relevant trigger condition defined", }, { name: "it fails for invalid code-push trigger item - missing pipeline & workflow", triggerMapItem: TriggerMapItemModel{ PushBranch: "*", }, - wantErr: "no pipeline nor workflow is defined as a trigger target: push_branch: *", + wantErr: "trigger item #1: no pipeline nor workflow is defined as trigger target", + }, + { + name: "it fails for mixed (mixed types) trigger item", + triggerMapItem: TriggerMapItemModel{ + PushBranch: "master", + PullRequestSourceBranch: "feature/*", + PullRequestTargetBranch: "", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: both push_branch and pull_request_source_branch defined", + }, + { + name: "push_branch can be a regex", + triggerMapItem: TriggerMapItemModel{ + PushBranch: map[interface{}]interface{}{ + "regex": "feature-.*", + }, + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + }, + { + name: "commit_message can be a regex", + triggerMapItem: TriggerMapItemModel{ + Type: CodePushType, + CommitMessage: map[string]interface{}{ + "regex": `^\[CI]\.*`, + }, + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + }, + { + name: "changed_files can be a regex", + triggerMapItem: TriggerMapItemModel{ + Type: CodePushType, + ChangedFiles: map[string]string{ + "regex": `^\/ios/.*`, + }, + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + }, + { + name: "condition value can be a hash with a regex key", + triggerMapItem: TriggerMapItemModel{ + Type: CodePushType, + ChangedFiles: map[string]string{ + "glob": `^\/ios/.*`, + }, + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: 'regex' key is expected for regex condition in changed_files field", + }, + { + name: "condition value can be a hash with a single key", + triggerMapItem: TriggerMapItemModel{ + Type: CodePushType, + ChangedFiles: map[string]string{ + "glob": `^\/ios/*`, + "regex": `^\/ios/.*`, + }, + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: single 'regex' key is expected for regex condition in changed_files field", + }, + { + name: "invalid type", + triggerMapItem: TriggerMapItemModel{ + Type: "code-push", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: invalid type (code-push) defined, valid types are: push, pull_request and tag", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + warns, err := tt.triggerMapItem.Validate(0, tt.workflows, tt.pipelines) + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.wantWarns, warns) + }) + } +} + +func TestTriggerMapItemModel_Validate_TagPushItem(t *testing.T) { + tests := []struct { + name string + triggerMapItem TriggerMapItemModel + workflows []string + pipelines []string + wantWarns []string + wantErr string + }{ + { + name: "it validates tag trigger item with triggered pipeline", + triggerMapItem: TriggerMapItemModel{ + Tag: "*", + PipelineID: "primary", + }, + pipelines: []string{"primary"}, + }, + { + name: "it validates tag trigger item with triggered workflow", + triggerMapItem: TriggerMapItemModel{ + Tag: "*", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + }, + { + name: "type is required, when no condition defined", + triggerMapItem: TriggerMapItemModel{ + Type: TagPushType, + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + }, + { + name: "type is required, when no push_branch defined", + triggerMapItem: TriggerMapItemModel{ + Tag: "", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: no type or relevant trigger condition defined", + }, + { + name: "it fails for invalid code-push trigger item - missing pipeline & workflow", + triggerMapItem: TriggerMapItemModel{ + Tag: "*", + }, + wantErr: "trigger item #1: no pipeline nor workflow is defined as trigger target", }, + { + name: "tag can be a regex", + triggerMapItem: TriggerMapItemModel{ + Tag: map[interface{}]interface{}{ + "regex": "feature-.*", + }, + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + warns, err := tt.triggerMapItem.Validate(0, tt.workflows, tt.pipelines) + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.wantWarns, warns) + }) + } +} + +func TestTriggerMapItemModel_Validate_PullRequestItem(t *testing.T) { + tests := []struct { + name string + triggerMapItem TriggerMapItemModel + workflows []string + pipelines []string + wantWarns []string + wantErr string + }{ { name: "it validates pull-request trigger item (with source branch) with triggered pipeline", triggerMapItem: TriggerMapItemModel{ @@ -569,12 +797,38 @@ func TestTriggerMapItemModel_Validate(t *testing.T) { }, workflows: []string{"primary"}, }, + { + name: "type is required, when no condition defined", + triggerMapItem: TriggerMapItemModel{ + Type: PullRequestType, + PipelineID: "primary", + }, + pipelines: []string{"primary"}, + }, + { + name: "type is required, when no pull_request_source_branch defined (pull_request_label)", + triggerMapItem: TriggerMapItemModel{ + PullRequestLabel: "CI", + PipelineID: "primary", + }, + pipelines: []string{"primary"}, + wantErr: "trigger item #1: no type or relevant trigger condition defined", + }, + { + name: "type is required, when no pull_request_source_branch defined (draft_pull_request_enabled)", + triggerMapItem: TriggerMapItemModel{ + DraftPullRequestEnabled: pointers.NewBoolPtr(false), + PipelineID: "primary", + }, + pipelines: []string{"primary"}, + wantErr: "trigger item #1: no type or relevant trigger condition defined", + }, { name: "it fails for invalid pull-request trigger item (target branch set) - missing pipeline & workflow", triggerMapItem: TriggerMapItemModel{ PullRequestTargetBranch: "*", }, - wantErr: "no pipeline nor workflow is defined as a trigger target: pull_request_target_branch: * && draft_pull_request_enabled: true", + wantErr: "trigger item #1: no pipeline nor workflow is defined as trigger target", }, { name: "it fails for invalid pull-request trigger item (target and source branch set) - missing pipeline & workflow", @@ -582,33 +836,84 @@ func TestTriggerMapItemModel_Validate(t *testing.T) { PullRequestSourceBranch: "feature*", PullRequestTargetBranch: "master", }, - wantErr: "no pipeline nor workflow is defined as a trigger target: pull_request_source_branch: feature* && pull_request_target_branch: master && draft_pull_request_enabled: true", + wantErr: "trigger item #1: no pipeline nor workflow is defined as trigger target", }, { - name: "it fails for mixed (mixed types) trigger item", + name: "pull_request_source_branch can be a regex", + triggerMapItem: TriggerMapItemModel{ + PullRequestSourceBranch: map[interface{}]interface{}{ + "regex": "feature-.*", + }, + PipelineID: "primary", + }, + pipelines: []string{"primary"}, + }, + { + name: "pull_request_target_branch can be a regex", + triggerMapItem: TriggerMapItemModel{ + PullRequestTargetBranch: map[string]interface{}{ + "regex": "feature-.*", + }, + PipelineID: "primary", + }, + pipelines: []string{"primary"}, + }, + { + name: "pull_request_label can be a regex", + triggerMapItem: TriggerMapItemModel{ + Type: PullRequestType, + PullRequestLabel: map[string]string{ + "regex": "CI", + }, + PipelineID: "primary", + }, + pipelines: []string{"primary"}, + }, + { + name: "it fails for mixed type trigger item (pull_request_source_branch + tag)", triggerMapItem: TriggerMapItemModel{ - PushBranch: "master", PullRequestSourceBranch: "feature/*", - PullRequestTargetBranch: "", + Tag: "master", WorkflowID: "primary", }, workflows: []string{"primary"}, - wantErr: "trigger map item (push_branch: master pull_request_source_branch: feature/* && draft_pull_request_enabled: true -> workflow: primary) validate failed, error: push_branch (master) selects code-push trigger event, but pull_request_source_branch (feature/*) also provided", + wantErr: "trigger item #1: both pull_request_source_branch and tag defined", }, { - name: "it fails for mixed (mixed new and legacy properties) trigger item", + name: "it fails for mixed type trigger item (pull_request_target_branch + tag)", triggerMapItem: TriggerMapItemModel{ - PushBranch: "master", - Pattern: "*", + PullRequestTargetBranch: "feature/*", + Tag: "master", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: both pull_request_target_branch and tag defined", + }, + { + name: "it fails for mixed type trigger item (pull_request_source_branch + pull_request_target_branch + tag)", + triggerMapItem: TriggerMapItemModel{ + PullRequestSourceBranch: "main", + PullRequestTargetBranch: "feature/*", + Tag: "master", + WorkflowID: "primary", + }, + workflows: []string{"primary"}, + wantErr: "trigger item #1: both pull_request_source_branch and pull_request_target_branch and tag defined", + }, + { + name: "it fails for mixed type trigger item (type + tag)", + triggerMapItem: TriggerMapItemModel{ + Type: PullRequestType, + Tag: "master", WorkflowID: "primary", }, workflows: []string{"primary"}, - wantErr: "deprecated trigger item (pattern defined), mixed with trigger params (push_branch: master, pull_request_source_branch: , pull_request_target_branch: , tag: )", + wantErr: "trigger item #1: both type: pull_request and tag defined", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - warns, err := tt.triggerMapItem.Validate(tt.workflows, tt.pipelines) + warns, err := tt.triggerMapItem.Validate(0, tt.workflows, tt.pipelines) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) } else { diff --git a/models/trigger_map_test.go b/models/trigger_map_test.go index f17543221..32a596faa 100644 --- a/models/trigger_map_test.go +++ b/models/trigger_map_test.go @@ -63,7 +63,7 @@ func TestTriggerMapModel_Validate(t *testing.T) { }, }, workflows: []string{"ci", "release"}, - wantErr: "duplicated trigger item found (push_branch: master)", + wantErr: "the 2. trigger item duplicates the 1. trigger item", }, { name: "Pull Request trigger items", @@ -92,7 +92,7 @@ func TestTriggerMapModel_Validate(t *testing.T) { }, }, workflows: []string{"ci", "release"}, - wantErr: "duplicated trigger item found (pull_request_source_branch: develop && draft_pull_request_enabled: true)", + wantErr: "the 2. trigger item duplicates the 1. trigger item", }, { name: "Pull Request trigger items - duplicated (target branch)", @@ -107,7 +107,7 @@ func TestTriggerMapModel_Validate(t *testing.T) { }, }, workflows: []string{"ci", "release"}, - wantErr: "duplicated trigger item found (pull_request_target_branch: master && draft_pull_request_enabled: true)", + wantErr: "the 2. trigger item duplicates the 1. trigger item", }, { name: "Pull Request trigger items - duplicated (source & target branch)", @@ -124,7 +124,7 @@ func TestTriggerMapModel_Validate(t *testing.T) { }, }, workflows: []string{"ci", "release"}, - wantErr: "duplicated trigger item found (pull_request_source_branch: develop && pull_request_target_branch: master && draft_pull_request_enabled: true)", + wantErr: "the 2. trigger item duplicates the 1. trigger item", }, { name: "Pull Request trigger items - different draft pr enabled", @@ -157,7 +157,7 @@ func TestTriggerMapModel_Validate(t *testing.T) { }, }, workflows: []string{"ci", "release"}, - wantErr: "duplicated trigger item found (tag: 0.9.0)", + wantErr: "the 2. trigger item duplicates the 1. trigger item", }, } for _, tt := range tests {