mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-02 05:05:15 +01:00
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>
67 lines
2.2 KiB
Go
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"))
|
|
}
|
|
}
|