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:
@@ -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"}
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -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"}]
|
||||
@@ -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"}]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -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}]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[{"id":1,"name":"Bug","color":"#ee0701","description":""},{"id":2,"name":"Question","color":"#d876e3","description":""}]
|
||||
@@ -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"}]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
[]
|
||||
File diff suppressed because one or more lines are too long
@@ -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":[]}]
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
{"topics":[]}
|
||||
@@ -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}
|
||||
@@ -0,0 +1,3 @@
|
||||
Content-Type: application/json
|
||||
|
||||
{"version":"1.22.0"}
|
||||
@@ -0,0 +1,2 @@
|
||||
Content-Type: text/plain
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Content-Type: text/plain
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Content-Type: text/plain
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Content-Type: text/plain
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Content-Type: text/plain
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Content-Type: text/plain
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user