summaryrefslogtreecommitdiffstats
path: root/tests/integration/integration_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /tests/integration/integration_test.go
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r--tests/integration/integration_test.go687
1 files changed, 687 insertions, 0 deletions
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
new file mode 100644
index 0000000..e43200f
--- /dev/null
+++ b/tests/integration/integration_test.go
@@ -0,0 +1,687 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+//nolint:forbidigo
+package integration
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "hash"
+ "hash/fnv"
+ "io"
+ "net/http"
+ "net/http/cookiejar"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/cmd"
+ "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/graceful"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/testlogger"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers"
+ "code.gitea.io/gitea/services/auth/source/remote"
+ gitea_context "code.gitea.io/gitea/services/context"
+ user_service "code.gitea.io/gitea/services/user"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/markbates/goth"
+ "github.com/markbates/goth/gothic"
+ goth_github "github.com/markbates/goth/providers/github"
+ goth_gitlab "github.com/markbates/goth/providers/gitlab"
+ "github.com/santhosh-tekuri/jsonschema/v6"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var testWebRoutes *web.Route
+
+type NilResponseRecorder struct {
+ httptest.ResponseRecorder
+ Length int
+}
+
+func (n *NilResponseRecorder) Write(b []byte) (int, error) {
+ n.Length += len(b)
+ return len(b), nil
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewNilResponseRecorder() *NilResponseRecorder {
+ return &NilResponseRecorder{
+ ResponseRecorder: *httptest.NewRecorder(),
+ }
+}
+
+type NilResponseHashSumRecorder struct {
+ httptest.ResponseRecorder
+ Hash hash.Hash
+ Length int
+}
+
+func (n *NilResponseHashSumRecorder) Write(b []byte) (int, error) {
+ _, _ = n.Hash.Write(b)
+ n.Length += len(b)
+ return len(b), nil
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder {
+ return &NilResponseHashSumRecorder{
+ Hash: fnv.New32(),
+ ResponseRecorder: *httptest.NewRecorder(),
+ }
+}
+
+// runMainApp runs the subcommand and returns its standard output. Any returned error will usually be of type *ExitError. If c.Stderr was nil, Output populates ExitError.Stderr.
+func runMainApp(subcommand string, args ...string) (string, error) {
+ return runMainAppWithStdin(nil, subcommand, args...)
+}
+
+// runMainAppWithStdin runs the subcommand and returns its standard output. Any returned error will usually be of type *ExitError. If c.Stderr was nil, Output populates ExitError.Stderr.
+func runMainAppWithStdin(stdin io.Reader, subcommand string, args ...string) (string, error) {
+ // running the main app directly will very likely mess with the testing setup (logger & co.)
+ // hence we run it as a subprocess and capture its output
+ args = append([]string{subcommand}, args...)
+ cmd := exec.Command(os.Args[0], args...)
+ cmd.Env = append(os.Environ(),
+ "GITEA_TEST_CLI=true",
+ "GITEA_CONF="+setting.CustomConf,
+ "GITEA_WORK_DIR="+setting.AppWorkPath)
+ cmd.Stdin = stdin
+ out, err := cmd.Output()
+ return string(out), err
+}
+
+func TestMain(m *testing.M) {
+ // GITEA_TEST_CLI is set by runMainAppWithStdin
+ // inspired by https://abhinavg.net/2022/05/15/hijack-testmain/
+ if testCLI := os.Getenv("GITEA_TEST_CLI"); testCLI == "true" {
+ app := cmd.NewMainApp("test-version", "integration-test")
+ args := append([]string{
+ "executable-name", // unused, but expected at position 1
+ "--config", os.Getenv("GITEA_CONF"),
+ },
+ os.Args[1:]..., // skip the executable name
+ )
+ if err := cmd.RunMainApp(app, args...); err != nil {
+ panic(err) // should never happen since RunMainApp exits on error
+ }
+ return
+ }
+
+ defer log.GetManager().Close()
+
+ managerCtx, cancel := context.WithCancel(context.Background())
+ graceful.InitManager(managerCtx)
+ defer cancel()
+
+ tests.InitTest(true)
+ testWebRoutes = routers.NormalRoutes()
+
+ // integration test settings...
+ if setting.CfgProvider != nil {
+ testingCfg := setting.CfgProvider.Section("integration-tests")
+ testlogger.SlowTest = testingCfg.Key("SLOW_TEST").MustDuration(testlogger.SlowTest)
+ testlogger.SlowFlush = testingCfg.Key("SLOW_FLUSH").MustDuration(testlogger.SlowFlush)
+ }
+
+ if os.Getenv("GITEA_SLOW_TEST_TIME") != "" {
+ duration, err := time.ParseDuration(os.Getenv("GITEA_SLOW_TEST_TIME"))
+ if err == nil {
+ testlogger.SlowTest = duration
+ }
+ }
+
+ if os.Getenv("GITEA_SLOW_FLUSH_TIME") != "" {
+ duration, err := time.ParseDuration(os.Getenv("GITEA_SLOW_FLUSH_TIME"))
+ if err == nil {
+ testlogger.SlowFlush = duration
+ }
+ }
+
+ os.Unsetenv("GIT_AUTHOR_NAME")
+ os.Unsetenv("GIT_AUTHOR_EMAIL")
+ os.Unsetenv("GIT_AUTHOR_DATE")
+ os.Unsetenv("GIT_COMMITTER_NAME")
+ os.Unsetenv("GIT_COMMITTER_EMAIL")
+ os.Unsetenv("GIT_COMMITTER_DATE")
+
+ err := unittest.InitFixtures(
+ unittest.FixturesOptions{
+ Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),
+ },
+ )
+ if err != nil {
+ fmt.Printf("Error initializing test database: %v\n", err)
+ os.Exit(1)
+ }
+
+ // FIXME: the console logger is deleted by mistake, so if there is any `log.Fatal`, developers won't see any error message.
+ // Instead, "No tests were found", last nonsense log is "According to the configuration, subsequent logs will not be printed to the console"
+ exitCode := m.Run()
+
+ if err := testlogger.WriterCloser.Reset(); err != nil {
+ fmt.Printf("testlogger.WriterCloser.Reset: error ignored: %v\n", err)
+ }
+
+ if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil {
+ fmt.Printf("util.RemoveAll: %v\n", err)
+ os.Exit(1)
+ }
+ if err = util.RemoveAll(setting.Indexer.RepoPath); err != nil {
+ fmt.Printf("Unable to remove repo indexer: %v\n", err)
+ os.Exit(1)
+ }
+
+ os.Exit(exitCode)
+}
+
+type TestSession struct {
+ jar http.CookieJar
+}
+
+func (s *TestSession) GetCookie(name string) *http.Cookie {
+ baseURL, err := url.Parse(setting.AppURL)
+ if err != nil {
+ return nil
+ }
+
+ for _, c := range s.jar.Cookies(baseURL) {
+ if c.Name == name {
+ return c
+ }
+ }
+ return nil
+}
+
+func (s *TestSession) SetCookie(cookie *http.Cookie) *http.Cookie {
+ baseURL, err := url.Parse(setting.AppURL)
+ if err != nil {
+ return nil
+ }
+
+ s.jar.SetCookies(baseURL, []*http.Cookie{cookie})
+ return nil
+}
+
+func (s *TestSession) MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest.ResponseRecorder {
+ t.Helper()
+ req := rw.Request
+ baseURL, err := url.Parse(setting.AppURL)
+ require.NoError(t, err)
+ for _, c := range s.jar.Cookies(baseURL) {
+ req.AddCookie(c)
+ }
+ resp := MakeRequest(t, rw, expectedStatus)
+
+ ch := http.Header{}
+ ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
+ cr := http.Request{Header: ch}
+ s.jar.SetCookies(baseURL, cr.Cookies())
+
+ return resp
+}
+
+func (s *TestSession) MakeRequestNilResponseRecorder(t testing.TB, rw *RequestWrapper, expectedStatus int) *NilResponseRecorder {
+ t.Helper()
+ req := rw.Request
+ baseURL, err := url.Parse(setting.AppURL)
+ require.NoError(t, err)
+ for _, c := range s.jar.Cookies(baseURL) {
+ req.AddCookie(c)
+ }
+ resp := MakeRequestNilResponseRecorder(t, rw, expectedStatus)
+
+ ch := http.Header{}
+ ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
+ cr := http.Request{Header: ch}
+ s.jar.SetCookies(baseURL, cr.Cookies())
+
+ return resp
+}
+
+func (s *TestSession) MakeRequestNilResponseHashSumRecorder(t testing.TB, rw *RequestWrapper, expectedStatus int) *NilResponseHashSumRecorder {
+ t.Helper()
+ req := rw.Request
+ baseURL, err := url.Parse(setting.AppURL)
+ require.NoError(t, err)
+ for _, c := range s.jar.Cookies(baseURL) {
+ req.AddCookie(c)
+ }
+ resp := MakeRequestNilResponseHashSumRecorder(t, rw, expectedStatus)
+
+ ch := http.Header{}
+ ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
+ cr := http.Request{Header: ch}
+ s.jar.SetCookies(baseURL, cr.Cookies())
+
+ return resp
+}
+
+const userPassword = "password"
+
+func emptyTestSession(t testing.TB) *TestSession {
+ t.Helper()
+ jar, err := cookiejar.New(nil)
+ require.NoError(t, err)
+
+ return &TestSession{jar: jar}
+}
+
+func getUserToken(t testing.TB, userName string, scope ...auth.AccessTokenScope) string {
+ return getTokenForLoggedInUser(t, loginUser(t, userName), scope...)
+}
+
+func mockCompleteUserAuth(mock func(res http.ResponseWriter, req *http.Request) (goth.User, error)) func() {
+ old := gothic.CompleteUserAuth
+ gothic.CompleteUserAuth = mock
+ return func() {
+ gothic.CompleteUserAuth = old
+ }
+}
+
+func addAuthSource(t *testing.T, payload map[string]string) *auth.Source {
+ session := loginUser(t, "user1")
+ payload["_csrf"] = GetCSRF(t, session, "/admin/auths/new")
+ req := NewRequestWithValues(t, "POST", "/admin/auths/new", payload)
+ session.MakeRequest(t, req, http.StatusSeeOther)
+ source, err := auth.GetSourceByName(context.Background(), payload["name"])
+ require.NoError(t, err)
+ return source
+}
+
+func authSourcePayloadOAuth2(name string) map[string]string {
+ return map[string]string{
+ "type": fmt.Sprintf("%d", auth.OAuth2),
+ "name": name,
+ "is_active": "on",
+ }
+}
+
+func authSourcePayloadOpenIDConnect(name, appURL string) map[string]string {
+ payload := authSourcePayloadOAuth2(name)
+ payload["oauth2_provider"] = "openidConnect"
+ payload["open_id_connect_auto_discovery_url"] = appURL + ".well-known/openid-configuration"
+ return payload
+}
+
+func authSourcePayloadGitLab(name string) map[string]string {
+ payload := authSourcePayloadOAuth2(name)
+ payload["oauth2_provider"] = "gitlab"
+ return payload
+}
+
+func authSourcePayloadGitLabCustom(name string) map[string]string {
+ payload := authSourcePayloadGitLab(name)
+ payload["oauth2_use_custom_url"] = "on"
+ payload["oauth2_auth_url"] = goth_gitlab.AuthURL
+ payload["oauth2_token_url"] = goth_gitlab.TokenURL
+ payload["oauth2_profile_url"] = goth_gitlab.ProfileURL
+ return payload
+}
+
+func authSourcePayloadGitHub(name string) map[string]string {
+ payload := authSourcePayloadOAuth2(name)
+ payload["oauth2_provider"] = "github"
+ return payload
+}
+
+func authSourcePayloadGitHubCustom(name string) map[string]string {
+ payload := authSourcePayloadGitHub(name)
+ payload["oauth2_use_custom_url"] = "on"
+ payload["oauth2_auth_url"] = goth_github.AuthURL
+ payload["oauth2_token_url"] = goth_github.TokenURL
+ payload["oauth2_profile_url"] = goth_github.ProfileURL
+ return payload
+}
+
+func createRemoteAuthSource(t *testing.T, name, url, matchingSource string) *auth.Source {
+ require.NoError(t, auth.CreateSource(context.Background(), &auth.Source{
+ Type: auth.Remote,
+ Name: name,
+ IsActive: true,
+ Cfg: &remote.Source{
+ URL: url,
+ MatchingSource: matchingSource,
+ },
+ }))
+ source, err := auth.GetSourceByName(context.Background(), name)
+ require.NoError(t, err)
+ return source
+}
+
+func createUser(ctx context.Context, t testing.TB, user *user_model.User) func() {
+ user.MustChangePassword = false
+ user.LowerName = strings.ToLower(user.Name)
+
+ require.NoError(t, db.Insert(ctx, user))
+
+ if len(user.Email) > 0 {
+ require.NoError(t, user_service.ReplacePrimaryEmailAddress(ctx, user, user.Email))
+ }
+
+ return func() {
+ require.NoError(t, user_service.DeleteUser(ctx, user, true))
+ }
+}
+
+func loginUser(t testing.TB, userName string) *TestSession {
+ t.Helper()
+
+ return loginUserWithPassword(t, userName, userPassword)
+}
+
+func loginUserWithPassword(t testing.TB, userName, password string) *TestSession {
+ t.Helper()
+
+ return loginUserWithPasswordRemember(t, userName, password, false)
+}
+
+func loginUserWithPasswordRemember(t testing.TB, userName, password string, rememberMe bool) *TestSession {
+ t.Helper()
+ req := NewRequest(t, "GET", "/user/login")
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ doc := NewHTMLParser(t, resp.Body)
+ req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
+ "_csrf": doc.GetCSRF(),
+ "user_name": userName,
+ "password": password,
+ "remember": strconv.FormatBool(rememberMe),
+ })
+ resp = MakeRequest(t, req, http.StatusSeeOther)
+
+ ch := http.Header{}
+ ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
+ cr := http.Request{Header: ch}
+
+ session := emptyTestSession(t)
+
+ baseURL, err := url.Parse(setting.AppURL)
+ require.NoError(t, err)
+ session.jar.SetCookies(baseURL, cr.Cookies())
+
+ return session
+}
+
+// token has to be unique this counter take care of
+var tokenCounter int64
+
+// getTokenForLoggedInUser returns a token for a logged in user.
+// The scope is an optional list of snake_case strings like the frontend form fields,
+// but without the "scope_" prefix.
+func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.AccessTokenScope) string {
+ t.Helper()
+ var token string
+ req := NewRequest(t, "GET", "/user/settings/applications")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ var csrf string
+ for _, cookie := range resp.Result().Cookies() {
+ if cookie.Name != "_csrf" {
+ continue
+ }
+ csrf = cookie.Value
+ break
+ }
+ if csrf == "" {
+ doc := NewHTMLParser(t, resp.Body)
+ csrf = doc.GetCSRF()
+ }
+ assert.NotEmpty(t, csrf)
+ urlValues := url.Values{}
+ urlValues.Add("_csrf", csrf)
+ urlValues.Add("name", fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1)))
+ for _, scope := range scopes {
+ urlValues.Add("scope", string(scope))
+ }
+ req = NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues)
+ resp = session.MakeRequest(t, req, http.StatusSeeOther)
+
+ // Log the flash values on failure
+ if !assert.Equal(t, []string{"/user/settings/applications"}, resp.Result().Header["Location"]) {
+ for _, cookie := range resp.Result().Cookies() {
+ if cookie.Name != gitea_context.CookieNameFlash {
+ continue
+ }
+ flash, _ := url.ParseQuery(cookie.Value)
+ for key, value := range flash {
+ t.Logf("Flash %q: %q", key, value)
+ }
+ }
+ }
+
+ req = NewRequest(t, "GET", "/user/settings/applications")
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ htmlDoc := NewHTMLParser(t, resp.Body)
+ token = htmlDoc.doc.Find(".ui.info p").Text()
+ assert.NotEmpty(t, token)
+ return token
+}
+
+type RequestWrapper struct {
+ *http.Request
+}
+
+func (req *RequestWrapper) AddBasicAuth(username string) *RequestWrapper {
+ req.Request.SetBasicAuth(username, userPassword)
+ return req
+}
+
+func (req *RequestWrapper) AddTokenAuth(token string) *RequestWrapper {
+ if token == "" {
+ return req
+ }
+ if !strings.HasPrefix(token, "Bearer ") {
+ token = "Bearer " + token
+ }
+ req.Request.Header.Set("Authorization", token)
+ return req
+}
+
+func (req *RequestWrapper) SetHeader(name, value string) *RequestWrapper {
+ req.Request.Header.Set(name, value)
+ return req
+}
+
+func NewRequest(t testing.TB, method, urlStr string) *RequestWrapper {
+ t.Helper()
+ return NewRequestWithBody(t, method, urlStr, nil)
+}
+
+func NewRequestf(t testing.TB, method, urlFormat string, args ...any) *RequestWrapper {
+ t.Helper()
+ return NewRequest(t, method, fmt.Sprintf(urlFormat, args...))
+}
+
+func NewRequestWithValues(t testing.TB, method, urlStr string, values map[string]string) *RequestWrapper {
+ t.Helper()
+ urlValues := url.Values{}
+ for key, value := range values {
+ urlValues[key] = []string{value}
+ }
+ return NewRequestWithURLValues(t, method, urlStr, urlValues)
+}
+
+func NewRequestWithURLValues(t testing.TB, method, urlStr string, urlValues url.Values) *RequestWrapper {
+ t.Helper()
+ return NewRequestWithBody(t, method, urlStr, bytes.NewBufferString(urlValues.Encode())).
+ SetHeader("Content-Type", "application/x-www-form-urlencoded")
+}
+
+func NewRequestWithJSON(t testing.TB, method, urlStr string, v any) *RequestWrapper {
+ t.Helper()
+
+ jsonBytes, err := json.Marshal(v)
+ require.NoError(t, err)
+ return NewRequestWithBody(t, method, urlStr, bytes.NewBuffer(jsonBytes)).
+ SetHeader("Content-Type", "application/json")
+}
+
+func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *RequestWrapper {
+ t.Helper()
+ if !strings.HasPrefix(urlStr, "http") && !strings.HasPrefix(urlStr, "/") {
+ urlStr = "/" + urlStr
+ }
+ req, err := http.NewRequest(method, urlStr, body)
+ require.NoError(t, err)
+ req.RequestURI = urlStr
+
+ return &RequestWrapper{req}
+}
+
+const NoExpectedStatus = -1
+
+func MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest.ResponseRecorder {
+ t.Helper()
+ req := rw.Request
+ recorder := httptest.NewRecorder()
+ if req.RemoteAddr == "" {
+ req.RemoteAddr = "test-mock:12345"
+ }
+ testWebRoutes.ServeHTTP(recorder, req)
+ if expectedStatus != NoExpectedStatus {
+ if !assert.EqualValues(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String()) {
+ logUnexpectedResponse(t, recorder)
+ }
+ }
+ return recorder
+}
+
+func MakeRequestNilResponseRecorder(t testing.TB, rw *RequestWrapper, expectedStatus int) *NilResponseRecorder {
+ t.Helper()
+ req := rw.Request
+ recorder := NewNilResponseRecorder()
+ testWebRoutes.ServeHTTP(recorder, req)
+ if expectedStatus != NoExpectedStatus {
+ if !assert.EqualValues(t, expectedStatus, recorder.Code,
+ "Request: %s %s", req.Method, req.URL.String()) {
+ logUnexpectedResponse(t, &recorder.ResponseRecorder)
+ }
+ }
+ return recorder
+}
+
+func MakeRequestNilResponseHashSumRecorder(t testing.TB, rw *RequestWrapper, expectedStatus int) *NilResponseHashSumRecorder {
+ t.Helper()
+ req := rw.Request
+ recorder := NewNilResponseHashSumRecorder()
+ testWebRoutes.ServeHTTP(recorder, req)
+ if expectedStatus != NoExpectedStatus {
+ if !assert.EqualValues(t, expectedStatus, recorder.Code,
+ "Request: %s %s", req.Method, req.URL.String()) {
+ logUnexpectedResponse(t, &recorder.ResponseRecorder)
+ }
+ }
+ return recorder
+}
+
+// logUnexpectedResponse logs the contents of an unexpected response.
+func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
+ t.Helper()
+ respBytes := recorder.Body.Bytes()
+ if len(respBytes) == 0 {
+ // log the content of the flash cookie
+ for _, cookie := range recorder.Result().Cookies() {
+ if cookie.Name != gitea_context.CookieNameFlash {
+ continue
+ }
+ flash, _ := url.ParseQuery(cookie.Value)
+ for key, value := range flash {
+ // the key is itself url-encoded
+ if flash, err := url.ParseQuery(key); err == nil {
+ for key, value := range flash {
+ t.Logf("FlashCookie %q: %q", key, value)
+ }
+ } else {
+ t.Logf("FlashCookie %q: %q", key, value)
+ }
+ }
+ }
+
+ return
+ } else if len(respBytes) < 500 {
+ // if body is short, just log the whole thing
+ t.Log("Response: ", string(respBytes))
+ return
+ }
+ t.Log("Response length: ", len(respBytes))
+
+ // log the "flash" error message, if one exists
+ // we must create a new buffer, so that we don't "use up" resp.Body
+ htmlDoc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(respBytes))
+ if err != nil {
+ return // probably a non-HTML response
+ }
+ errMsg := htmlDoc.Find(".ui.negative.message").Text()
+ if len(errMsg) > 0 {
+ t.Log("A flash error message was found:", errMsg)
+ }
+}
+
+func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v any) {
+ t.Helper()
+
+ decoder := json.NewDecoder(resp.Body)
+ require.NoError(t, decoder.Decode(v))
+}
+
+func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) {
+ t.Helper()
+
+ schemaFilePath := filepath.Join(filepath.Dir(setting.AppPath), "tests", "integration", "schemas", schemaFile)
+ _, schemaFileErr := os.Stat(schemaFilePath)
+ require.NoError(t, schemaFileErr)
+
+ schema, err := jsonschema.NewCompiler().Compile(schemaFilePath)
+ require.NoError(t, err)
+
+ var data any
+ err = json.Unmarshal(resp.Body.Bytes(), &data)
+ require.NoError(t, err)
+
+ schemaValidation := schema.Validate(data)
+ require.NoError(t, schemaValidation)
+}
+
+func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
+ t.Helper()
+ req := NewRequest(t, "GET", urlStr)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+ return doc.GetCSRF()
+}
+
+func GetHTMLTitle(t testing.TB, session *TestSession, urlStr string) string {
+ t.Helper()
+
+ req := NewRequest(t, "GET", urlStr)
+ var resp *httptest.ResponseRecorder
+ if session == nil {
+ resp = MakeRequest(t, req, http.StatusOK)
+ } else {
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ }
+
+ doc := NewHTMLParser(t, resp.Body)
+ return doc.Find("head title").Text()
+}