github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/services/pull_request/bitbucket_server_test.go (about) 1 package pull_request 2 3 import ( 4 "crypto/x509" 5 "encoding/pem" 6 "io" 7 "net/http" 8 "net/http/httptest" 9 "testing" 10 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 14 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 15 ) 16 17 func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { 18 t.Helper() 19 return func(w http.ResponseWriter, r *http.Request) { 20 w.Header().Set("Content-Type", "application/json") 21 var err error 22 switch r.RequestURI { 23 case "/rest/api/1.0/projects/PROJECT/repos/REPO/pull-requests?limit=100": 24 _, err = io.WriteString(w, `{ 25 "size": 1, 26 "limit": 100, 27 "isLastPage": true, 28 "values": [ 29 { 30 "id": 101, 31 "title": "feat(ABC) : 123", 32 "toRef": { 33 "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", 34 "displayId": "master", 35 "id": "refs/heads/master" 36 }, 37 "fromRef": { 38 "id": "refs/heads/feature-ABC-123", 39 "displayId": "feature-ABC-123", 40 "latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992" 41 }, 42 "author": { 43 "user": { 44 "name": "testName" 45 } 46 } 47 } 48 ], 49 "start": 0 50 }`) 51 default: 52 t.Fail() 53 } 54 if err != nil { 55 t.Fail() 56 } 57 } 58 } 59 60 func TestListPullRequestNoAuth(t *testing.T) { 61 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 62 assert.Empty(t, r.Header.Get("Authorization")) 63 defaultHandler(t)(w, r) 64 })) 65 defer ts.Close() 66 svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) 67 require.NoError(t, err) 68 pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) 69 require.NoError(t, err) 70 assert.Len(t, pullRequests, 1) 71 assert.Equal(t, 101, pullRequests[0].Number) 72 assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title) 73 assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch) 74 assert.Equal(t, "master", pullRequests[0].TargetBranch) 75 assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA) 76 assert.Equal(t, "testName", pullRequests[0].Author) 77 } 78 79 func TestListPullRequestPagination(t *testing.T) { 80 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 81 w.Header().Set("Content-Type", "application/json") 82 var err error 83 switch r.RequestURI { 84 case "/rest/api/1.0/projects/PROJECT/repos/REPO/pull-requests?limit=100": 85 _, err = io.WriteString(w, `{ 86 "size": 2, 87 "limit": 2, 88 "isLastPage": false, 89 "values": [ 90 { 91 "id": 101, 92 "title": "feat(101)", 93 "toRef": { 94 "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", 95 "displayId": "master", 96 "id": "refs/heads/master" 97 }, 98 "fromRef": { 99 "id": "refs/heads/feature-101", 100 "displayId": "feature-101", 101 "latestCommit": "ab3cf2e4d1517c83e720d2585b9402dbef71f992" 102 }, 103 "author": { 104 "user": { 105 "name": "testName" 106 } 107 } 108 }, 109 { 110 "id": 102, 111 "title": "feat(102)", 112 "toRef": { 113 "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", 114 "displayId": "branch", 115 "id": "refs/heads/branch" 116 }, 117 "fromRef": { 118 "id": "refs/heads/feature-102", 119 "displayId": "feature-102", 120 "latestCommit": "bb3cf2e4d1517c83e720d2585b9402dbef71f992" 121 }, 122 "author": { 123 "user": { 124 "name": "testName" 125 } 126 } 127 } 128 ], 129 "nextPageStart": 200 130 }`) 131 case "/rest/api/1.0/projects/PROJECT/repos/REPO/pull-requests?limit=100&start=200": 132 _, err = io.WriteString(w, `{ 133 "size": 1, 134 "limit": 2, 135 "isLastPage": true, 136 "values": [ 137 { 138 "id": 200, 139 "title": "feat(200)", 140 "toRef": { 141 "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", 142 "displayId": "master", 143 "id": "refs/heads/master" 144 }, 145 "fromRef": { 146 "id": "refs/heads/feature-200", 147 "displayId": "feature-200", 148 "latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992" 149 }, 150 "author": { 151 "user": { 152 "name": "testName" 153 } 154 } 155 } 156 ], 157 "start": 200 158 }`) 159 default: 160 t.Fail() 161 } 162 if err != nil { 163 t.Fail() 164 } 165 })) 166 defer ts.Close() 167 svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) 168 require.NoError(t, err) 169 pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) 170 require.NoError(t, err) 171 assert.Len(t, pullRequests, 3) 172 assert.Equal(t, PullRequest{ 173 Number: 101, 174 Title: "feat(101)", 175 Branch: "feature-101", 176 TargetBranch: "master", 177 HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992", 178 Labels: []string{}, 179 Author: "testName", 180 }, *pullRequests[0]) 181 assert.Equal(t, PullRequest{ 182 Number: 102, 183 Title: "feat(102)", 184 Branch: "feature-102", 185 TargetBranch: "branch", 186 HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992", 187 Labels: []string{}, 188 Author: "testName", 189 }, *pullRequests[1]) 190 assert.Equal(t, PullRequest{ 191 Number: 200, 192 Title: "feat(200)", 193 Branch: "feature-200", 194 TargetBranch: "master", 195 HeadSHA: "cb3cf2e4d1517c83e720d2585b9402dbef71f992", 196 Labels: []string{}, 197 Author: "testName", 198 }, *pullRequests[2]) 199 } 200 201 func TestListPullRequestBasicAuth(t *testing.T) { 202 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 203 // base64(user:password) 204 assert.Equal(t, "Basic dXNlcjpwYXNzd29yZA==", r.Header.Get("Authorization")) 205 assert.Equal(t, "no-check", r.Header.Get("X-Atlassian-Token")) 206 defaultHandler(t)(w, r) 207 })) 208 defer ts.Close() 209 svc, err := NewBitbucketServiceBasicAuth(t.Context(), "user", "password", ts.URL, "PROJECT", "REPO", "", false, nil) 210 require.NoError(t, err) 211 pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) 212 require.NoError(t, err) 213 assert.Len(t, pullRequests, 1) 214 assert.Equal(t, 101, pullRequests[0].Number) 215 assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch) 216 assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA) 217 } 218 219 func TestListPullRequestBearerAuth(t *testing.T) { 220 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 221 assert.Equal(t, "Bearer tolkien", r.Header.Get("Authorization")) 222 assert.Equal(t, "no-check", r.Header.Get("X-Atlassian-Token")) 223 defaultHandler(t)(w, r) 224 })) 225 defer ts.Close() 226 svc, err := NewBitbucketServiceBearerToken(t.Context(), "tolkien", ts.URL, "PROJECT", "REPO", "", false, nil) 227 require.NoError(t, err) 228 pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) 229 require.NoError(t, err) 230 assert.Len(t, pullRequests, 1) 231 assert.Equal(t, 101, pullRequests[0].Number) 232 assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title) 233 assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch) 234 assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA) 235 } 236 237 func TestListPullRequestTLS(t *testing.T) { 238 tests := []struct { 239 name string 240 tlsInsecure bool 241 passCerts bool 242 requireErr bool 243 }{ 244 { 245 name: "TLS Insecure: true, No Certs", 246 tlsInsecure: true, 247 passCerts: false, 248 requireErr: false, 249 }, 250 { 251 name: "TLS Insecure: true, With Certs", 252 tlsInsecure: true, 253 passCerts: true, 254 requireErr: false, 255 }, 256 { 257 name: "TLS Insecure: false, With Certs", 258 tlsInsecure: false, 259 passCerts: true, 260 requireErr: false, 261 }, 262 { 263 name: "TLS Insecure: false, No Certs", 264 tlsInsecure: false, 265 passCerts: false, 266 requireErr: true, 267 }, 268 } 269 270 for _, test := range tests { 271 test := test 272 t.Run(test.name, func(t *testing.T) { 273 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 274 defaultHandler(t)(w, r) 275 })) 276 defer ts.Close() 277 278 var certs []byte 279 if test.passCerts { 280 for _, cert := range ts.TLS.Certificates { 281 for _, c := range cert.Certificate { 282 parsedCert, err := x509.ParseCertificate(c) 283 require.NoError(t, err, "Failed to parse certificate") 284 certs = append(certs, pem.EncodeToMemory(&pem.Block{ 285 Type: "CERTIFICATE", 286 Bytes: parsedCert.Raw, 287 })...) 288 } 289 } 290 } 291 292 svc, err := NewBitbucketServiceBasicAuth(t.Context(), "user", "password", ts.URL, "PROJECT", "REPO", "", test.tlsInsecure, certs) 293 require.NoError(t, err) 294 _, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) 295 if test.requireErr { 296 require.Error(t, err) 297 } else { 298 require.NoError(t, err) 299 } 300 }) 301 } 302 } 303 304 func TestListResponseError(t *testing.T) { 305 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 306 w.WriteHeader(http.StatusInternalServerError) 307 })) 308 defer ts.Close() 309 svc, _ := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) 310 _, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) 311 require.Error(t, err) 312 } 313 314 func TestListResponseMalformed(t *testing.T) { 315 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 316 w.Header().Set("Content-Type", "application/json") 317 switch r.RequestURI { 318 case "/rest/api/1.0/projects/PROJECT/repos/REPO/pull-requests?limit=100": 319 _, err := io.WriteString(w, `{ 320 "size": 1, 321 "limit": 100, 322 "isLastPage": true, 323 "values": { "id": 101 }, 324 "start": 0 325 }`) 326 if err != nil { 327 t.Fail() 328 } 329 default: 330 t.Fail() 331 } 332 })) 333 defer ts.Close() 334 svc, _ := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) 335 _, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) 336 require.Error(t, err) 337 } 338 339 func TestListResponseEmpty(t *testing.T) { 340 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 341 w.Header().Set("Content-Type", "application/json") 342 switch r.RequestURI { 343 case "/rest/api/1.0/projects/PROJECT/repos/REPO/pull-requests?limit=100": 344 _, err := io.WriteString(w, `{ 345 "size": 0, 346 "limit": 100, 347 "isLastPage": true, 348 "values": [], 349 "start": 0 350 }`) 351 if err != nil { 352 t.Fail() 353 } 354 default: 355 t.Fail() 356 } 357 })) 358 defer ts.Close() 359 svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) 360 require.NoError(t, err) 361 pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) 362 require.NoError(t, err) 363 assert.Empty(t, pullRequests) 364 } 365 366 func TestListPullRequestBranchMatch(t *testing.T) { 367 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 368 w.Header().Set("Content-Type", "application/json") 369 var err error 370 switch r.RequestURI { 371 case "/rest/api/1.0/projects/PROJECT/repos/REPO/pull-requests?limit=100": 372 _, err = io.WriteString(w, `{ 373 "size": 2, 374 "limit": 2, 375 "isLastPage": false, 376 "values": [ 377 { 378 "id": 101, 379 "title": "feat(101)", 380 "toRef": { 381 "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", 382 "displayId": "master", 383 "id": "refs/heads/master" 384 }, 385 "fromRef": { 386 "id": "refs/heads/feature-101", 387 "displayId": "feature-101", 388 "latestCommit": "ab3cf2e4d1517c83e720d2585b9402dbef71f992" 389 }, 390 "author": { 391 "user": { 392 "name": "testName" 393 } 394 } 395 }, 396 { 397 "id": 102, 398 "title": "feat(102)", 399 "toRef": { 400 "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", 401 "displayId": "branch", 402 "id": "refs/heads/branch" 403 }, 404 "fromRef": { 405 "id": "refs/heads/feature-102", 406 "displayId": "feature-102", 407 "latestCommit": "bb3cf2e4d1517c83e720d2585b9402dbef71f992" 408 }, 409 "author": { 410 "user": { 411 "name": "testName" 412 } 413 } 414 } 415 ], 416 "nextPageStart": 200 417 }`) 418 case "/rest/api/1.0/projects/PROJECT/repos/REPO/pull-requests?limit=100&start=200": 419 _, err = io.WriteString(w, `{ 420 "size": 1, 421 "limit": 2, 422 "isLastPage": true, 423 "values": [ 424 { 425 "id": 200, 426 "title": "feat(200)", 427 "toRef": { 428 "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", 429 "displayId": "master", 430 "id": "refs/heads/master" 431 }, 432 "fromRef": { 433 "id": "refs/heads/feature-200", 434 "displayId": "feature-200", 435 "latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992" 436 }, 437 "author": { 438 "user": { 439 "name": "testName" 440 } 441 } 442 } 443 ], 444 "start": 200 445 }`) 446 default: 447 t.Fail() 448 } 449 if err != nil { 450 t.Fail() 451 } 452 })) 453 defer ts.Close() 454 regexp := `feature-1[\d]{2}` 455 svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) 456 require.NoError(t, err) 457 pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ 458 { 459 BranchMatch: ®exp, 460 }, 461 }) 462 require.NoError(t, err) 463 assert.Len(t, pullRequests, 2) 464 assert.Equal(t, PullRequest{ 465 Number: 101, 466 Title: "feat(101)", 467 Branch: "feature-101", 468 TargetBranch: "master", 469 HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992", 470 Labels: []string{}, 471 Author: "testName", 472 }, *pullRequests[0]) 473 assert.Equal(t, PullRequest{ 474 Number: 102, 475 Title: "feat(102)", 476 Branch: "feature-102", 477 TargetBranch: "branch", 478 HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992", 479 Labels: []string{}, 480 Author: "testName", 481 }, *pullRequests[1]) 482 483 regexp = `.*2$` 484 svc, err = NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) 485 require.NoError(t, err) 486 pullRequests, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ 487 { 488 BranchMatch: ®exp, 489 }, 490 }) 491 require.NoError(t, err) 492 assert.Len(t, pullRequests, 1) 493 assert.Equal(t, PullRequest{ 494 Number: 102, 495 Title: "feat(102)", 496 Branch: "feature-102", 497 TargetBranch: "branch", 498 HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992", 499 Labels: []string{}, 500 Author: "testName", 501 }, *pullRequests[0]) 502 503 regexp = `[\d{2}` 504 svc, err = NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) 505 require.NoError(t, err) 506 _, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ 507 { 508 BranchMatch: ®exp, 509 }, 510 }) 511 require.Error(t, err) 512 } 513 514 func TestBitbucketServerListReturnsRepositoryNotFoundError(t *testing.T) { 515 mux := http.NewServeMux() 516 server := httptest.NewServer(mux) 517 defer server.Close() 518 519 path := "/rest/api/1.0/projects/nonexistent/repos/nonexistent/pull-requests?limit=100" 520 521 mux.HandleFunc(path, func(w http.ResponseWriter, _ *http.Request) { 522 // Return 404 status to simulate repository not found 523 w.WriteHeader(http.StatusNotFound) 524 _, _ = w.Write([]byte(`{"message": "404 Project Not Found"}`)) 525 }) 526 527 svc, err := NewBitbucketServiceNoAuth(t.Context(), server.URL, "nonexistent", "nonexistent", "", false, nil) 528 require.NoError(t, err) 529 530 prs, err := svc.List(t.Context()) 531 532 // Should return empty pull requests list 533 assert.Empty(t, prs) 534 535 // Should return RepositoryNotFoundError 536 require.Error(t, err) 537 assert.True(t, IsRepositoryNotFoundError(err), "Expected RepositoryNotFoundError but got: %v", err) 538 }