Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions models/unittest/mock_http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package unittest

import (
"fmt"
"io"
"maps"
"net/http"
"net/http/httptest"
"net/url"
"os"
"slices"
"strings"
"testing"

"code.gitea.io/gitea/modules/log"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// MockServerOptions tweaks NewMockWebServer behavior.
type MockServerOptions struct {
// Routes installs extra handlers on the mux before the fixture fallback;
// more specific patterns win.
Routes func(mux *http.ServeMux)
// StripPrefix is trimmed from the request path before forwarding upstream,
// useful when the client prepends a prefix the real upstream does not use
// (e.g. go-github prepends "/api/v3").
StripPrefix string
}

// NewMockWebServer returns a test HTTP server that records upstream responses on demand
// and replays them from disk on subsequent runs.
//
// - liveMode=true: requests are forwarded to liveServerBaseURL and responses written as
// fixture files under testDataDir.
// - liveMode=false: responses come from existing fixture files.
//
// Fixture format: header lines ("Name: value"), a blank line, then the body. Before
// replay, occurrences of liveServerBaseURL in the body are swapped for the mock URL.
//
// The typical switch is an env var holding an API token; fixtures ship committed so the
// default run (no token) works offline.
//
// token := os.Getenv("GITEA_TOKEN")
// mock := NewMockWebServer(t, "https://gitea.com", fixtureDir, token != "")
func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveMode bool, opts ...MockServerOptions) *httptest.Server {
t.Helper()

var opt MockServerOptions
if len(opts) > 0 {
opt = opts[0]
}

ignoredHeaders := []string{"cf-ray", "server", "date", "report-to", "nel", "x-request-id", "set-cookie"}

var mockURL string

fallback := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqPath := r.URL.EscapedPath()
if r.URL.RawQuery != "" {
reqPath += "?" + r.URL.RawQuery
}
log.Info("mock server: %s %s", r.Method, reqPath)

fixturePath := fmt.Sprintf("%s/%s_%s", testDataDir, r.Method, url.QueryEscape(reqPath))
if strings.Contains(r.URL.Path, ".git/") {
fixturePath = fmt.Sprintf("%s/%s_%s", testDataDir, r.Method, url.QueryEscape(r.URL.Path))
}

if liveMode {
require.NoError(t, os.MkdirAll(testDataDir, 0o755))

liveURL := liveServerBaseURL + strings.TrimPrefix(reqPath, opt.StripPrefix)
req, err := http.NewRequest(r.Method, liveURL, r.Body)
require.NoError(t, err, "building upstream request to %s", liveURL)
for name, values := range r.Header {
if strings.EqualFold(name, "accept-encoding") {
continue
}
for _, value := range values {
req.Header.Add(name, value)
}
}

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err, "upstream request to %s failed", liveURL)
defer resp.Body.Close()
assert.Less(t, resp.StatusCode, 400, "upstream %s returned status %d", liveURL, resp.StatusCode)

out, err := os.Create(fixturePath)
require.NoError(t, err, "creating fixture %s", fixturePath)
defer out.Close()

for _, name := range slices.Sorted(maps.Keys(resp.Header)) {
if slices.Contains(ignoredHeaders, strings.ToLower(name)) {
continue
}
for _, value := range resp.Header[name] {
_, err := fmt.Fprintf(out, "%s: %s\n", name, value)
require.NoError(t, err)
}
}
_, err = out.WriteString("\n")
require.NoError(t, err)

_, err = io.Copy(out, resp.Body)
require.NoError(t, err, "writing fixture body for %s", liveURL)
require.NoError(t, out.Sync())
}

raw, err := os.ReadFile(fixturePath)
require.NoError(t, err, "missing fixture: %s", fixturePath)

replayed := strings.ReplaceAll(string(raw), liveServerBaseURL, mockURL)
headers, body, _ := strings.Cut(replayed, "\n\n")
for line := range strings.SplitSeq(headers, "\n") {
name, value, ok := strings.Cut(line, ": ")
if !ok || strings.EqualFold(name, "Content-Length") {
continue
}
w.Header().Set(name, value)
}
w.WriteHeader(http.StatusOK)
_, err = w.Write([]byte(body))
require.NoError(t, err)
})

mux := http.NewServeMux()
if opt.Routes != nil {
opt.Routes(mux)
}
mux.Handle("/", fallback)

