gitea源码

http_client_test.go 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package lfs
  4. import (
  5. "bytes"
  6. "context"
  7. "io"
  8. "net/http"
  9. "strings"
  10. "testing"
  11. "code.gitea.io/gitea/modules/json"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/test"
  14. "github.com/stretchr/testify/assert"
  15. )
  16. type RoundTripFunc func(req *http.Request) *http.Response
  17. func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
  18. return f(req), nil
  19. }
  20. type DummyTransferAdapter struct{}
  21. func (a *DummyTransferAdapter) Name() string {
  22. return "dummy"
  23. }
  24. func (a *DummyTransferAdapter) Download(ctx context.Context, l *Link) (io.ReadCloser, error) {
  25. return io.NopCloser(strings.NewReader("dummy")), nil
  26. }
  27. func (a *DummyTransferAdapter) Upload(ctx context.Context, l *Link, p Pointer, r io.Reader) error {
  28. return nil
  29. }
  30. func (a *DummyTransferAdapter) Verify(ctx context.Context, l *Link, p Pointer) error {
  31. return nil
  32. }
  33. func lfsTestRoundtripHandler(req *http.Request) *http.Response {
  34. var batchResponse *BatchResponse
  35. url := req.URL.String()
  36. if strings.Contains(url, "status-not-ok") {
  37. return &http.Response{StatusCode: http.StatusBadRequest}
  38. } else if strings.Contains(url, "invalid-json-response") {
  39. return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader("invalid json"))}
  40. } else if strings.Contains(url, "valid-batch-request-download") {
  41. batchResponse = &BatchResponse{
  42. Transfer: "dummy",
  43. Objects: []*ObjectResponse{
  44. {
  45. Actions: map[string]*Link{
  46. "download": {},
  47. },
  48. },
  49. },
  50. }
  51. } else if strings.Contains(url, "legacy-batch-request-download") {
  52. batchResponse = &BatchResponse{
  53. Transfer: "dummy",
  54. Objects: []*ObjectResponse{
  55. {
  56. Links: map[string]*Link{
  57. "download": {},
  58. },
  59. },
  60. },
  61. }
  62. } else if strings.Contains(url, "valid-batch-request-upload") {
  63. batchResponse = &BatchResponse{
  64. Transfer: "dummy",
  65. Objects: []*ObjectResponse{
  66. {
  67. Actions: map[string]*Link{
  68. "upload": {},
  69. },
  70. },
  71. },
  72. }
  73. } else if strings.Contains(url, "response-no-objects") {
  74. batchResponse = &BatchResponse{Transfer: "dummy"}
  75. } else if strings.Contains(url, "unknown-transfer-adapter") {
  76. batchResponse = &BatchResponse{Transfer: "unknown_adapter"}
  77. } else if strings.Contains(url, "error-in-response-objects") {
  78. batchResponse = &BatchResponse{
  79. Transfer: "dummy",
  80. Objects: []*ObjectResponse{
  81. {
  82. Error: &ObjectError{
  83. Code: http.StatusNotFound,
  84. Message: "Object not found",
  85. },
  86. },
  87. },
  88. }
  89. } else if strings.Contains(url, "empty-actions-map") {
  90. batchResponse = &BatchResponse{
  91. Transfer: "dummy",
  92. Objects: []*ObjectResponse{
  93. {
  94. Actions: map[string]*Link{},
  95. },
  96. },
  97. }
  98. } else if strings.Contains(url, "download-actions-map") {
  99. batchResponse = &BatchResponse{
  100. Transfer: "dummy",
  101. Objects: []*ObjectResponse{
  102. {
  103. Actions: map[string]*Link{
  104. "download": {},
  105. },
  106. },
  107. },
  108. }
  109. } else if strings.Contains(url, "upload-actions-map") {
  110. batchResponse = &BatchResponse{
  111. Transfer: "dummy",
  112. Objects: []*ObjectResponse{
  113. {
  114. Actions: map[string]*Link{
  115. "upload": {},
  116. },
  117. },
  118. },
  119. }
  120. } else if strings.Contains(url, "verify-actions-map") {
  121. batchResponse = &BatchResponse{
  122. Transfer: "dummy",
  123. Objects: []*ObjectResponse{
  124. {
  125. Actions: map[string]*Link{
  126. "verify": {},
  127. },
  128. },
  129. },
  130. }
  131. } else if strings.Contains(url, "unknown-actions-map") {
  132. batchResponse = &BatchResponse{
  133. Transfer: "dummy",
  134. Objects: []*ObjectResponse{
  135. {
  136. Actions: map[string]*Link{
  137. "unknown": {},
  138. },
  139. },
  140. },
  141. }
  142. } else {
  143. return nil
  144. }
  145. payload := new(bytes.Buffer)
  146. json.NewEncoder(payload).Encode(batchResponse)
  147. return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(payload)}
  148. }
  149. func TestHTTPClientDownload(t *testing.T) {
  150. p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}
  151. hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
  152. assert.Equal(t, "POST", req.Method)
  153. assert.Equal(t, MediaType, req.Header.Get("Content-type"))
  154. assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
  155. var batchRequest BatchRequest
  156. err := json.NewDecoder(req.Body).Decode(&batchRequest)
  157. assert.NoError(t, err)
  158. assert.Equal(t, "download", batchRequest.Operation)
  159. assert.Len(t, batchRequest.Objects, 1)
  160. assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid)
  161. assert.Equal(t, p.Size, batchRequest.Objects[0].Size)
  162. return lfsTestRoundtripHandler(req)
  163. })}
  164. dummy := &DummyTransferAdapter{}
  165. cases := []struct {
  166. endpoint string
  167. expectedError string
  168. }{
  169. {
  170. endpoint: "https://status-not-ok.io",
  171. expectedError: io.ErrUnexpectedEOF.Error(),
  172. },
  173. {
  174. endpoint: "https://invalid-json-response.io",
  175. expectedError: "invalid json",
  176. },
  177. {
  178. endpoint: "https://valid-batch-request-download.io",
  179. expectedError: "",
  180. },
  181. {
  182. endpoint: "https://response-no-objects.io",
  183. expectedError: "",
  184. },
  185. {
  186. endpoint: "https://unknown-transfer-adapter.io",
  187. expectedError: "TransferAdapter not found: ",
  188. },
  189. {
  190. endpoint: "https://error-in-response-objects.io",
  191. expectedError: "Object not found",
  192. },
  193. {
  194. endpoint: "https://empty-actions-map.io",
  195. expectedError: "missing action 'download'",
  196. },
  197. {
  198. endpoint: "https://download-actions-map.io",
  199. expectedError: "",
  200. },
  201. {
  202. endpoint: "https://upload-actions-map.io",
  203. expectedError: "missing action 'download'",
  204. },
  205. {
  206. endpoint: "https://verify-actions-map.io",
  207. expectedError: "missing action 'download'",
  208. },
  209. {
  210. endpoint: "https://unknown-actions-map.io",
  211. expectedError: "missing action 'download'",
  212. },
  213. {
  214. endpoint: "https://legacy-batch-request-download.io",
  215. expectedError: "",
  216. },
  217. }
  218. defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)()
  219. for _, c := range cases {
  220. t.Run(c.endpoint, func(t *testing.T) {
  221. client := &HTTPClient{
  222. client: hc,
  223. endpoint: c.endpoint,
  224. transfers: map[string]TransferAdapter{
  225. "dummy": dummy,
  226. },
  227. }
  228. err := client.Download(t.Context(), []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error {
  229. if objectError != nil {
  230. return objectError
  231. }
  232. b, err := io.ReadAll(content)
  233. assert.NoError(t, err)
  234. assert.Equal(t, []byte("dummy"), b)
  235. return nil
  236. })
  237. if c.expectedError != "" {
  238. assert.ErrorContains(t, err, c.expectedError)
  239. } else {
  240. assert.NoError(t, err)
  241. }
  242. })
  243. }
  244. }
  245. func TestHTTPClientUpload(t *testing.T) {
  246. p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}
  247. hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
  248. assert.Equal(t, "POST", req.Method)
  249. assert.Equal(t, MediaType, req.Header.Get("Content-type"))
  250. assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
  251. var batchRequest BatchRequest
  252. err := json.NewDecoder(req.Body).Decode(&batchRequest)
  253. assert.NoError(t, err)
  254. assert.Equal(t, "upload", batchRequest.Operation)
  255. assert.Len(t, batchRequest.Objects, 1)
  256. assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid)
  257. assert.Equal(t, p.Size, batchRequest.Objects[0].Size)
  258. return lfsTestRoundtripHandler(req)
  259. })}
  260. dummy := &DummyTransferAdapter{}
  261. cases := []struct {
  262. endpoint string
  263. expectedError string
  264. }{
  265. {
  266. endpoint: "https://status-not-ok.io",
  267. expectedError: io.ErrUnexpectedEOF.Error(),
  268. },
  269. {
  270. endpoint: "https://invalid-json-response.io",
  271. expectedError: "invalid json",
  272. },
  273. {
  274. endpoint: "https://valid-batch-request-upload.io",
  275. expectedError: "",
  276. },
  277. {
  278. endpoint: "https://response-no-objects.io",
  279. expectedError: "",
  280. },
  281. {
  282. endpoint: "https://unknown-transfer-adapter.io",
  283. expectedError: "TransferAdapter not found: ",
  284. },
  285. {
  286. endpoint: "https://error-in-response-objects.io",
  287. expectedError: "Object not found",
  288. },
  289. {
  290. endpoint: "https://empty-actions-map.io",
  291. expectedError: "",
  292. },
  293. {
  294. endpoint: "https://download-actions-map.io",
  295. expectedError: "missing action 'upload'",
  296. },
  297. {
  298. endpoint: "https://upload-actions-map.io",
  299. expectedError: "",
  300. },
  301. {
  302. endpoint: "https://verify-actions-map.io",
  303. expectedError: "missing action 'upload'",
  304. },
  305. {
  306. endpoint: "https://unknown-actions-map.io",
  307. expectedError: "missing action 'upload'",
  308. },
  309. }
  310. defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)()
  311. for _, c := range cases {
  312. t.Run(c.endpoint, func(t *testing.T) {
  313. client := &HTTPClient{
  314. client: hc,
  315. endpoint: c.endpoint,
  316. transfers: map[string]TransferAdapter{
  317. "dummy": dummy,
  318. },
  319. }
  320. err := client.Upload(t.Context(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) {
  321. return io.NopCloser(new(bytes.Buffer)), objectError
  322. })
  323. if c.expectedError != "" {
  324. assert.ErrorContains(t, err, c.expectedError)
  325. } else {
  326. assert.NoError(t, err)
  327. }
  328. })
  329. }
  330. }