Remove external service dependencies in migration tests (#36866)

Fix #36859

Replace live third-party API calls in migration tests with a
fixture-based HTTP mock server. Fixtures are committed so tests run
offline by default; live recording is gated per service on an API-token
env var.

Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
silverwind
2026-04-23 17:18:53 +02:00
committed by GitHub
parent 12d83cbfa3
commit 7947851e57
176 changed files with 2446 additions and 160 deletions

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
{"id":1,"name":"test_repo","full_name":"gitea/test_repo","owner":{"id":1,"login":"gitea","username":"gitea"},"private":false,"html_url":"https://gitea.com/gitea/test_repo","clone_url":"https://gitea.com/gitea/test_repo.git","default_branch":"master","description":"test repo for migration"}

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
[{"id":1553,"body":"TESTSET for gitea2gitea migration\n","user":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z"},{"id":1554,"body":"Oh!\n","user":{"id":-1,"login":"Ghost","full_name":"","email":"","avatar_url":"","username":"Ghost"},"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z"}]

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
[{"user":{"id":-1,"login":"Ghost","full_name":"","email":"","avatar_url":"","username":"Ghost"},"content":"gitea"},{"user":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"content":"laugh"}]

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
[{"number":1,"title":"issue1","body":"","state":"open","user":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"labels":[],"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z","is_locked":false},{"number":2,"title":"issue2","body":"","state":"open","user":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"labels":[],"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z","is_locked":false},{"number":3,"title":"issue3","body":"","state":"open","user":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"labels":[],"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z","is_locked":false},{"number":4,"title":"what is this repo about?","body":"","state":"closed","user":{"id":-1,"login":"Ghost","full_name":"","email":"","avatar_url":"","username":"Ghost"},"labels":[{"id":2,"name":"Question","color":"#d876e3"}],"milestone":{"id":1,"title":"V1"},"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z","closed_at":"2020-01-01T00:00:00Z","is_locked":true},{"number":5,"title":"issue5","body":"","state":"open","user":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"labels":[],"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z","is_locked":false},{"number":6,"title":"issue6","body":"","state":"open","user":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"labels":[],"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z","is_locked":false},{"number":7,"title":"issue7","body":"","state":"open","user":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"labels":[],"created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z","is_locked":false}]

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
[{"id":1,"name":"Bug","color":"#ee0701","description":""},{"id":2,"name":"Question","color":"#d876e3","description":""}]

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
[{"id":1,"title":"V1","description":"first milestone","state":"closed","created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z","closed_at":"2020-01-01T00:00:00Z"},{"id":2,"title":"V2 Finalize","description":"second milestone","state":"open","created_at":"2020-01-01T00:00:00Z","updated_at":"2020-01-01T00:00:00Z"}]

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
[{"id":1,"tag_name":"V1","target_commitish":"master","name":"First Release","body":"as title","draft":false,"prerelease":false,"created_at":"2020-01-01T00:00:00Z","published_at":"2020-01-01T00:00:00Z","author":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"assets":[]},{"id":2,"tag_name":"v2-rc1","target_commitish":"master","name":"Second Release","body":"this repo has:\n- issues\n- pulls","draft":false,"prerelease":true,"created_at":"2020-01-01T00:00:00Z","published_at":"2020-01-01T00:00:00Z","author":{"id":689,"login":"6543","full_name":"","email":"6543@obermui.de","avatar_url":"","username":"6543"},"assets":[]}]

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
{"max_response_items":50,"default_paging_num":30,"default_git_trees_per_page":40,"default_max_blob_size":10485760}

View File

@@ -0,0 +1,3 @@
Content-Type: application/json
{"version":"1.22.0"}

View File

@@ -0,0 +1,2 @@
Content-Type: text/plain

View File

@@ -0,0 +1,2 @@
Content-Type: text/plain

View File

@@ -0,0 +1,2 @@
Content-Type: text/plain

View File

@@ -0,0 +1,2 @@
Content-Type: text/plain

View File

@@ -0,0 +1,2 @@
Content-Type: text/plain

View File

@@ -0,0 +1,2 @@
Content-Type: text/plain

View File

@@ -6,13 +6,16 @@ package integration
import (
"fmt"
"net/http"
"net/http/cgi"
"net/http/httptest"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"time"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
@@ -22,7 +25,6 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/migrations"
@@ -120,24 +122,133 @@ func Test_UpdateCommentsMigrationsByType(t *testing.T) {
assert.NoError(t, err)
}
// setupGiteaMockServer creates a mock HTTP server that replays API responses from fixture files.
// If a GITEA_TOKEN environment variable is set, the mock server proxies requests to the live
// gitea.com instance and saves the responses as fixture files for future test runs.
// Example: GITEA_TOKEN=your_token go test -run Test_MigrateFromGiteaToGitea
func setupGiteaMockServer(t *testing.T) *httptest.Server {
t.Helper()
giteaToken := os.Getenv("GITEA_TOKEN")
liveMode := giteaToken != ""
// fast-import data creates deterministic commits (fixed author/committer/timestamps),
// so the resulting SHAs are always the same across runs.
fastImportData := `commit refs/heads/master
mark :1
author Test <test@test.com> 1000000000 +0000
committer Test <test@test.com> 1000000000 +0000
data 8
initial
commit refs/heads/master
mark :2
author Test <test@test.com> 1000000001 +0000
committer Test <test@test.com> 1000000001 +0000
data 7
second
from :1
commit refs/heads/6543-patch-1
mark :3
author Test <test@test.com> 1000000002 +0000
committer Test <test@test.com> 1000000002 +0000
data 6
patch
from :2
reset refs/tags/V1
from :1
reset refs/tags/v2-rc1
from :2
done
`
// Fork adds one extra branch for the PR head (from master = 873987e)
forkExtraData := `commit refs/heads/add-xkcd-2199
author Test <test@test.com> 1000000003 +0000
committer Test <test@test.com> 1000000003 +0000
data 5
xkcd
from 873987ea3e99c206bb0841266845098ee74d4ce9
done
`
fastImport := func(dir, data string) {
cmd := exec.Command("git", "-C", dir, "fast-import", "--date-format=raw", "--done")
cmd.Stdin = strings.NewReader(data)
out, err := cmd.CombinedOutput()
require.NoError(t, err, "fast-import failed: %s", out)
}
repoDir := t.TempDir()
out, err := exec.Command("git", "init", "--bare", repoDir).CombinedOutput()
require.NoError(t, err, "git init failed: %s", out)
fastImport(repoDir, fastImportData)
forkDir := t.TempDir()
out, err = exec.Command("git", "clone", "--bare", repoDir, forkDir).CombinedOutput()
require.NoError(t, err, "git clone failed: %s", out)
fastImport(forkDir, forkExtraData)
// Find git-http-backend
execPathBytes, err := exec.Command("git", "--exec-path").Output()
require.NoError(t, err)
httpBackend := filepath.Join(strings.TrimSpace(string(execPathBytes)), "git-http-backend")
_, callerFile, _, _ := runtime.Caller(0)
fixtureDir := filepath.Join(filepath.Dir(callerFile), "_mock_data/Test_MigrateFromGiteaToGitea")
return unittest.NewMockWebServer(t, "https://gitea.com",
fixtureDir, liveMode,
unittest.MockServerOptions{
Routes: func(mux *http.ServeMux) {
mux.HandleFunc("/gitea/test_repo.wiki.git/", func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "wiki not found", http.StatusNotFound)
})
gitHandler := func(dir, prefix string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
handler := &cgi.Handler{
Path: httpBackend,
Dir: dir,
Env: []string{
"GIT_PROJECT_ROOT=" + filepath.Dir(dir),
"GIT_HTTP_EXPORT_ALL=1",
},
}
r.URL.Path = "/" + filepath.Base(dir) + strings.TrimPrefix(r.URL.Path, prefix)
handler.ServeHTTP(w, r)
}
}
mux.HandleFunc("/gitea/test_repo.git/", gitHandler(repoDir, "/gitea/test_repo.git"))
mux.HandleFunc("/6543-forks/test_repo.git/", gitHandler(forkDir, "/6543-forks/test_repo.git"))
},
},
)
}
func Test_MigrateFromGiteaToGitea(t *testing.T) {
defer tests.PrepareTestEnv(t)()
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
setting.Migrations.AllowLocalNetworks = true
defer func() {
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
migrations.Init()
}()
require.NoError(t, migrations.Init())
mockServer := setupGiteaMockServer(t)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
resp, err := httplib.NewRequest("https://gitea.com/gitea/test_repo.git", "GET").SetReadWriteTimeout(5 * time.Second).Response()
if err != nil || resp.StatusCode != http.StatusOK {
if resp != nil {
resp.Body.Close()
}
t.Skipf("Can't reach https://gitea.com, skipping %s", t.Name())
}
resp.Body.Close()
repoName := fmt.Sprintf("gitea-to-gitea-%d", time.Now().UnixNano())
cloneAddr := "https://gitea.com/gitea/test_repo.git"
repoName := "migrated-from-mock-gitea"
cloneAddr := mockServer.URL + "/gitea/test_repo.git"
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", &structs.MigrateRepoOptions{
CloneAddr: cloneAddr,