1package workflow
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7)
8
9func TestUnmarshalWorkflowWithBranch(t *testing.T) {
10 yamlData := `
11when:
12 - event: ["push", "pull_request"]
13 branch: ["main", "develop"]`
14
15 wf, err := FromFile("test.yml", []byte(yamlData))
16 assert.NoError(t, err, "YAML should unmarshal without error")
17
18 assert.Len(t, wf.When, 1, "Should have one constraint")
19 assert.ElementsMatch(t, []string{"main", "develop"}, wf.When[0].Branch)
20 assert.ElementsMatch(t, []string{"push", "pull_request"}, wf.When[0].Event)
21
22 assert.False(t, wf.CloneOpts.Skip, "Skip should default to false")
23}
24
25func TestUnmarshalCloneFalse(t *testing.T) {
26 yamlData := `
27when:
28 - event: pull_request_close
29
30clone:
31 skip: true
32`
33
34 wf, err := FromFile("test.yml", []byte(yamlData))
35 assert.NoError(t, err)
36
37 assert.ElementsMatch(t, []string{"pull_request_close"}, wf.When[0].Event)
38
39 assert.True(t, wf.CloneOpts.Skip, "Skip should be false")
40}
41
42func TestUnmarshalWorkflowWithTags(t *testing.T) {
43 yamlData := `
44when:
45 - event: ["push"]
46 tag: ["v*", "release-*"]`
47
48 wf, err := FromFile("test.yml", []byte(yamlData))
49 assert.NoError(t, err, "YAML should unmarshal without error")
50
51 assert.Len(t, wf.When, 1, "Should have one constraint")
52 assert.ElementsMatch(t, []string{"v*", "release-*"}, wf.When[0].Tag)
53 assert.ElementsMatch(t, []string{"push"}, wf.When[0].Event)
54}
55
56func TestUnmarshalWorkflowWithBranchAndTag(t *testing.T) {
57 yamlData := `
58when:
59 - event: ["push"]
60 branch: ["main", "develop"]
61 tag: ["v*"]`
62
63 wf, err := FromFile("test.yml", []byte(yamlData))
64 assert.NoError(t, err, "YAML should unmarshal without error")
65
66 assert.Len(t, wf.When, 1, "Should have one constraint")
67 assert.ElementsMatch(t, []string{"main", "develop"}, wf.When[0].Branch)
68 assert.ElementsMatch(t, []string{"v*"}, wf.When[0].Tag)
69}
70
71func TestMatchesPattern(t *testing.T) {
72 tests := []struct {
73 name string
74 input string
75 patterns []string
76 expected bool
77 }{
78 {"exact match", "main", []string{"main"}, true},
79 {"exact match in list", "develop", []string{"main", "develop"}, true},
80 {"no match", "feature", []string{"main", "develop"}, false},
81 {"wildcard prefix", "v1.0.0", []string{"v*"}, true},
82 {"wildcard suffix", "release-1.0", []string{"*-1.0"}, true},
83 {"wildcard middle", "feature-123-test", []string{"feature-*-test"}, true},
84 {"double star prefix", "release-1.0.0", []string{"release-**"}, true},
85 {"double star with slashes", "release/1.0/hotfix", []string{"release/**"}, true},
86 {"double star matches multiple levels", "foo/bar/baz/qux", []string{"foo/**"}, true},
87 {"double star no match", "feature/test", []string{"release/**"}, false},
88 {"no patterns matches nothing", "anything", []string{}, false},
89 {"pattern doesn't match", "v1.0.0", []string{"release-*"}, false},
90 {"complex pattern", "release/v1.2.3", []string{"release/*"}, true},
91 {"single star stops at slash", "release/1.0/hotfix", []string{"release/*"}, false},
92 }
93
94 for _, tt := range tests {
95 t.Run(tt.name, func(t *testing.T) {
96 result, _ := matchesPattern(tt.input, tt.patterns)
97 assert.Equal(t, tt.expected, result, "matchesPattern(%q, %v) should be %v", tt.input, tt.patterns, tt.expected)
98 })
99 }
100}
101
102func TestConstraintMatchRef_Branches(t *testing.T) {
103 tests := []struct {
104 name string
105 constraint Constraint
106 ref string
107 expected bool
108 }{
109 {
110 name: "exact branch match",
111 constraint: Constraint{Branch: []string{"main"}},
112 ref: "refs/heads/main",
113 expected: true,
114 },
115 {
116 name: "branch glob match",
117 constraint: Constraint{Branch: []string{"feature-*"}},
118 ref: "refs/heads/feature-123",
119 expected: true,
120 },
121 {
122 name: "branch no match",
123 constraint: Constraint{Branch: []string{"main"}},
124 ref: "refs/heads/develop",
125 expected: false,
126 },
127 {
128 name: "no constraints matches nothing",
129 constraint: Constraint{},
130 ref: "refs/heads/anything",
131 expected: false,
132 },
133 }
134
135 for _, tt := range tests {
136 t.Run(tt.name, func(t *testing.T) {
137 result, _ := tt.constraint.MatchRef(tt.ref)
138 assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref)
139 })
140 }
141}
142
143func TestConstraintMatchRef_Tags(t *testing.T) {
144 tests := []struct {
145 name string
146 constraint Constraint
147 ref string
148 expected bool
149 }{
150 {
151 name: "exact tag match",
152 constraint: Constraint{Tag: []string{"v1.0.0"}},
153 ref: "refs/tags/v1.0.0",
154 expected: true,
155 },
156 {
157 name: "tag glob match",
158 constraint: Constraint{Tag: []string{"v*"}},
159 ref: "refs/tags/v1.2.3",
160 expected: true,
161 },
162 {
163 name: "tag glob with pattern",
164 constraint: Constraint{Tag: []string{"release-*"}},
165 ref: "refs/tags/release-2024",
166 expected: true,
167 },
168 {
169 name: "tag no match",
170 constraint: Constraint{Tag: []string{"v*"}},
171 ref: "refs/tags/release-1.0",
172 expected: false,
173 },
174 {
175 name: "tag not matched when only branch constraint",
176 constraint: Constraint{Branch: []string{"main"}},
177 ref: "refs/tags/v1.0.0",
178 expected: false,
179 },
180 }
181
182 for _, tt := range tests {
183 t.Run(tt.name, func(t *testing.T) {
184 result, _ := tt.constraint.MatchRef(tt.ref)
185 assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref)
186 })
187 }
188}
189
190func TestConstraintMatchRef_Combined(t *testing.T) {
191 tests := []struct {
192 name string
193 constraint Constraint
194 ref string
195 expected bool
196 }{
197 {
198 name: "matches branch in combined constraint",
199 constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}},
200 ref: "refs/heads/main",
201 expected: true,
202 },
203 {
204 name: "matches tag in combined constraint",
205 constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}},
206 ref: "refs/tags/v1.0.0",
207 expected: true,
208 },
209 {
210 name: "no match in combined constraint",
211 constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}},
212 ref: "refs/heads/develop",
213 expected: false,
214 },
215 {
216 name: "glob patterns in combined constraint - branch",
217 constraint: Constraint{Branch: []string{"release-*"}, Tag: []string{"v*"}},
218 ref: "refs/heads/release-2024",
219 expected: true,
220 },
221 {
222 name: "glob patterns in combined constraint - tag",
223 constraint: Constraint{Branch: []string{"release-*"}, Tag: []string{"v*"}},
224 ref: "refs/tags/v2.0.0",
225 expected: true,
226 },
227 }
228
229 for _, tt := range tests {
230 t.Run(tt.name, func(t *testing.T) {
231 result, _ := tt.constraint.MatchRef(tt.ref)
232 assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref)
233 })
234 }
235}
236
237func TestConstraintMatchBranch_GlobPatterns(t *testing.T) {
238 tests := []struct {
239 name string
240 constraint Constraint
241 branch string
242 expected bool
243 }{
244 {
245 name: "exact match",
246 constraint: Constraint{Branch: []string{"main"}},
247 branch: "main",
248 expected: true,
249 },
250 {
251 name: "glob match",
252 constraint: Constraint{Branch: []string{"feature-*"}},
253 branch: "feature-123",
254 expected: true,
255 },
256 {
257 name: "no match",
258 constraint: Constraint{Branch: []string{"main"}},
259 branch: "develop",
260 expected: false,
261 },
262 {
263 name: "multiple patterns with match",
264 constraint: Constraint{Branch: []string{"main", "release-*"}},
265 branch: "release-1.0",
266 expected: true,
267 },
268 }
269
270 for _, tt := range tests {
271 t.Run(tt.name, func(t *testing.T) {
272 result, _ := tt.constraint.MatchBranch(tt.branch)
273 assert.Equal(t, tt.expected, result, "MatchBranch should return %v for branch %q", tt.expected, tt.branch)
274 })
275 }
276}
277
278func TestConstraintMatchTag_GlobPatterns(t *testing.T) {
279 tests := []struct {
280 name string
281 constraint Constraint
282 tag string
283 expected bool
284 }{
285 {
286 name: "exact match",
287 constraint: Constraint{Tag: []string{"v1.0.0"}},
288 tag: "v1.0.0",
289 expected: true,
290 },
291 {
292 name: "glob match",
293 constraint: Constraint{Tag: []string{"v*"}},
294 tag: "v2.3.4",
295 expected: true,
296 },
297 {
298 name: "no match",
299 constraint: Constraint{Tag: []string{"v*"}},
300 tag: "release-1.0",
301 expected: false,
302 },
303 {
304 name: "multiple patterns with match",
305 constraint: Constraint{Tag: []string{"v*", "release-*"}},
306 tag: "release-2024",
307 expected: true,
308 },
309 {
310 name: "empty tag list matches nothing",
311 constraint: Constraint{Tag: []string{}},
312 tag: "v1.0.0",
313 expected: false,
314 },
315 }
316
317 for _, tt := range tests {
318 t.Run(tt.name, func(t *testing.T) {
319 result, _ := tt.constraint.MatchTag(tt.tag)
320 assert.Equal(t, tt.expected, result, "MatchTag should return %v for tag %q", tt.expected, tt.tag)
321 })
322 }
323}