You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

integration_test.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "bytes"
  6. "context"
  7. "fmt"
  8. "hash"
  9. "hash/fnv"
  10. "io"
  11. "net/http"
  12. "net/http/cookiejar"
  13. "net/http/httptest"
  14. "net/url"
  15. "os"
  16. "path/filepath"
  17. "strings"
  18. "sync/atomic"
  19. "testing"
  20. "time"
  21. "code.gitea.io/gitea/models/unittest"
  22. "code.gitea.io/gitea/modules/graceful"
  23. "code.gitea.io/gitea/modules/json"
  24. "code.gitea.io/gitea/modules/log"
  25. "code.gitea.io/gitea/modules/setting"
  26. "code.gitea.io/gitea/modules/util"
  27. "code.gitea.io/gitea/modules/web"
  28. "code.gitea.io/gitea/routers"
  29. "code.gitea.io/gitea/tests"
  30. "github.com/PuerkitoBio/goquery"
  31. "github.com/stretchr/testify/assert"
  32. )
  33. var c *web.Route
  34. type NilResponseRecorder struct {
  35. httptest.ResponseRecorder
  36. Length int
  37. }
  38. func (n *NilResponseRecorder) Write(b []byte) (int, error) {
  39. n.Length += len(b)
  40. return len(b), nil
  41. }
  42. // NewRecorder returns an initialized ResponseRecorder.
  43. func NewNilResponseRecorder() *NilResponseRecorder {
  44. return &NilResponseRecorder{
  45. ResponseRecorder: *httptest.NewRecorder(),
  46. }
  47. }
  48. type NilResponseHashSumRecorder struct {
  49. httptest.ResponseRecorder
  50. Hash hash.Hash
  51. Length int
  52. }
  53. func (n *NilResponseHashSumRecorder) Write(b []byte) (int, error) {
  54. _, _ = n.Hash.Write(b)
  55. n.Length += len(b)
  56. return len(b), nil
  57. }
  58. // NewRecorder returns an initialized ResponseRecorder.
  59. func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder {
  60. return &NilResponseHashSumRecorder{
  61. Hash: fnv.New32(),
  62. ResponseRecorder: *httptest.NewRecorder(),
  63. }
  64. }
  65. func TestMain(m *testing.M) {
  66. defer log.Close()
  67. managerCtx, cancel := context.WithCancel(context.Background())
  68. graceful.InitManager(managerCtx)
  69. defer cancel()
  70. tests.InitTest(true)
  71. c = routers.NormalRoutes(context.TODO())
  72. // integration test settings...
  73. if setting.Cfg != nil {
  74. testingCfg := setting.Cfg.Section("integration-tests")
  75. tests.SlowTest = testingCfg.Key("SLOW_TEST").MustDuration(tests.SlowTest)
  76. tests.SlowFlush = testingCfg.Key("SLOW_FLUSH").MustDuration(tests.SlowFlush)
  77. }
  78. if os.Getenv("GITEA_SLOW_TEST_TIME") != "" {
  79. duration, err := time.ParseDuration(os.Getenv("GITEA_SLOW_TEST_TIME"))
  80. if err == nil {
  81. tests.SlowTest = duration
  82. }
  83. }
  84. if os.Getenv("GITEA_SLOW_FLUSH_TIME") != "" {
  85. duration, err := time.ParseDuration(os.Getenv("GITEA_SLOW_FLUSH_TIME"))
  86. if err == nil {
  87. tests.SlowFlush = duration
  88. }
  89. }
  90. os.Unsetenv("GIT_AUTHOR_NAME")
  91. os.Unsetenv("GIT_AUTHOR_EMAIL")
  92. os.Unsetenv("GIT_AUTHOR_DATE")
  93. os.Unsetenv("GIT_COMMITTER_NAME")
  94. os.Unsetenv("GIT_COMMITTER_EMAIL")
  95. os.Unsetenv("GIT_COMMITTER_DATE")
  96. err := unittest.InitFixtures(
  97. unittest.FixturesOptions{
  98. Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),
  99. },
  100. )
  101. if err != nil {
  102. fmt.Printf("Error initializing test database: %v\n", err)
  103. os.Exit(1)
  104. }
  105. exitCode := m.Run()
  106. tests.WriterCloser.Reset()
  107. if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil {
  108. fmt.Printf("util.RemoveAll: %v\n", err)
  109. os.Exit(1)
  110. }
  111. if err = util.RemoveAll(setting.Indexer.RepoPath); err != nil {
  112. fmt.Printf("Unable to remove repo indexer: %v\n", err)
  113. os.Exit(1)
  114. }
  115. os.Exit(exitCode)
  116. }
  117. type TestSession struct {
  118. jar http.CookieJar
  119. }
  120. func (s *TestSession) GetCookie(name string) *http.Cookie {
  121. baseURL, err := url.Parse(setting.AppURL)
  122. if err != nil {
  123. return nil
  124. }
  125. for _, c := range s.jar.Cookies(baseURL) {
  126. if c.Name == name {
  127. return c
  128. }
  129. }
  130. return nil
  131. }
  132. func (s *TestSession) MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder {
  133. t.Helper()
  134. baseURL, err := url.Parse(setting.AppURL)
  135. assert.NoError(t, err)
  136. for _, c := range s.jar.Cookies(baseURL) {
  137. req.AddCookie(c)
  138. }
  139. resp := MakeRequest(t, req, expectedStatus)
  140. ch := http.Header{}
  141. ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
  142. cr := http.Request{Header: ch}
  143. s.jar.SetCookies(baseURL, cr.Cookies())
  144. return resp
  145. }
  146. func (s *TestSession) MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseRecorder {
  147. t.Helper()
  148. baseURL, err := url.Parse(setting.AppURL)
  149. assert.NoError(t, err)
  150. for _, c := range s.jar.Cookies(baseURL) {
  151. req.AddCookie(c)
  152. }
  153. resp := MakeRequestNilResponseRecorder(t, req, expectedStatus)
  154. ch := http.Header{}
  155. ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
  156. cr := http.Request{Header: ch}
  157. s.jar.SetCookies(baseURL, cr.Cookies())
  158. return resp
  159. }
  160. func (s *TestSession) MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder {
  161. t.Helper()
  162. baseURL, err := url.Parse(setting.AppURL)
  163. assert.NoError(t, err)
  164. for _, c := range s.jar.Cookies(baseURL) {
  165. req.AddCookie(c)
  166. }
  167. resp := MakeRequestNilResponseHashSumRecorder(t, req, expectedStatus)
  168. ch := http.Header{}
  169. ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
  170. cr := http.Request{Header: ch}
  171. s.jar.SetCookies(baseURL, cr.Cookies())
  172. return resp
  173. }
  174. const userPassword = "password"
  175. var loginSessionCache = make(map[string]*TestSession, 10)
  176. func emptyTestSession(t testing.TB) *TestSession {
  177. t.Helper()
  178. jar, err := cookiejar.New(nil)
  179. assert.NoError(t, err)
  180. return &TestSession{jar: jar}
  181. }
  182. func getUserToken(t testing.TB, userName string) string {
  183. return getTokenForLoggedInUser(t, loginUser(t, userName))
  184. }
  185. func loginUser(t testing.TB, userName string) *TestSession {
  186. t.Helper()
  187. if session, ok := loginSessionCache[userName]; ok {
  188. return session
  189. }
  190. session := loginUserWithPassword(t, userName, userPassword)
  191. loginSessionCache[userName] = session
  192. return session
  193. }
  194. func loginUserWithPassword(t testing.TB, userName, password string) *TestSession {
  195. t.Helper()
  196. req := NewRequest(t, "GET", "/user/login")
  197. resp := MakeRequest(t, req, http.StatusOK)
  198. doc := NewHTMLParser(t, resp.Body)
  199. req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
  200. "_csrf": doc.GetCSRF(),
  201. "user_name": userName,
  202. "password": password,
  203. })
  204. resp = MakeRequest(t, req, http.StatusSeeOther)
  205. ch := http.Header{}
  206. ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
  207. cr := http.Request{Header: ch}
  208. session := emptyTestSession(t)
  209. baseURL, err := url.Parse(setting.AppURL)
  210. assert.NoError(t, err)
  211. session.jar.SetCookies(baseURL, cr.Cookies())
  212. return session
  213. }
  214. // token has to be unique this counter take care of
  215. var tokenCounter int64
  216. func getTokenForLoggedInUser(t testing.TB, session *TestSession) string {
  217. t.Helper()
  218. req := NewRequest(t, "GET", "/user/settings/applications")
  219. resp := session.MakeRequest(t, req, http.StatusOK)
  220. doc := NewHTMLParser(t, resp.Body)
  221. req = NewRequestWithValues(t, "POST", "/user/settings/applications", map[string]string{
  222. "_csrf": doc.GetCSRF(),
  223. "name": fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1)),
  224. })
  225. session.MakeRequest(t, req, http.StatusSeeOther)
  226. req = NewRequest(t, "GET", "/user/settings/applications")
  227. resp = session.MakeRequest(t, req, http.StatusOK)
  228. htmlDoc := NewHTMLParser(t, resp.Body)
  229. token := htmlDoc.doc.Find(".ui.info p").Text()
  230. assert.NotEmpty(t, token)
  231. return token
  232. }
  233. func NewRequest(t testing.TB, method, urlStr string) *http.Request {
  234. t.Helper()
  235. return NewRequestWithBody(t, method, urlStr, nil)
  236. }
  237. func NewRequestf(t testing.TB, method, urlFormat string, args ...interface{}) *http.Request {
  238. t.Helper()
  239. return NewRequest(t, method, fmt.Sprintf(urlFormat, args...))
  240. }
  241. func NewRequestWithValues(t testing.TB, method, urlStr string, values map[string]string) *http.Request {
  242. t.Helper()
  243. urlValues := url.Values{}
  244. for key, value := range values {
  245. urlValues[key] = []string{value}
  246. }
  247. req := NewRequestWithBody(t, method, urlStr, bytes.NewBufferString(urlValues.Encode()))
  248. req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
  249. return req
  250. }
  251. func NewRequestWithJSON(t testing.TB, method, urlStr string, v interface{}) *http.Request {
  252. t.Helper()
  253. jsonBytes, err := json.Marshal(v)
  254. assert.NoError(t, err)
  255. req := NewRequestWithBody(t, method, urlStr, bytes.NewBuffer(jsonBytes))
  256. req.Header.Add("Content-Type", "application/json")
  257. return req
  258. }
  259. func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *http.Request {
  260. t.Helper()
  261. if !strings.HasPrefix(urlStr, "http") && !strings.HasPrefix(urlStr, "/") {
  262. urlStr = "/" + urlStr
  263. }
  264. request, err := http.NewRequest(method, urlStr, body)
  265. assert.NoError(t, err)
  266. request.RequestURI = urlStr
  267. return request
  268. }
  269. func AddBasicAuthHeader(request *http.Request, username string) *http.Request {
  270. request.SetBasicAuth(username, userPassword)
  271. return request
  272. }
  273. const NoExpectedStatus = -1
  274. func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder {
  275. t.Helper()
  276. recorder := httptest.NewRecorder()
  277. c.ServeHTTP(recorder, req)
  278. if expectedStatus != NoExpectedStatus {
  279. if !assert.EqualValues(t, expectedStatus, recorder.Code,
  280. "Request: %s %s", req.Method, req.URL.String()) {
  281. logUnexpectedResponse(t, recorder)
  282. }
  283. }
  284. return recorder
  285. }
  286. func MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseRecorder {
  287. t.Helper()
  288. recorder := NewNilResponseRecorder()
  289. c.ServeHTTP(recorder, req)
  290. if expectedStatus != NoExpectedStatus {
  291. if !assert.EqualValues(t, expectedStatus, recorder.Code,
  292. "Request: %s %s", req.Method, req.URL.String()) {
  293. logUnexpectedResponse(t, &recorder.ResponseRecorder)
  294. }
  295. }
  296. return recorder
  297. }
  298. func MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder {
  299. t.Helper()
  300. recorder := NewNilResponseHashSumRecorder()
  301. c.ServeHTTP(recorder, req)
  302. if expectedStatus != NoExpectedStatus {
  303. if !assert.EqualValues(t, expectedStatus, recorder.Code,
  304. "Request: %s %s", req.Method, req.URL.String()) {
  305. logUnexpectedResponse(t, &recorder.ResponseRecorder)
  306. }
  307. }
  308. return recorder
  309. }
  310. // logUnexpectedResponse logs the contents of an unexpected response.
  311. func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
  312. t.Helper()
  313. respBytes := recorder.Body.Bytes()
  314. if len(respBytes) == 0 {
  315. return
  316. } else if len(respBytes) < 500 {
  317. // if body is short, just log the whole thing
  318. t.Log("Response:", string(respBytes))
  319. return
  320. }
  321. // log the "flash" error message, if one exists
  322. // we must create a new buffer, so that we don't "use up" resp.Body
  323. htmlDoc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(respBytes))
  324. if err != nil {
  325. return // probably a non-HTML response
  326. }
  327. errMsg := htmlDoc.Find(".ui.negative.message").Text()
  328. if len(errMsg) > 0 {
  329. t.Log("A flash error message was found:", errMsg)
  330. }
  331. }
  332. func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v interface{}) {
  333. t.Helper()
  334. decoder := json.NewDecoder(resp.Body)
  335. assert.NoError(t, decoder.Decode(v))
  336. }
  337. func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
  338. t.Helper()
  339. req := NewRequest(t, "GET", urlStr)
  340. resp := session.MakeRequest(t, req, http.StatusOK)
  341. doc := NewHTMLParser(t, resp.Body)
  342. return doc.GetCSRF()
  343. }