Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499package workflow
import ( "testing"
"github.com/stretchr/testify/assert" "tangled.org/core/api/tangled")
func TestUnmarshalWorkflowWithBranch(t *testing.T) { yamlData := `when: - event: ["push", "pull_request"] branch: ["main", "develop"]`
wf, err := FromFile("test.yml", []byte(yamlData)) assert.NoError(t, err, "YAML should unmarshal without error")
assert.Len(t, wf.When, 1, "Should have one constraint") assert.ElementsMatch(t, []string{"main", "develop"}, wf.When[0].Branch) assert.ElementsMatch(t, []string{"push", "pull_request"}, wf.When[0].Event)
assert.False(t, wf.CloneOpts.Skip, "Skip should default to false")}
func TestUnmarshalCloneFalse(t *testing.T) { yamlData := `when: - event: pull_request_close
clone: skip: true`
wf, err := FromFile("test.yml", []byte(yamlData)) assert.NoError(t, err)
assert.ElementsMatch(t, []string{"pull_request_close"}, wf.When[0].Event)
assert.True(t, wf.CloneOpts.Skip, "Skip should be false")}
func TestUnmarshalWorkflowWithTags(t *testing.T) { yamlData := `when: - event: ["push"] tag: ["v*", "release-*"]`
wf, err := FromFile("test.yml", []byte(yamlData)) assert.NoError(t, err, "YAML should unmarshal without error")
assert.Len(t, wf.When, 1, "Should have one constraint") assert.ElementsMatch(t, []string{"v*", "release-*"}, wf.When[0].Tag) assert.ElementsMatch(t, []string{"push"}, wf.When[0].Event)}
func TestUnmarshalWorkflowWithBranchAndTag(t *testing.T) { yamlData := `when: - event: ["push"] branch: ["main", "develop"] tag: ["v*"]`
wf, err := FromFile("test.yml", []byte(yamlData)) assert.NoError(t, err, "YAML should unmarshal without error")
assert.Len(t, wf.When, 1, "Should have one constraint") assert.ElementsMatch(t, []string{"main", "develop"}, wf.When[0].Branch) assert.ElementsMatch(t, []string{"v*"}, wf.When[0].Tag)}
func TestMatchesPattern(t *testing.T) { tests := []struct { name string input string patterns []string expected bool }{ {"exact match", "main", []string{"main"}, true}, {"exact match in list", "develop", []string{"main", "develop"}, true}, {"no match", "feature", []string{"main", "develop"}, false}, {"wildcard prefix", "v1.0.0", []string{"v*"}, true}, {"wildcard suffix", "release-1.0", []string{"*-1.0"}, true}, {"wildcard middle", "feature-123-test", []string{"feature-*-test"}, true}, {"double star prefix", "release-1.0.0", []string{"release-**"}, true}, {"double star with slashes", "release/1.0/hotfix", []string{"release/**"}, true}, {"double star matches multiple levels", "foo/bar/baz/qux", []string{"foo/**"}, true}, {"double star no match", "feature/test", []string{"release/**"}, false}, {"no patterns matches nothing", "anything", []string{}, false}, {"pattern doesn't match", "v1.0.0", []string{"release-*"}, false}, {"complex pattern", "release/v1.2.3", []string{"release/*"}, true}, {"single star stops at slash", "release/1.0/hotfix", []string{"release/*"}, false}, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, _ := matchesPattern(tt.input, tt.patterns) assert.Equal(t, tt.expected, result, "matchesPattern(%q, %v) should be %v", tt.input, tt.patterns, tt.expected) }) }}
func TestConstraintMatchRef_Branches(t *testing.T) { tests := []struct { name string constraint Constraint ref string expected bool }{ { name: "exact branch match", constraint: Constraint{Branch: []string{"main"}}, ref: "refs/heads/main", expected: true, }, { name: "branch glob match", constraint: Constraint{Branch: []string{"feature-*"}}, ref: "refs/heads/feature-123", expected: true, }, { name: "branch no match", constraint: Constraint{Branch: []string{"main"}}, ref: "refs/heads/develop", expected: false, }, { name: "no constraints matches nothing", constraint: Constraint{}, ref: "refs/heads/anything", expected: false, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, _ := tt.constraint.MatchRef(tt.ref) assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref) }) }}
func TestConstraintMatchRef_Tags(t *testing.T) { tests := []struct { name string constraint Constraint ref string expected bool }{ { name: "exact tag match", constraint: Constraint{Tag: []string{"v1.0.0"}}, ref: "refs/tags/v1.0.0", expected: true, }, { name: "tag glob match", constraint: Constraint{Tag: []string{"v*"}}, ref: "refs/tags/v1.2.3", expected: true, }, { name: "tag glob with pattern", constraint: Constraint{Tag: []string{"release-*"}}, ref: "refs/tags/release-2024", expected: true, }, { name: "tag no match", constraint: Constraint{Tag: []string{"v*"}}, ref: "refs/tags/release-1.0", expected: false, }, { name: "tag not matched when only branch constraint", constraint: Constraint{Branch: []string{"main"}}, ref: "refs/tags/v1.0.0", expected: false, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, _ := tt.constraint.MatchRef(tt.ref) assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref) }) }}
func TestConstraintMatchRef_Combined(t *testing.T) { tests := []struct { name string constraint Constraint ref string expected bool }{ { name: "matches branch in combined constraint", constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}}, ref: "refs/heads/main", expected: true, }, { name: "matches tag in combined constraint", constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}}, ref: "refs/tags/v1.0.0", expected: true, }, { name: "no match in combined constraint", constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}}, ref: "refs/heads/develop", expected: false, }, { name: "glob patterns in combined constraint - branch", constraint: Constraint{Branch: []string{"release-*"}, Tag: []string{"v*"}}, ref: "refs/heads/release-2024", expected: true, }, { name: "glob patterns in combined constraint - tag", constraint: Constraint{Branch: []string{"release-*"}, Tag: []string{"v*"}}, ref: "refs/tags/v2.0.0", expected: true, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, _ := tt.constraint.MatchRef(tt.ref) assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref) }) }}
func TestConstraintMatchBranch_GlobPatterns(t *testing.T) { tests := []struct { name string constraint Constraint branch string expected bool }{ { name: "exact match", constraint: Constraint{Branch: []string{"main"}}, branch: "main", expected: true, }, { name: "glob match", constraint: Constraint{Branch: []string{"feature-*"}}, branch: "feature-123", expected: true, }, { name: "no match", constraint: Constraint{Branch: []string{"main"}}, branch: "develop", expected: false, }, { name: "multiple patterns with match", constraint: Constraint{Branch: []string{"main", "release-*"}}, branch: "release-1.0", expected: true, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, _ := tt.constraint.MatchBranch(tt.branch) assert.Equal(t, tt.expected, result, "MatchBranch should return %v for branch %q", tt.expected, tt.branch) }) }}
func TestMatchesAnyFile(t *testing.T) { tests := []struct { name string files []string patterns []string expected bool }{ { name: "exact file match", files: []string{"src/main.go"}, patterns: []string{"src/main.go"}, expected: true, }, { name: "glob match single star", files: []string{"src/main.go"}, patterns: []string{"src/*.go"}, expected: true, }, { name: "glob match double star", files: []string{"src/pkg/util.go"}, patterns: []string{"src/**/*.go"}, expected: true, }, { name: "any file in list matches", files: []string{"README.md", "src/main.go", "docs/guide.md"}, patterns: []string{"src/**"}, expected: true, }, { name: "no file matches", files: []string{"README.md", "docs/guide.md"}, patterns: []string{"src/**"}, expected: false, }, { name: "empty files list", files: []string{}, patterns: []string{"src/**"}, expected: false, }, { name: "nil files list", files: nil, patterns: []string{"src/**"}, expected: false, }, { name: "multiple patterns, second matches", files: []string{"docs/guide.md"}, patterns: []string{"src/**", "docs/**"}, expected: true, }, { name: "single star does not cross directory boundary", files: []string{"src/pkg/util.go"}, patterns: []string{"src/*.go"}, expected: false, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := matchesAnyFile(tt.files, tt.patterns) assert.NoError(t, err) assert.Equal(t, tt.expected, result) }) }}
func TestConstraintMatch_PathsFilter(t *testing.T) { pushTrigger := tangled.Pipeline_TriggerMetadata{ Kind: string(TriggerKindPush), Push: &tangled.Pipeline_PushTriggerData{ Ref: "refs/heads/main", }, }
tests := []struct { name string constraint Constraint changedFiles []string expected bool }{ { name: "paths match - workflow runs", constraint: Constraint{ Event: []string{"push"}, Branch: []string{"main"}, Paths: []string{"src/**"}, }, changedFiles: []string{"src/main.go"}, expected: true, }, { name: "paths no match - workflow skipped", constraint: Constraint{ Event: []string{"push"}, Branch: []string{"main"}, Paths: []string{"src/**"}, }, changedFiles: []string{"docs/guide.md"}, expected: false, }, { name: "no paths filter - all files pass", constraint: Constraint{ Event: []string{"push"}, Branch: []string{"main"}, }, changedFiles: []string{"docs/guide.md"}, expected: true, }, { name: "paths filter with empty changed files - skipped", constraint: Constraint{ Event: []string{"push"}, Branch: []string{"main"}, Paths: []string{"src/**"}, }, changedFiles: []string{}, expected: false, }, { name: "paths glob matches one of many changed files", constraint: Constraint{ Event: []string{"push"}, Branch: []string{"main"}, Paths: []string{"**/*.go"}, }, changedFiles: []string{"README.md", "go.mod", "src/main.go"}, expected: true, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := tt.constraint.Match(pushTrigger, tt.changedFiles) assert.NoError(t, err) assert.Equal(t, tt.expected, result) }) }}
func TestUnmarshalWorkflowWithPaths(t *testing.T) { yamlData := `when: - event: push branch: main paths: - "src/**" - "**.go"`
wf, err := FromFile("test.yml", []byte(yamlData)) assert.NoError(t, err) assert.Len(t, wf.When, 1) assert.ElementsMatch(t, []string{"src/**", "**.go"}, wf.When[0].Paths)}
func TestUnmarshalWorkflowWithPathsSingleString(t *testing.T) { yamlData := `when: - event: push branch: main paths: "src/**"`
wf, err := FromFile("test.yml", []byte(yamlData)) assert.NoError(t, err) assert.Len(t, wf.When, 1) assert.ElementsMatch(t, []string{"src/**"}, wf.When[0].Paths)}
func TestConstraintMatchTag_GlobPatterns(t *testing.T) { tests := []struct { name string constraint Constraint tag string expected bool }{ { name: "exact match", constraint: Constraint{Tag: []string{"v1.0.0"}}, tag: "v1.0.0", expected: true, }, { name: "glob match", constraint: Constraint{Tag: []string{"v*"}}, tag: "v2.3.4", expected: true, }, { name: "no match", constraint: Constraint{Tag: []string{"v*"}}, tag: "release-1.0", expected: false, }, { name: "multiple patterns with match", constraint: Constraint{Tag: []string{"v*", "release-*"}}, tag: "release-2024", expected: true, }, { name: "empty tag list matches nothing", constraint: Constraint{Tag: []string{}}, tag: "v1.0.0", expected: false, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, _ := tt.constraint.MatchTag(tt.tag) assert.Equal(t, tt.expected, result, "MatchTag should return %v for tag %q", tt.expected, tt.tag) }) }}