Files
gitea/services/migrations/http_client_test.go
T
Lunny Xiaoandsilverwind bd5fbcfb29 feat(migrations): add repository migration from bitbucket.org (#38607)
Adds migration from bitbucket.org as the Cloud variant of the Bitbucket
entry, in the web UI and via `POST /repos/migrate` with
`service=bitbucket`. It migrates the repository and pull requests with
their comments and reviews. Bitbucket Cloud has no releases, and
Atlassian removed its issues and wikis on 2026-08-20
(https://community.atlassian.com/forums/Bitbucket-articles/Announcing-sunset-of-Bitbucket-Issues-and-Wikis/ba-p/3193882).

- Private repositories need an API or access token, which also raises
the rate limit.
- Rate-limited requests back off and retry, Bitbucket sends no
`Retry-After`.
- Tests replay recorded responses, set `BITBUCKET_READ_TOKEN` to
re-record.

Co-authored-by: silverwind <me@silverwind.io>
2026-10-31 14:51:43 +00:00

67 lines
2.2 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package migrations
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMigrationHTTPTransportRetryAfter(t *testing.T) {
var requests atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch requests.Add(1) {
case 1:
w.Header().Set("Retry-After", "0")
w.WriteHeader(http.StatusTooManyRequests)
case 2:
w.Header().Set("Retry-After", time.Now().UTC().Format(http.TimeFormat))
w.WriteHeader(http.StatusForbidden)
case 3:
w.WriteHeader(http.StatusTooManyRequests)
default:
_, _ = io.Copy(w, r.Body)
}
}))
defer server.Close()
client := &http.Client{Transport: retryAfterTransport{RoundTripper: getMigrationTransport(), backoff: time.Nanosecond}}
resp, err := client.Post(server.URL, "text/plain", strings.NewReader("payload"))
require.NoError(t, err)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "payload", string(body))
assert.EqualValues(t, 4, requests.Load())
}
func TestAuthTransportSendsCredentialsToSameHostOnly(t *testing.T) {
for _, testCase := range []struct{ baseURL, target, expected string }{
{"http://source.example/sub", "https://source.example/api", "token secret"},
{"https://source.example", "https://source.example/api", "token secret"},
{"https://source.example", "http://source.example/api", ""},
{"https://source.example", "https://other.example/api", ""},
{"https://source.example:3000", "https://source.example:8443/api", ""},
} {
req, err := http.NewRequest(http.MethodGet, testCase.target, nil)
require.NoError(t, err)
resp, err := authTransport(roundTripperFunc(func(req *http.Request) (*http.Response, error) {
assert.Equal(t, testCase.expected, req.Header.Get("Authorization"), testCase.target)
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}, nil
}), testCase.baseURL, "token secret").RoundTrip(req)
require.NoError(t, err)
resp.Body.Close()
assert.Empty(t, req.Header.Get("Authorization"))
}
}