server := httptest.NewServer(mux)
mockURL = server.URL
t.Cleanup(server.Close)
return server
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<ticketing-milestone type="array">
<ticketing-milestone>
<id type="integer">1</id>
<identifier>milestone1</identifier>
<name>Milestone1</name>
<deadline type="date">2021-09-16</deadline>
<description></description>
<status>active</status>
</ticketing-milestone>
<ticketing-milestone>
<id type="integer">2</id>
<identifier>milestone2</identifier>
<name>Milestone2</name>
<deadline type="date">2021-09-17</deadline>
<description></description>
<status>closed</status>
</ticketing-milestone>
</ticketing-milestone>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<repository>
<name>test</name>
<description>Repository Description</description>
<permalink>test</permalink>
<clone-url>git@codebasehq.com:gitea-test/gitea-test/test.git</clone-url>
<source></source>
</repository>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<commits type="array">
<commit>
<ref>f32b0a9dfd09a60f616f29158f772cedd89942d2</ref>
</commit>
</commits>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<commits type="array">
<commit>
<ref>1287f206b888d4d13540e0a8e1c07458f5420059</ref>
</commit>
</commits>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<merge-request>
<id type="integer">100</id>
<source-ref>readme-mr</source-ref>
<target-ref>master</target-ref>
<subject>Readme Change</subject>
<status>new</status>
<user-id type="integer">43</user-id>
<created-at type="datetime">2021-09-26T20:25:47+00:00</created-at>
<updated-at type="datetime">2021-09-26T20:25:47+00:00</updated-at>
<comments type="array">
<comment>
<content>Merge Request comment</content>
<id type="integer">300</id>
<user-id type="integer">43</user-id>
<action nil="true"></action>
<created-at type="datetime">2021-09-26T20:25:47+00:00</created-at>
</comment>
</comments>
</merge-request>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<merge-requests type="array">
<merge-request>
<id type="integer">100</id>
</merge-request>
</merge-requests>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<tickets type="array">
<ticket>
<ticket-id type="integer">2</ticket-id>
<summary>Open Ticket</summary>
<ticket-type>Feature</ticket-type>
<reporter-id type="integer">43</reporter-id>
<reporter>gitea-test-43</reporter>
<type>
<name>Feature</name>
</type>
<status>
<treat-as-closed type="boolean">false</treat-as-closed>
</status>
<milestone>
<name></name>
</milestone>
<updated-at type="datetime">2021-09-26T19:19:34+00:00</updated-at>
<created-at type="datetime">2021-09-26T19:19:14+00:00</created-at>
</ticket>
<ticket>
<ticket-id type="integer">1</ticket-id>
<summary>Closed Ticket</summary>
<ticket-type>Bug</ticket-type>
<reporter-id type="integer">43</reporter-id>
<reporter>gitea-test-43</reporter>
<type>
<name>Bug</name>
</type>
<status>
<treat-as-closed type="boolean">true</treat-as-closed>
</status>
<milestone>
<name>Milestone1</name>
</milestone>
<updated-at type="datetime">2021-09-26T19:18:55+00:00</updated-at>
<created-at type="datetime">2021-09-26T19:18:33+00:00</created-at>
</ticket>
</tickets>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<ticket-notes type="array">
<ticket-note>
<content>Closed Ticket Message</content>
<created-at type="datetime">2021-09-26T19:18:33+00:00</created-at>
<updated-at type="datetime">2021-09-26T19:18:33+00:00</updated-at>
<id type="integer">200</id>
<user-id type="integer">43</user-id>
</ticket-note>
</ticket-notes>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<ticket-notes type="array">
<ticket-note>
<content>Open Ticket Message</content>
<created-at type="datetime">2021-09-26T19:19:14+00:00</created-at>
<updated-at type="datetime">2021-09-26T19:19:14+00:00</updated-at>
<id type="integer">100</id>
<user-id type="integer">43</user-id>
</ticket-note>
<ticket-note>
<content>open comment</content>
<created-at type="datetime">2021-09-26T19:19:34+00:00</created-at>
<updated-at type="datetime">2021-09-26T19:19:34+00:00</updated-at>
<id type="integer">101</id>
<user-id type="integer">43</user-id>
</ticket-note>
</ticket-notes>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<ticketing-types type="array">
<ticketing-type>
<id type="integer">1</id>
<name>Bug</name>
</ticketing-type>
<ticketing-type>
<id type="integer">2</id>
<name>Feature</name>
</ticketing-type>
<ticketing-type>
<id type="integer">3</id>
<name>Enhancement</name>
</ticketing-type>
<ticketing-type>
<id type="integer">4</id>
<name>Task</name>
</ticketing-type>
</ticketing-types>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<users type="array">
<user>
<email-address>gitea-codebase@smack.email</email-address>
<id type="integer">43</id>
<last-name>Test</last-name>
<first-name>Gitea</first-name>
<username>gitea-test-43</username>
</user>
</users>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
Cache-Control: no-cache
Content-Security-Policy: default-src 'none'
Content-Type: application/json; charset=utf-8
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
Vary: Accept-Encoding, Accept, X-Requested-With
X-Accepted-Github-Permissions: allows_permissionless_access=true
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-Github-Api-Version-Selected: 2022-11-28
X-Github-Media-Type: github.v3; format=json
X-Github-Request-Id: C4F6:A93E1:6A2E9E:5B1BA1:69AF24E6
X-Ratelimit-Limit: 5000
X-Ratelimit-Remaining: 4937
X-Ratelimit-Reset: 1773089427
X-Ratelimit-Resource: core
X-Ratelimit-Used: 63
X-Xss-Protection: 0

