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.

http_client_test.go 8.8KB

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