Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

at master 13 kB View raw
1package workflow 2 3import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "tangled.org/core/api/tangled" 8) 9 10func TestUnmarshalWorkflowWithBranch(t *testing.T) { 11 yamlData := ` 12when: 13 - event: ["push", "pull_request"] 14 branch: ["main", "develop"]` 15 16 wf, err := FromFile("test.yml", []byte(yamlData)) 17 assert.NoError(t, err, "YAML should unmarshal without error") 18 19 assert.Len(t, wf.When, 1, "Should have one constraint") 20 assert.ElementsMatch(t, []string{"main", "develop"}, wf.When[0].Branch) 21 assert.ElementsMatch(t, []string{"push", "pull_request"}, wf.When[0].Event) 22 23 assert.False(t, wf.CloneOpts.Skip, "Skip should default to false") 24} 25 26func TestUnmarshalCloneFalse(t *testing.T) { 27 yamlData := ` 28when: 29 - event: pull_request_close 30 31clone: 32 skip: true 33` 34 35 wf, err := FromFile("test.yml", []byte(yamlData)) 36 assert.NoError(t, err) 37 38 assert.ElementsMatch(t, []string{"pull_request_close"}, wf.When[0].Event) 39 40 assert.True(t, wf.CloneOpts.Skip, "Skip should be false") 41} 42 43func TestUnmarshalWorkflowWithTags(t *testing.T) { 44 yamlData := ` 45when: 46 - event: ["push"] 47 tag: ["v*", "release-*"]` 48 49 wf, err := FromFile("test.yml", []byte(yamlData)) 50 assert.NoError(t, err, "YAML should unmarshal without error") 51 52 assert.Len(t, wf.When, 1, "Should have one constraint") 53 assert.ElementsMatch(t, []string{"v*", "release-*"}, wf.When[0].Tag) 54 assert.ElementsMatch(t, []string{"push"}, wf.When[0].Event) 55} 56 57func TestUnmarshalWorkflowWithBranchAndTag(t *testing.T) { 58 yamlData := ` 59when: 60 - event: ["push"] 61 branch: ["main", "develop"] 62 tag: ["v*"]` 63 64 wf, err := FromFile("test.yml", []byte(yamlData)) 65 assert.NoError(t, err, "YAML should unmarshal without error") 66 67 assert.Len(t, wf.When, 1, "Should have one constraint") 68 assert.ElementsMatch(t, []string{"main", "develop"}, wf.When[0].Branch) 69 assert.ElementsMatch(t, []string{"v*"}, wf.When[0].Tag) 70} 71 72func TestMatchesPattern(t *testing.T) { 73 tests := []struct { 74 name string 75 input string 76 patterns []string 77 expected bool 78 }{ 79 {"exact match", "main", []string{"main"}, true}, 80 {"exact match in list", "develop", []string{"main", "develop"}, true}, 81 {"no match", "feature", []string{"main", "develop"}, false}, 82 {"wildcard prefix", "v1.0.0", []string{"v*"}, true}, 83 {"wildcard suffix", "release-1.0", []string{"*-1.0"}, true}, 84 {"wildcard middle", "feature-123-test", []string{"feature-*-test"}, true}, 85 {"double star prefix", "release-1.0.0", []string{"release-**"}, true}, 86 {"double star with slashes", "release/1.0/hotfix", []string{"release/**"}, true}, 87 {"double star matches multiple levels", "foo/bar/baz/qux", []string{"foo/**"}, true}, 88 {"double star no match", "feature/test", []string{"release/**"}, false}, 89 {"no patterns matches nothing", "anything", []string{}, false}, 90 {"pattern doesn't match", "v1.0.0", []string{"release-*"}, false}, 91 {"complex pattern", "release/v1.2.3", []string{"release/*"}, true}, 92 {"single star stops at slash", "release/1.0/hotfix", []string{"release/*"}, false}, 93 } 94 95 for _, tt := range tests { 96 t.Run(tt.name, func(t *testing.T) { 97 result, _ := matchesPattern(tt.input, tt.patterns) 98 assert.Equal(t, tt.expected, result, "matchesPattern(%q, %v) should be %v", tt.input, tt.patterns, tt.expected) 99 }) 100 } 101} 102 103func TestConstraintMatchRef_Branches(t *testing.T) { 104 tests := []struct { 105 name string 106 constraint Constraint 107 ref string 108 expected bool 109 }{ 110 { 111 name: "exact branch match", 112 constraint: Constraint{Branch: []string{"main"}}, 113 ref: "refs/heads/main", 114 expected: true, 115 }, 116 { 117 name: "branch glob match", 118 constraint: Constraint{Branch: []string{"feature-*"}}, 119 ref: "refs/heads/feature-123", 120 expected: true, 121 }, 122 { 123 name: "branch no match", 124 constraint: Constraint{Branch: []string{"main"}}, 125 ref: "refs/heads/develop", 126 expected: false, 127 }, 128 { 129 name: "no constraints matches nothing", 130 constraint: Constraint{}, 131 ref: "refs/heads/anything", 132 expected: false, 133 }, 134 } 135 136 for _, tt := range tests { 137 t.Run(tt.name, func(t *testing.T) { 138 result, _ := tt.constraint.MatchRef(tt.ref) 139 assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref) 140 }) 141 } 142} 143 144func TestConstraintMatchRef_Tags(t *testing.T) { 145 tests := []struct { 146 name string 147 constraint Constraint 148 ref string 149 expected bool 150 }{ 151 { 152 name: "exact tag match", 153 constraint: Constraint{Tag: []string{"v1.0.0"}}, 154 ref: "refs/tags/v1.0.0", 155 expected: true, 156 }, 157 { 158 name: "tag glob match", 159 constraint: Constraint{Tag: []string{"v*"}}, 160 ref: "refs/tags/v1.2.3", 161 expected: true, 162 }, 163 { 164 name: "tag glob with pattern", 165 constraint: Constraint{Tag: []string{"release-*"}}, 166 ref: "refs/tags/release-2024", 167 expected: true, 168 }, 169 { 170 name: "tag no match", 171 constraint: Constraint{Tag: []string{"v*"}}, 172 ref: "refs/tags/release-1.0", 173 expected: false, 174 }, 175 { 176 name: "tag not matched when only branch constraint", 177 constraint: Constraint{Branch: []string{"main"}}, 178 ref: "refs/tags/v1.0.0", 179 expected: false, 180 }, 181 } 182 183 for _, tt := range tests { 184 t.Run(tt.name, func(t *testing.T) { 185 result, _ := tt.constraint.MatchRef(tt.ref) 186 assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref) 187 }) 188 } 189} 190 191func TestConstraintMatchRef_Combined(t *testing.T) { 192 tests := []struct { 193 name string 194 constraint Constraint 195 ref string 196 expected bool 197 }{ 198 { 199 name: "matches branch in combined constraint", 200 constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}}, 201 ref: "refs/heads/main", 202 expected: true, 203 }, 204 { 205 name: "matches tag in combined constraint", 206 constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}}, 207 ref: "refs/tags/v1.0.0", 208 expected: true, 209 }, 210 { 211 name: "no match in combined constraint", 212 constraint: Constraint{Branch: []string{"main"}, Tag: []string{"v*"}}, 213 ref: "refs/heads/develop", 214 expected: false, 215 }, 216 { 217 name: "glob patterns in combined constraint - branch", 218 constraint: Constraint{Branch: []string{"release-*"}, Tag: []string{"v*"}}, 219 ref: "refs/heads/release-2024", 220 expected: true, 221 }, 222 { 223 name: "glob patterns in combined constraint - tag", 224 constraint: Constraint{Branch: []string{"release-*"}, Tag: []string{"v*"}}, 225 ref: "refs/tags/v2.0.0", 226 expected: true, 227 }, 228 } 229 230 for _, tt := range tests { 231 t.Run(tt.name, func(t *testing.T) { 232 result, _ := tt.constraint.MatchRef(tt.ref) 233 assert.Equal(t, tt.expected, result, "MatchRef should return %v for ref %q", tt.expected, tt.ref) 234 }) 235 } 236} 237 238func TestConstraintMatchBranch_GlobPatterns(t *testing.T) { 239 tests := []struct { 240 name string 241 constraint Constraint 242 branch string 243 expected bool 244 }{ 245 { 246 name: "exact match", 247 constraint: Constraint{Branch: []string{"main"}}, 248 branch: "main", 249 expected: true, 250 }, 251 { 252 name: "glob match", 253 constraint: Constraint{Branch: []string{"feature-*"}}, 254 branch: "feature-123", 255 expected: true, 256 }, 257 { 258 name: "no match", 259 constraint: Constraint{Branch: []string{"main"}}, 260 branch: "develop", 261 expected: false, 262 }, 263 { 264 name: "multiple patterns with match", 265 constraint: Constraint{Branch: []string{"main", "release-*"}}, 266 branch: "release-1.0", 267 expected: true, 268 }, 269 } 270 271 for _, tt := range tests { 272 t.Run(tt.name, func(t *testing.T) { 273 result, _ := tt.constraint.MatchBranch(tt.branch) 274 assert.Equal(t, tt.expected, result, "MatchBranch should return %v for branch %q", tt.expected, tt.branch) 275 }) 276 } 277} 278 279func TestMatchesAnyFile(t *testing.T) { 280 tests := []struct { 281 name string 282 files []string 283 patterns []string 284 expected bool 285 }{ 286 { 287 name: "exact file match", 288 files: []string{"src/main.go"}, 289 patterns: []string{"src/main.go"}, 290 expected: true, 291 }, 292 { 293 name: "glob match single star", 294 files: []string{"src/main.go"}, 295 patterns: []string{"src/*.go"}, 296 expected: true, 297 }, 298 { 299 name: "glob match double star", 300 files: []string{"src/pkg/util.go"}, 301 patterns: []string{"src/**/*.go"}, 302 expected: true, 303 }, 304 { 305 name: "any file in list matches", 306 files: []string{"README.md", "src/main.go", "docs/guide.md"}, 307 patterns: []string{"src/**"}, 308 expected: true, 309 }, 310 { 311 name: "no file matches", 312 files: []string{"README.md", "docs/guide.md"}, 313 patterns: []string{"src/**"}, 314 expected: false, 315 }, 316 { 317 name: "empty files list", 318 files: []string{}, 319 patterns: []string{"src/**"}, 320 expected: false, 321 }, 322 { 323 name: "nil files list", 324 files: nil, 325 patterns: []string{"src/**"}, 326 expected: false, 327 }, 328 { 329 name: "multiple patterns, second matches", 330 files: []string{"docs/guide.md"}, 331 patterns: []string{"src/**", "docs/**"}, 332 expected: true, 333 }, 334 { 335 name: "single star does not cross directory boundary", 336 files: []string{"src/pkg/util.go"}, 337 patterns: []string{"src/*.go"}, 338 expected: false, 339 }, 340 } 341 342 for _, tt := range tests { 343 t.Run(tt.name, func(t *testing.T) { 344 result, err := matchesAnyFile(tt.files, tt.patterns) 345 assert.NoError(t, err) 346 assert.Equal(t, tt.expected, result) 347 }) 348 } 349} 350 351func TestConstraintMatch_PathsFilter(t *testing.T) { 352 pushTrigger := tangled.Pipeline_TriggerMetadata{ 353 Kind: string(TriggerKindPush), 354 Push: &tangled.Pipeline_PushTriggerData{ 355 Ref: "refs/heads/main", 356 }, 357 } 358 359 tests := []struct { 360 name string 361 constraint Constraint 362 changedFiles []string 363 expected bool 364 }{ 365 { 366 name: "paths match - workflow runs", 367 constraint: Constraint{ 368 Event: []string{"push"}, 369 Branch: []string{"main"}, 370 Paths: []string{"src/**"}, 371 }, 372 changedFiles: []string{"src/main.go"}, 373 expected: true, 374 }, 375 { 376 name: "paths no match - workflow skipped", 377 constraint: Constraint{ 378 Event: []string{"push"}, 379 Branch: []string{"main"}, 380 Paths: []string{"src/**"}, 381 }, 382 changedFiles: []string{"docs/guide.md"}, 383 expected: false, 384 }, 385 { 386 name: "no paths filter - all files pass", 387 constraint: Constraint{ 388 Event: []string{"push"}, 389 Branch: []string{"main"}, 390 }, 391 changedFiles: []string{"docs/guide.md"}, 392 expected: true, 393 }, 394 { 395 name: "paths filter with empty changed files - skipped", 396 constraint: Constraint{ 397 Event: []string{"push"}, 398 Branch: []string{"main"}, 399 Paths: []string{"src/**"}, 400 }, 401 changedFiles: []string{}, 402 expected: false, 403 }, 404 { 405 name: "paths glob matches one of many changed files", 406 constraint: Constraint{ 407 Event: []string{"push"}, 408 Branch: []string{"main"}, 409 Paths: []string{"**/*.go"}, 410 }, 411 changedFiles: []string{"README.md", "go.mod", "src/main.go"}, 412 expected: true, 413 }, 414 } 415 416 for _, tt := range tests { 417 t.Run(tt.name, func(t *testing.T) { 418 result, err := tt.constraint.Match(pushTrigger, tt.changedFiles) 419 assert.NoError(t, err) 420 assert.Equal(t, tt.expected, result) 421 }) 422 } 423} 424 425func TestUnmarshalWorkflowWithPaths(t *testing.T) { 426 yamlData := ` 427when: 428 - event: push 429 branch: main 430 paths: 431 - "src/**" 432 - "**.go"` 433 434 wf, err := FromFile("test.yml", []byte(yamlData)) 435 assert.NoError(t, err) 436 assert.Len(t, wf.When, 1) 437 assert.ElementsMatch(t, []string{"src/**", "**.go"}, wf.When[0].Paths) 438} 439 440func TestUnmarshalWorkflowWithPathsSingleString(t *testing.T) { 441 yamlData := ` 442when: 443 - event: push 444 branch: main 445 paths: "src/**"` 446 447 wf, err := FromFile("test.yml", []byte(yamlData)) 448 assert.NoError(t, err) 449 assert.Len(t, wf.When, 1) 450 assert.ElementsMatch(t, []string{"src/**"}, wf.When[0].Paths) 451} 452 453func TestConstraintMatchTag_GlobPatterns(t *testing.T) { 454 tests := []struct { 455 name string 456 constraint Constraint 457 tag string 458 expected bool 459 }{ 460 { 461 name: "exact match", 462 constraint: Constraint{Tag: []string{"v1.0.0"}}, 463 tag: "v1.0.0", 464 expected: true, 465 }, 466 { 467 name: "glob match", 468 constraint: Constraint{Tag: []string{"v*"}}, 469 tag: "v2.3.4", 470 expected: true, 471 }, 472 { 473 name: "no match", 474 constraint: Constraint{Tag: []string{"v*"}}, 475 tag: "release-1.0", 476 expected: false, 477 }, 478 { 479 name: "multiple patterns with match", 480 constraint: Constraint{Tag: []string{"v*", "release-*"}}, 481 tag: "release-2024", 482 expected: true, 483 }, 484 { 485 name: "empty tag list matches nothing", 486 constraint: Constraint{Tag: []string{}}, 487 tag: "v1.0.0", 488 expected: false, 489 }, 490 } 491 492 for _, tt := range tests { 493 t.Run(tt.name, func(t *testing.T) { 494 result, _ := tt.constraint.MatchTag(tt.tag) 495 assert.Equal(t, tt.expected, result, "MatchTag should return %v for tag %q", tt.expected, tt.tag) 496 }) 497 } 498}