{"resources":{"core":{"limit":5000,"used":63,"remaining":4937,"reset":1773089427},"search":{"limit":30,"used":0,"remaining":30,"reset":1773085986},"graphql":{"limit":5000,"used":40,"remaining":4960,"reset":1773087278},"integration_manifest":{"limit":5000,"used":0,"remaining":5000,"reset":1773089526},"source_import":{"limit":100,"used":0,"remaining":100,"reset":1773085986},"code_scanning_upload":{"limit":5000,"used":63,"remaining":4937,"reset":1773089427},"code_scanning_autofix":{"limit":10,"used":0,"remaining":10,"reset":1773085986},"actions_runner_registration":{"limit":10000,"used":0,"remaining":10000,"reset":1773089526},"scim":{"limit":15000,"used":0,"remaining":15000,"reset":1773089526},"dependency_snapshots":{"limit":100,"used":0,"remaining":100,"reset":1773085986},"dependency_sbom":{"limit":100,"used":0,"remaining":100,"reset":1773085986},"audit_log":{"limit":1750,"used":0,"remaining":1750,"reset":1773089526},"audit_log_streaming":{"limit":15,"used":0,"remaining":15,"reset":1773089526},"code_search":{"limit":10,"used":0,"remaining":10,"reset":1773085986}},"rate":{"limit":5000,"used":63,"remaining":4937,"reset":1773089427}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
Cache-Control: private, max-age=60, s-maxage=60
Content-Security-Policy: default-src 'none'
Content-Type: application/json; charset=utf-8
Etag: W/"4bd381e9d79f99cd2153b922e196d49eee23b7a4c10bb22a3ab3ffc6cf78ce39"
Last-Modified: Thu, 02 Mar 2023 14:02:26 GMT
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
Vary: Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With
X-Accepted-Github-Permissions: metadata=read
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-Github-Api-Version-Selected: 2022-11-28
X-Github-Media-Type: github.v3; param=scarlet-witch-preview; format=json, github.mercy-preview; param=baptiste-preview.nebula-preview; format=json
X-Github-Request-Id: C4F6:A93E1:6A3367:5B1FCA:69AF24E6
X-Ratelimit-Limit: 5000
X-Ratelimit-Remaining: 4935
X-Ratelimit-Reset: 1773089427
X-Ratelimit-Resource: core
X-Ratelimit-Used: 65
X-Xss-Protection: 0

{"id":220672974,"node_id":"MDEwOlJlcG9zaXRvcnkyMjA2NzI5NzQ=","name":"test_repo","full_name":"go-gitea/test_repo","private":false,"owner":{"login":"go-gitea","id":12724356,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyNzI0MzU2","avatar_url":"https://avatars.githubusercontent.com/u/12724356?v=4","gravatar_id":"","url":"https://api.github.com/users/go-gitea","html_url":"https://github.com/go-gitea","followers_url":"https://api.github.com/users/go-gitea/followers","following_url":"https://api.github.com/users/go-gitea/following{/other_user}","gists_url":"https://api.github.com/users/go-gitea/gists{/gist_id}","starred_url":"https://api.github.com/users/go-gitea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/go-gitea/subscriptions","organizations_url":"https://api.github.com/users/go-gitea/orgs","repos_url":"https://api.github.com/users/go-gitea/repos","events_url":"https://api.github.com/users/go-gitea/events{/privacy}","received_events_url":"https://api.github.com/users/go-gitea/received_events","type":"Organization","user_view_type":"public","site_admin":false},"html_url":"https://github.com/go-gitea/test_repo","description":"Test repository for testing migration from github to gitea","fork":false,"url":"https://api.github.com/repos/go-gitea/test_repo","forks_url":"https://api.github.com/repos/go-gitea/test_repo/forks","keys_url":"https://api.github.com/repos/go-gitea/test_repo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/go-gitea/test_repo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/go-gitea/test_repo/teams","hooks_url":"https://api.github.com/repos/go-gitea/test_repo/hooks","issue_events_url":"https://api.github.com/repos/go-gitea/test_repo/issues/events{/number}","events_url":"https://api.github.com/repos/go-gitea/test_repo/events","assignees_url":"https://api.github.com/repos/go-gitea/test_repo/assignees{/user}","branches_url":"https://api.github.com/repos/go-gitea/test_repo/branches{/branch}","tags_url":"https://api.github.com/repos/go-gitea/test_repo/tags","blobs_url":"https://api.github.com/repos/go-gitea/test_repo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/go-gitea/test_repo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/go-gitea/test_repo/git/refs{/sha}","trees_url":"https://api.github.com/repos/go-gitea/test_repo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/go-gitea/test_repo/statuses/{sha}","languages_url":"https://api.github.com/repos/go-gitea/test_repo/languages","stargazers_url":"https://api.github.com/repos/go-gitea/test_repo/stargazers","contributors_url":"https://api.github.com/repos/go-gitea/test_repo/contributors","subscribers_url":"https://api.github.com/repos/go-gitea/test_repo/subscribers","subscription_url":"https://api.github.com/repos/go-gitea/test_repo/subscription","commits_url":"https://api.github.com/repos/go-gitea/test_repo/commits{/sha}","git_commits_url":"https://api.github.com/repos/go-gitea/test_repo/git/commits{/sha}","comments_url":"https://api.github.com/repos/go-gitea/test_repo/comments{/number}","issue_comment_url":"https://api.github.com/repos/go-gitea/test_repo/issues/comments{/number}","contents_url":"https://api.github.com/repos/go-gitea/test_repo/contents/{+path}","compare_url":"https://api.github.com/repos/go-gitea/test_repo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/go-gitea/test_repo/merges","archive_url":"https://api.github.com/repos/go-gitea/test_repo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/go-gitea/test_repo/downloads","issues_url":"https://api.github.com/repos/go-gitea/test_repo/issues{/number}","pulls_url":"https://api.github.com/repos/go-gitea/test_repo/pulls{/number}","milestones_url":"https://api.github.com/repos/go-gitea/test_repo/milestones{/number}","notifications_url":"https://api.github.com/repos/go-gitea/test_repo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/go-gitea/test_repo/labels{/name}","releases_url":"https://api.github.com/repos/go-gitea/test_repo/releases{/id}","deployments_url":"https://api.github.com/repos/go-gitea/test_repo/deployments","created_at":"2019-11-09T16:49:20Z","updated_at":"2023-03-02T14:02:26Z","pushed_at":"2019-11-12T21:54:19Z","git_url":"git://github.com/go-gitea/test_repo.git","ssh_url":"git@github.com:go-gitea/test_repo.git","clone_url":"https://github.com/go-gitea/test_repo.git","svn_url":"https://github.com/go-gitea/test_repo","homepage":null,"size":1,"stargazers_count":3,"watchers_count":3,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"has_discussions":false,"forks_count":3,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":3,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"has_pull_requests":true,"pull_request_creation_policy":"all","topics":["gitea"],"visibility":"public","forks":3,"open_issues":3,"watchers":3,"default_branch":"master","permissions":{"admin":false,"maintain":true,"push":true,"triage":true,"pull":true},"custom_properties":{"vanta_production_branch_name":"main"},"organization":{"login":"go-gitea","id":12724356,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyNzI0MzU2","avatar_url":"https://avatars.githubusercontent.com/u/12724356?v=4","gravatar_id":"","url":"https://api.github.com/users/go-gitea","html_url":"https://github.com/go-gitea","followers_url":"https://api.github.com/users/go-gitea/followers","following_url":"https://api.github.com/users/go-gitea/following{/other_user}","gists_url":"https://api.github.com/users/go-gitea/gists{/gist_id}","starred_url":"https://api.github.com/users/go-gitea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/go-gitea/subscriptions","organizations_url":"https://api.github.com/users/go-gitea/orgs","repos_url":"https://api.github.com/users/go-gitea/repos","events_url":"https://api.github.com/users/go-gitea/events{/privacy}","received_events_url":"https://api.github.com/users/go-gitea/received_events","type":"Organization","user_view_type":"public","site_admin":false},"network_count":3,"subscribers_count":4}
Loading