|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- // Copyright 2015 Google Inc. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
-
- package gensupport
-
- import (
- "bytes"
- "crypto/rand"
- "io"
- "io/ioutil"
- "net/http"
- "reflect"
- "strings"
- "testing"
-
- "google.golang.org/api/googleapi"
- )
-
- func TestContentSniffing(t *testing.T) {
- type testCase struct {
- data []byte // the data to read from the Reader
- finalErr error // error to return after data has been read
-
- wantContentType string
- wantContentTypeResult bool
- }
-
- for _, tc := range []testCase{
- {
- data: []byte{0, 0, 0, 0},
- finalErr: nil,
- wantContentType: "application/octet-stream",
- wantContentTypeResult: true,
- },
- {
- data: []byte(""),
- finalErr: nil,
- wantContentType: "text/plain; charset=utf-8",
- wantContentTypeResult: true,
- },
- {
- data: []byte(""),
- finalErr: io.ErrUnexpectedEOF,
- wantContentType: "text/plain; charset=utf-8",
- wantContentTypeResult: false,
- },
- {
- data: []byte("abc"),
- finalErr: nil,
- wantContentType: "text/plain; charset=utf-8",
- wantContentTypeResult: true,
- },
- {
- data: []byte("abc"),
- finalErr: io.ErrUnexpectedEOF,
- wantContentType: "text/plain; charset=utf-8",
- wantContentTypeResult: false,
- },
- // The following examples contain more bytes than are buffered for sniffing.
- {
- data: bytes.Repeat([]byte("a"), 513),
- finalErr: nil,
- wantContentType: "text/plain; charset=utf-8",
- wantContentTypeResult: true,
- },
- {
- data: bytes.Repeat([]byte("a"), 513),
- finalErr: io.ErrUnexpectedEOF,
- wantContentType: "text/plain; charset=utf-8",
- wantContentTypeResult: true, // true because error is after first 512 bytes.
- },
- } {
- er := &errReader{buf: tc.data, err: tc.finalErr}
-
- sct := newContentSniffer(er)
-
- // Even if was an error during the first 512 bytes, we should still be able to read those bytes.
- buf, err := ioutil.ReadAll(sct)
-
- if !reflect.DeepEqual(buf, tc.data) {
- t.Fatalf("Failed reading buffer: got: %q; want:%q", buf, tc.data)
- }
-
- if err != tc.finalErr {
- t.Fatalf("Reading buffer error: got: %v; want: %v", err, tc.finalErr)
- }
-
- ct, ok := sct.ContentType()
- if ok != tc.wantContentTypeResult {
- t.Fatalf("Content type result got: %v; want: %v", ok, tc.wantContentTypeResult)
- }
- if ok && ct != tc.wantContentType {
- t.Fatalf("Content type got: %q; want: %q", ct, tc.wantContentType)
- }
- }
- }
-
- type staticContentTyper struct {
- io.Reader
- }
-
- func (sct staticContentTyper) ContentType() string {
- return "static content type"
- }
-
- func TestDetermineContentType(t *testing.T) {
- data := []byte("abc")
- rdr := func() io.Reader {
- return bytes.NewBuffer(data)
- }
-
- type testCase struct {
- r io.Reader
- explicitConentType string
- wantContentType string
- }
-
- for _, tc := range []testCase{
- {
- r: rdr(),
- wantContentType: "text/plain; charset=utf-8",
- },
- {
- r: staticContentTyper{rdr()},
- wantContentType: "static content type",
- },
- {
- r: staticContentTyper{rdr()},
- explicitConentType: "explicit",
- wantContentType: "explicit",
- },
- } {
- r, ctype := DetermineContentType(tc.r, tc.explicitConentType)
- got, err := ioutil.ReadAll(r)
- if err != nil {
- t.Fatalf("Failed reading buffer: %v", err)
- }
- if !reflect.DeepEqual(got, data) {
- t.Fatalf("Failed reading buffer: got: %q; want:%q", got, data)
- }
-
- if ctype != tc.wantContentType {
- t.Fatalf("Content type got: %q; want: %q", ctype, tc.wantContentType)
- }
- }
- }
-
- func TestNewInfoFromMedia(t *testing.T) {
- const textType = "text/plain; charset=utf-8"
- for _, test := range []struct {
- desc string
- r io.Reader
- opts []googleapi.MediaOption
- wantType string
- wantMedia, wantBuffer, wantSingleChunk bool
- }{
- {
- desc: "an empty reader results in a MediaBuffer with a single, empty chunk",
- r: new(bytes.Buffer),
- opts: nil,
- wantType: textType,
- wantBuffer: true,
- wantSingleChunk: true,
- },
- {
- desc: "ContentType is observed",
- r: new(bytes.Buffer),
- opts: []googleapi.MediaOption{googleapi.ContentType("xyz")},
- wantType: "xyz",
- wantBuffer: true,
- wantSingleChunk: true,
- },
- {
- desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
- r: strings.NewReader("12345"),
- opts: []googleapi.MediaOption{googleapi.ChunkSize(0)},
- wantType: textType,
- wantMedia: true,
- wantSingleChunk: true,
- },
- {
- desc: "chunk size > data size: MediaBuffer with single chunk",
- r: strings.NewReader("12345"),
- opts: []googleapi.MediaOption{googleapi.ChunkSize(100)},
- wantType: textType,
- wantBuffer: true,
- wantSingleChunk: true,
- },
- {
- desc: "chunk size == data size: MediaBuffer with single chunk",
- r: &nullReader{googleapi.MinUploadChunkSize},
- opts: []googleapi.MediaOption{googleapi.ChunkSize(1)},
- wantType: "application/octet-stream",
- wantBuffer: true,
- wantSingleChunk: true,
- },
- {
- desc: "chunk size < data size: MediaBuffer, not single chunk",
- // Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
- r: &nullReader{2 * googleapi.MinUploadChunkSize},
- opts: []googleapi.MediaOption{googleapi.ChunkSize(1)},
- wantType: "application/octet-stream",
- wantBuffer: true,
- wantSingleChunk: false,
- },
- } {
-
- mi := NewInfoFromMedia(test.r, test.opts)
- if got, want := mi.mType, test.wantType; got != want {
- t.Errorf("%s: type: got %q, want %q", test.desc, got, want)
- }
- if got, want := (mi.media != nil), test.wantMedia; got != want {
- t.Errorf("%s: media non-nil: got %t, want %t", test.desc, got, want)
- }
- if got, want := (mi.buffer != nil), test.wantBuffer; got != want {
- t.Errorf("%s: buffer non-nil: got %t, want %t", test.desc, got, want)
- }
- if got, want := mi.singleChunk, test.wantSingleChunk; got != want {
- t.Errorf("%s: singleChunk: got %t, want %t", test.desc, got, want)
- }
- }
- }
-
- func TestUploadRequest(t *testing.T) {
- for _, test := range []struct {
- desc string
- r io.Reader
- chunkSize int
- wantContentType string
- wantUploadType string
- }{
- {
- desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
- r: strings.NewReader("12345"),
- chunkSize: 0,
- wantContentType: "multipart/related;",
- },
- {
- desc: "chunk size > data size: MediaBuffer with single chunk",
- r: strings.NewReader("12345"),
- chunkSize: 100,
- wantContentType: "multipart/related;",
- },
- {
- desc: "chunk size == data size: MediaBuffer with single chunk",
- r: &nullReader{googleapi.MinUploadChunkSize},
- chunkSize: 1,
- wantContentType: "multipart/related;",
- },
- {
- desc: "chunk size < data size: MediaBuffer, not single chunk",
- // Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
- r: &nullReader{2 * googleapi.MinUploadChunkSize},
- chunkSize: 1,
- wantUploadType: "application/octet-stream",
- },
- } {
- mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
- h := http.Header{}
- mi.UploadRequest(h, new(bytes.Buffer))
- if got, want := h.Get("Content-Type"), test.wantContentType; !strings.HasPrefix(got, want) {
- t.Errorf("%s: Content-Type: got %q, want prefix %q", test.desc, got, want)
- }
- if got, want := h.Get("X-Upload-Content-Type"), test.wantUploadType; got != want {
- t.Errorf("%s: X-Upload-Content-Type: got %q, want %q", test.desc, got, want)
- }
- }
- }
-
- func TestUploadRequestGetBody(t *testing.T) {
- // Test that a single chunk results in a getBody function that is non-nil, and
- // that produces the same content as the original body.
-
- // Mock out rand.Reader so we use the same multipart boundary every time.
- rr := rand.Reader
- rand.Reader = &nullReader{1000}
- defer func() {
- rand.Reader = rr
- }()
-
- for _, test := range []struct {
- desc string
- r io.Reader
- chunkSize int
- wantGetBody bool
- wantContentType string
- wantUploadType string
- }{
- {
- desc: "chunk size of zero: no getBody",
- r: &nullReader{10},
- chunkSize: 0,
- wantGetBody: false,
- },
- {
- desc: "chunk size == data size: 1 chunk, getBody",
- r: &nullReader{googleapi.MinUploadChunkSize},
- chunkSize: 1,
- wantGetBody: true,
- },
- {
- desc: "chunk size < data size: MediaBuffer, >1 chunk, no getBody",
- // No getBody here, because the initial request contains no media data
- // Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
- r: &nullReader{2 * googleapi.MinUploadChunkSize},
- chunkSize: 1,
- wantGetBody: false,
- },
- } {
- mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
- r, getBody, _ := mi.UploadRequest(http.Header{}, bytes.NewBuffer([]byte("body")))
- if got, want := (getBody != nil), test.wantGetBody; got != want {
- t.Errorf("%s: getBody: got %t, want %t", test.desc, got, want)
- continue
- }
- if getBody == nil {
- continue
- }
- want, err := ioutil.ReadAll(r)
- if err != nil {
- t.Fatal(err)
- }
- for i := 0; i < 3; i++ {
- rc, err := getBody()
- if err != nil {
- t.Fatal(err)
- }
- got, err := ioutil.ReadAll(rc)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(got, want) {
- t.Errorf("%s, %d:\ngot:\n%s\nwant:\n%s", test.desc, i, string(got), string(want))
- }
- }
- }
- }
-
- func TestResumableUpload(t *testing.T) {
- for _, test := range []struct {
- desc string
- r io.Reader
- chunkSize int
- wantUploadType string
- wantResumableUpload bool
- }{
- {
- desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
- r: strings.NewReader("12345"),
- chunkSize: 0,
- wantUploadType: "multipart",
- wantResumableUpload: false,
- },
- {
- desc: "chunk size > data size: MediaBuffer with single chunk",
- r: strings.NewReader("12345"),
- chunkSize: 100,
- wantUploadType: "multipart",
- wantResumableUpload: false,
- },
- {
- desc: "chunk size == data size: MediaBuffer with single chunk",
- // (Because nullReader returns EOF with the last bytes.)
- r: &nullReader{googleapi.MinUploadChunkSize},
- chunkSize: googleapi.MinUploadChunkSize,
- wantUploadType: "multipart",
- wantResumableUpload: false,
- },
- {
- desc: "chunk size < data size: MediaBuffer, not single chunk",
- // Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
- r: &nullReader{2 * googleapi.MinUploadChunkSize},
- chunkSize: 1,
- wantUploadType: "resumable",
- wantResumableUpload: true,
- },
- } {
- mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
- if got, want := mi.UploadType(), test.wantUploadType; got != want {
- t.Errorf("%s: upload type: got %q, want %q", test.desc, got, want)
- }
- if got, want := mi.ResumableUpload("") != nil, test.wantResumableUpload; got != want {
- t.Errorf("%s: resumable upload non-nil: got %t, want %t", test.desc, got, want)
- }
- }
- }
-
- // A nullReader simulates reading a fixed number of bytes.
- type nullReader struct {
- remain int
- }
-
- // Read doesn't touch buf, but it does reduce the amount of bytes remaining
- // by len(buf).
- func (r *nullReader) Read(buf []byte) (int, error) {
- n := len(buf)
- if r.remain < n {
- n = r.remain
- }
- r.remain -= n
- var err error
- if r.remain == 0 {
- err = io.EOF
- }
- return n, err
- }
|