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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  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.Equal(t, 1, len(batchRequest.Objects))
  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: "Unexpected server response: ",
  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: make(map[string]TransferAdapter),
  217. }
  218. client.transfers["dummy"] = dummy
  219. err := client.Download(context.Background(), []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error {
  220. if objectError != nil {
  221. return objectError
  222. }
  223. b, err := io.ReadAll(content)
  224. assert.NoError(t, err)
  225. assert.Equal(t, []byte("dummy"), b)
  226. return nil
  227. })
  228. if len(c.expectederror) > 0 {
  229. assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
  230. } else {
  231. assert.NoError(t, err, "case %d", n)
  232. }
  233. }
  234. }
  235. func TestHTTPClientUpload(t *testing.T) {
  236. p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}
  237. hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
  238. assert.Equal(t, "POST", req.Method)
  239. assert.Equal(t, MediaType, req.Header.Get("Content-type"))
  240. assert.Equal(t, MediaType, req.Header.Get("Accept"))
  241. var batchRequest BatchRequest
  242. err := json.NewDecoder(req.Body).Decode(&batchRequest)
  243. assert.NoError(t, err)
  244. assert.Equal(t, "upload", batchRequest.Operation)
  245. assert.Equal(t, 1, len(batchRequest.Objects))
  246. assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid)
  247. assert.Equal(t, p.Size, batchRequest.Objects[0].Size)
  248. return lfsTestRoundtripHandler(req)
  249. })}
  250. dummy := &DummyTransferAdapter{}
  251. cases := []struct {
  252. endpoint string
  253. expectederror string
  254. }{
  255. // case 0
  256. {
  257. endpoint: "https://status-not-ok.io",
  258. expectederror: "Unexpected server response: ",
  259. },
  260. // case 1
  261. {
  262. endpoint: "https://invalid-json-response.io",
  263. expectederror: "invalid json",
  264. },
  265. // case 2
  266. {
  267. endpoint: "https://valid-batch-request-upload.io",
  268. expectederror: "",
  269. },
  270. // case 3
  271. {
  272. endpoint: "https://response-no-objects.io",
  273. expectederror: "",
  274. },
  275. // case 4
  276. {
  277. endpoint: "https://unknown-transfer-adapter.io",
  278. expectederror: "TransferAdapter not found: ",
  279. },
  280. // case 5
  281. {
  282. endpoint: "https://error-in-response-objects.io",
  283. expectederror: "Object not found",
  284. },
  285. // case 6
  286. {
  287. endpoint: "https://empty-actions-map.io",
  288. expectederror: "",
  289. },
  290. // case 7
  291. {
  292. endpoint: "https://download-actions-map.io",
  293. expectederror: "Missing action 'upload'",
  294. },
  295. // case 8
  296. {
  297. endpoint: "https://upload-actions-map.io",
  298. expectederror: "",
  299. },
  300. // case 9
  301. {
  302. endpoint: "https://verify-actions-map.io",
  303. expectederror: "Missing action 'upload'",
  304. },
  305. // case 10
  306. {
  307. endpoint: "https://unknown-actions-map.io",
  308. expectederror: "Missing action 'upload'",
  309. },
  310. }
  311. for n, c := range cases {
  312. client := &HTTPClient{
  313. client: hc,
  314. endpoint: c.endpoint,
  315. transfers: make(map[string]TransferAdapter),
  316. }
  317. client.transfers["dummy"] = dummy
  318. err := client.Upload(context.Background(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) {
  319. return io.NopCloser(new(bytes.Buffer)), objectError
  320. })
  321. if len(c.expectederror) > 0 {
  322. assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
  323. } else {
  324. assert.NoError(t, err, "case %d", n)
  325. }
  326. }
  327. }