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: &regexp,
   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: &regexp,
   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: &regexp,
   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  }