forgejo/models/unittest/mock_http.go
Antonin Delpeuch db0dbab552
[GITEA] Enable mocked HTTP responses for GitLab migration test
Fix gitlab migration unit test

Closes #1837.

The differences in dates can be explained by commit e19b9653ea, which
changed the order in which "created_date" and "updated_date" are
considered.

(cherry picked from commit b0bba20aa4)

Mock HTTP requests in GitLab migration test

This introduces a new utility which can be added to other tests
making HTTP calls to a live service, to cache the responses of this
service in the repository.

(cherry picked from commit 52053b1389)

Enable mocked HTTP responses for GitLab migration test

(cherry picked from commit 19cefc4de2)

Simplify HTTP mocking utility in unit tests

Follow-up to https://codeberg.org/forgejo/forgejo/pulls/1841

(cherry picked from commit ca517c8bb4)
(cherry picked from commit b227e0dd6b)
(cherry picked from commit 6cc9d06556)
(cherry picked from commit f0746e648d)
(cherry picked from commit 414193341b)
(cherry picked from commit 6e93df3bbb)
2024-01-08 17:05:46 +01:00

113 lines
4.1 KiB
Go

// Copyright 2017 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package unittest
import (
"bufio"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"slices"
"strings"
"testing"
"code.gitea.io/gitea/modules/log"
"github.com/stretchr/testify/assert"
)
// Mocks HTTP responses of a third-party service (such as GitHub, GitLab…)
// This has two modes:
// - live mode: the requests made to the mock HTTP server are transmitted to the live
// service, and responses are saved as test data files
// - test mode: the responses to requests to the mock HTTP server are read from the
// test data files
func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveMode bool) *httptest.Server {
mockServerBaseURL := ""
ignoredHeaders := []string{"cf-ray", "server", "date", "report-to", "nel", "x-request-id"}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := NormalizedFullPath(r.URL)
log.Info("Mock HTTP Server: got request for path %s", r.URL.Path)
// TODO check request method (support POST?)
fixturePath := fmt.Sprintf("%s/%s", testDataDir, strings.ReplaceAll(path, "/", "_"))
if liveMode {
liveURL := fmt.Sprintf("%s%s", liveServerBaseURL, path)
request, err := http.NewRequest(r.Method, liveURL, nil)
assert.NoError(t, err, "constructing an HTTP request to %s failed", liveURL)
for headerName, headerValues := range r.Header {
// do not pass on the encoding: let the Transport of the HTTP client handle that for us
if strings.ToLower(headerName) != "accept-encoding" {
for _, headerValue := range headerValues {
request.Header.Add(headerName, headerValue)
}
}
}
response, err := http.DefaultClient.Do(request)
assert.NoError(t, err, "HTTP request to %s failed: %s", liveURL)
fixture, err := os.Create(fixturePath)
assert.NoError(t, err, "failed to open the fixture file %s for writing", fixturePath)
defer fixture.Close()
fixtureWriter := bufio.NewWriter(fixture)
for headerName, headerValues := range response.Header {
for _, headerValue := range headerValues {
if !slices.Contains(ignoredHeaders, strings.ToLower(headerName)) {
_, err := fixtureWriter.WriteString(fmt.Sprintf("%s: %s\n", headerName, headerValue))
assert.NoError(t, err, "writing the header of the HTTP response to the fixture file failed")
}
}
}
_, err = fixtureWriter.WriteString("\n")
assert.NoError(t, err, "writing the header of the HTTP response to the fixture file failed")
fixtureWriter.Flush()
log.Info("Mock HTTP Server: writing response to %s", fixturePath)
_, err = io.Copy(fixture, response.Body)
assert.NoError(t, err, "writing the body of the HTTP response to %s failed", liveURL)
err = fixture.Sync()
assert.NoError(t, err, "writing the body of the HTTP response to the fixture file failed")
}
fixture, err := os.ReadFile(fixturePath)
assert.NoError(t, err, "missing mock HTTP response: "+fixturePath)
w.WriteHeader(http.StatusOK)
// replace any mention of the live HTTP service by the mocked host
stringFixture := strings.ReplaceAll(string(fixture), liveServerBaseURL, mockServerBaseURL)
// parse back the fixture file into a series of HTTP headers followed by response body
lines := strings.Split(stringFixture, "\n")
for idx, line := range lines {
colonIndex := strings.Index(line, ": ")
if colonIndex != -1 {
w.Header().Set(line[0:colonIndex], line[colonIndex+2:])
} else {
// we reached the end of the headers (empty line), so what follows is the body
responseBody := strings.Join(lines[idx+1:], "\n")
_, err := w.Write([]byte(responseBody))
assert.NoError(t, err, "writing the body of the HTTP response failed")
break
}
}
}))
mockServerBaseURL = server.URL
return server
}
func NormalizedFullPath(url *url.URL) string {
// TODO normalize path (remove trailing slash?)
// TODO normalize RawQuery (order query parameters?)
if len(url.Query()) == 0 {
return url.EscapedPath()
}
return fmt.Sprintf("%s?%s", url.EscapedPath(), url.RawQuery)
}