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.
 
 
 

406 lines
12 KiB

  1. // Copyright 2015 Google LLC
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gensupport
  5. import (
  6. "bytes"
  7. "crypto/rand"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "reflect"
  12. "strings"
  13. "testing"
  14. "google.golang.org/api/googleapi"
  15. )
  16. func TestContentSniffing(t *testing.T) {
  17. type testCase struct {
  18. data []byte // the data to read from the Reader
  19. finalErr error // error to return after data has been read
  20. wantContentType string
  21. wantContentTypeResult bool
  22. }
  23. for _, tc := range []testCase{
  24. {
  25. data: []byte{0, 0, 0, 0},
  26. finalErr: nil,
  27. wantContentType: "application/octet-stream",
  28. wantContentTypeResult: true,
  29. },
  30. {
  31. data: []byte(""),
  32. finalErr: nil,
  33. wantContentType: "text/plain; charset=utf-8",
  34. wantContentTypeResult: true,
  35. },
  36. {
  37. data: []byte(""),
  38. finalErr: io.ErrUnexpectedEOF,
  39. wantContentType: "text/plain; charset=utf-8",
  40. wantContentTypeResult: false,
  41. },
  42. {
  43. data: []byte("abc"),
  44. finalErr: nil,
  45. wantContentType: "text/plain; charset=utf-8",
  46. wantContentTypeResult: true,
  47. },
  48. {
  49. data: []byte("abc"),
  50. finalErr: io.ErrUnexpectedEOF,
  51. wantContentType: "text/plain; charset=utf-8",
  52. wantContentTypeResult: false,
  53. },
  54. // The following examples contain more bytes than are buffered for sniffing.
  55. {
  56. data: bytes.Repeat([]byte("a"), 513),
  57. finalErr: nil,
  58. wantContentType: "text/plain; charset=utf-8",
  59. wantContentTypeResult: true,
  60. },
  61. {
  62. data: bytes.Repeat([]byte("a"), 513),
  63. finalErr: io.ErrUnexpectedEOF,
  64. wantContentType: "text/plain; charset=utf-8",
  65. wantContentTypeResult: true, // true because error is after first 512 bytes.
  66. },
  67. } {
  68. er := &errReader{buf: tc.data, err: tc.finalErr}
  69. sct := newContentSniffer(er)
  70. // Even if was an error during the first 512 bytes, we should still be able to read those bytes.
  71. buf, err := ioutil.ReadAll(sct)
  72. if !reflect.DeepEqual(buf, tc.data) {
  73. t.Fatalf("Failed reading buffer: got: %q; want:%q", buf, tc.data)
  74. }
  75. if err != tc.finalErr {
  76. t.Fatalf("Reading buffer error: got: %v; want: %v", err, tc.finalErr)
  77. }
  78. ct, ok := sct.ContentType()
  79. if ok != tc.wantContentTypeResult {
  80. t.Fatalf("Content type result got: %v; want: %v", ok, tc.wantContentTypeResult)
  81. }
  82. if ok && ct != tc.wantContentType {
  83. t.Fatalf("Content type got: %q; want: %q", ct, tc.wantContentType)
  84. }
  85. }
  86. }
  87. type staticContentTyper struct {
  88. io.Reader
  89. }
  90. func (sct staticContentTyper) ContentType() string {
  91. return "static content type"
  92. }
  93. func TestDetermineContentType(t *testing.T) {
  94. data := []byte("abc")
  95. rdr := func() io.Reader {
  96. return bytes.NewBuffer(data)
  97. }
  98. type testCase struct {
  99. r io.Reader
  100. explicitConentType string
  101. wantContentType string
  102. }
  103. for _, tc := range []testCase{
  104. {
  105. r: rdr(),
  106. wantContentType: "text/plain; charset=utf-8",
  107. },
  108. {
  109. r: staticContentTyper{rdr()},
  110. wantContentType: "static content type",
  111. },
  112. {
  113. r: staticContentTyper{rdr()},
  114. explicitConentType: "explicit",
  115. wantContentType: "explicit",
  116. },
  117. } {
  118. r, ctype := DetermineContentType(tc.r, tc.explicitConentType)
  119. got, err := ioutil.ReadAll(r)
  120. if err != nil {
  121. t.Fatalf("Failed reading buffer: %v", err)
  122. }
  123. if !reflect.DeepEqual(got, data) {
  124. t.Fatalf("Failed reading buffer: got: %q; want:%q", got, data)
  125. }
  126. if ctype != tc.wantContentType {
  127. t.Fatalf("Content type got: %q; want: %q", ctype, tc.wantContentType)
  128. }
  129. }
  130. }
  131. func TestNewInfoFromMedia(t *testing.T) {
  132. const textType = "text/plain; charset=utf-8"
  133. for _, test := range []struct {
  134. desc string
  135. r io.Reader
  136. opts []googleapi.MediaOption
  137. wantType string
  138. wantMedia, wantBuffer, wantSingleChunk bool
  139. }{
  140. {
  141. desc: "an empty reader results in a MediaBuffer with a single, empty chunk",
  142. r: new(bytes.Buffer),
  143. opts: nil,
  144. wantType: textType,
  145. wantBuffer: true,
  146. wantSingleChunk: true,
  147. },
  148. {
  149. desc: "ContentType is observed",
  150. r: new(bytes.Buffer),
  151. opts: []googleapi.MediaOption{googleapi.ContentType("xyz")},
  152. wantType: "xyz",
  153. wantBuffer: true,
  154. wantSingleChunk: true,
  155. },
  156. {
  157. desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
  158. r: strings.NewReader("12345"),
  159. opts: []googleapi.MediaOption{googleapi.ChunkSize(0)},
  160. wantType: textType,
  161. wantMedia: true,
  162. wantSingleChunk: true,
  163. },
  164. {
  165. desc: "chunk size > data size: MediaBuffer with single chunk",
  166. r: strings.NewReader("12345"),
  167. opts: []googleapi.MediaOption{googleapi.ChunkSize(100)},
  168. wantType: textType,
  169. wantBuffer: true,
  170. wantSingleChunk: true,
  171. },
  172. {
  173. desc: "chunk size == data size: MediaBuffer with single chunk",
  174. r: &nullReader{googleapi.MinUploadChunkSize},
  175. opts: []googleapi.MediaOption{googleapi.ChunkSize(1)},
  176. wantType: "application/octet-stream",
  177. wantBuffer: true,
  178. wantSingleChunk: true,
  179. },
  180. {
  181. desc: "chunk size < data size: MediaBuffer, not single chunk",
  182. // Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
  183. r: &nullReader{2 * googleapi.MinUploadChunkSize},
  184. opts: []googleapi.MediaOption{googleapi.ChunkSize(1)},
  185. wantType: "application/octet-stream",
  186. wantBuffer: true,
  187. wantSingleChunk: false,
  188. },
  189. } {
  190. mi := NewInfoFromMedia(test.r, test.opts)
  191. if got, want := mi.mType, test.wantType; got != want {
  192. t.Errorf("%s: type: got %q, want %q", test.desc, got, want)
  193. }
  194. if got, want := (mi.media != nil), test.wantMedia; got != want {
  195. t.Errorf("%s: media non-nil: got %t, want %t", test.desc, got, want)
  196. }
  197. if got, want := (mi.buffer != nil), test.wantBuffer; got != want {
  198. t.Errorf("%s: buffer non-nil: got %t, want %t", test.desc, got, want)
  199. }
  200. if got, want := mi.singleChunk, test.wantSingleChunk; got != want {
  201. t.Errorf("%s: singleChunk: got %t, want %t", test.desc, got, want)
  202. }
  203. }
  204. }
  205. func TestUploadRequest(t *testing.T) {
  206. for _, test := range []struct {
  207. desc string
  208. r io.Reader
  209. chunkSize int
  210. wantContentType string
  211. wantUploadType string
  212. }{
  213. {
  214. desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
  215. r: strings.NewReader("12345"),
  216. chunkSize: 0,
  217. wantContentType: "multipart/related;",
  218. },
  219. {
  220. desc: "chunk size > data size: MediaBuffer with single chunk",
  221. r: strings.NewReader("12345"),
  222. chunkSize: 100,
  223. wantContentType: "multipart/related;",
  224. },
  225. {
  226. desc: "chunk size == data size: MediaBuffer with single chunk",
  227. r: &nullReader{googleapi.MinUploadChunkSize},
  228. chunkSize: 1,
  229. wantContentType: "multipart/related;",
  230. },
  231. {
  232. desc: "chunk size < data size: MediaBuffer, not single chunk",
  233. // Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
  234. r: &nullReader{2 * googleapi.MinUploadChunkSize},
  235. chunkSize: 1,
  236. wantUploadType: "application/octet-stream",
  237. },
  238. } {
  239. mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
  240. h := http.Header{}
  241. mi.UploadRequest(h, new(bytes.Buffer))
  242. if got, want := h.Get("Content-Type"), test.wantContentType; !strings.HasPrefix(got, want) {
  243. t.Errorf("%s: Content-Type: got %q, want prefix %q", test.desc, got, want)
  244. }
  245. if got, want := h.Get("X-Upload-Content-Type"), test.wantUploadType; got != want {
  246. t.Errorf("%s: X-Upload-Content-Type: got %q, want %q", test.desc, got, want)
  247. }
  248. }
  249. }
  250. func TestUploadRequestGetBody(t *testing.T) {
  251. // Test that a single chunk results in a getBody function that is non-nil, and
  252. // that produces the same content as the original body.
  253. // Mock out rand.Reader so we use the same multipart boundary every time.
  254. rr := rand.Reader
  255. rand.Reader = &nullReader{1000}
  256. defer func() {
  257. rand.Reader = rr
  258. }()
  259. for _, test := range []struct {
  260. desc string
  261. r io.Reader
  262. chunkSize int
  263. wantGetBody bool
  264. }{
  265. {
  266. desc: "chunk size of zero: no getBody",
  267. r: &nullReader{10},
  268. chunkSize: 0,
  269. wantGetBody: false,
  270. },
  271. {
  272. desc: "chunk size == data size: 1 chunk, getBody",
  273. r: &nullReader{googleapi.MinUploadChunkSize},
  274. chunkSize: 1,
  275. wantGetBody: true,
  276. },
  277. {
  278. desc: "chunk size < data size: MediaBuffer, >1 chunk, no getBody",
  279. // No getBody here, because the initial request contains no media data
  280. // Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
  281. r: &nullReader{2 * googleapi.MinUploadChunkSize},
  282. chunkSize: 1,
  283. wantGetBody: false,
  284. },
  285. } {
  286. mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
  287. r, getBody, _ := mi.UploadRequest(http.Header{}, bytes.NewBuffer([]byte("body")))
  288. if got, want := (getBody != nil), test.wantGetBody; got != want {
  289. t.Errorf("%s: getBody: got %t, want %t", test.desc, got, want)
  290. continue
  291. }
  292. if getBody == nil {
  293. continue
  294. }
  295. want, err := ioutil.ReadAll(r)
  296. if err != nil {
  297. t.Fatal(err)
  298. }
  299. for i := 0; i < 3; i++ {
  300. rc, err := getBody()
  301. if err != nil {
  302. t.Fatal(err)
  303. }
  304. got, err := ioutil.ReadAll(rc)
  305. if err != nil {
  306. t.Fatal(err)
  307. }
  308. if !bytes.Equal(got, want) {
  309. t.Errorf("%s, %d:\ngot:\n%s\nwant:\n%s", test.desc, i, string(got), string(want))
  310. }
  311. }
  312. }
  313. }
  314. func TestResumableUpload(t *testing.T) {
  315. for _, test := range []struct {
  316. desc string
  317. r io.Reader
  318. chunkSize int
  319. wantUploadType string
  320. wantResumableUpload bool
  321. }{
  322. {
  323. desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
  324. r: strings.NewReader("12345"),
  325. chunkSize: 0,
  326. wantUploadType: "multipart",
  327. wantResumableUpload: false,
  328. },
  329. {
  330. desc: "chunk size > data size: MediaBuffer with single chunk",
  331. r: strings.NewReader("12345"),
  332. chunkSize: 100,
  333. wantUploadType: "multipart",
  334. wantResumableUpload: false,
  335. },
  336. {
  337. desc: "chunk size == data size: MediaBuffer with single chunk",
  338. // (Because nullReader returns EOF with the last bytes.)
  339. r: &nullReader{googleapi.MinUploadChunkSize},
  340. chunkSize: googleapi.MinUploadChunkSize,
  341. wantUploadType: "multipart",
  342. wantResumableUpload: false,
  343. },
  344. {
  345. desc: "chunk size < data size: MediaBuffer, not single chunk",
  346. // Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
  347. r: &nullReader{2 * googleapi.MinUploadChunkSize},
  348. chunkSize: 1,
  349. wantUploadType: "resumable",
  350. wantResumableUpload: true,
  351. },
  352. } {
  353. mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
  354. if got, want := mi.UploadType(), test.wantUploadType; got != want {
  355. t.Errorf("%s: upload type: got %q, want %q", test.desc, got, want)
  356. }
  357. if got, want := mi.ResumableUpload("") != nil, test.wantResumableUpload; got != want {
  358. t.Errorf("%s: resumable upload non-nil: got %t, want %t", test.desc, got, want)
  359. }
  360. }
  361. }
  362. // A nullReader simulates reading a fixed number of bytes.
  363. type nullReader struct {
  364. remain int
  365. }
  366. // Read doesn't touch buf, but it does reduce the amount of bytes remaining
  367. // by len(buf).
  368. func (r *nullReader) Read(buf []byte) (int, error) {
  369. n := len(buf)
  370. if r.remain < n {
  371. n = r.remain
  372. }
  373. r.remain -= n
  374. var err error
  375. if r.remain == 0 {
  376. err = io.EOF
  377. }
  378. return n, err
  379. }