1package cos
2
3import (
4	"bytes"
5	"context"
6	"encoding/xml"
7	"fmt"
8	"io"
9	"io/ioutil"
10	"net/http"
11	"net/http/httptest"
12	"net/textproto"
13	"net/url"
14	"reflect"
15	"sort"
16	"strings"
17	"testing"
18	"time"
19)
20
21var (
22	// mux is the HTTP request multiplexer used with the test server.
23	mux *http.ServeMux
24
25	// client is the COS client being tested.
26	client *Client
27
28	// server is a test HTTP server used to provide mock API responses.
29	server *httptest.Server
30)
31
32// setup sets up a test HTTP server along with a cos.Client that is
33// configured to talk to that test server. Tests should register handlers on
34// mux which provide mock responses for the API method being tested.
35func setup() {
36	// test server
37	mux = http.NewServeMux()
38	server = httptest.NewServer(mux)
39
40	u, _ := url.Parse(server.URL)
41	client = NewClient(&BaseURL{u, u}, nil)
42}
43
44// teardown closes the test HTTP server.
45func teardown() {
46	server.Close()
47}
48
49type values map[string]string
50
51func testFormValues(t *testing.T, r *http.Request, values values) {
52	want := url.Values{}
53	for k, v := range values {
54		want.Set(k, v)
55	}
56
57	r.ParseForm()
58	if got := r.Form; !reflect.DeepEqual(got, want) {
59		t.Errorf("Request parameters: %v, want %v", got, want)
60	}
61}
62
63func testMethod(t *testing.T, r *http.Request, want string) {
64	if got := r.Method; got != want {
65		t.Errorf("Request method: %v, want %v", got, want)
66	}
67}
68
69func testHeader(t *testing.T, r *http.Request, header string, want string) {
70	if got := r.Header.Get(header); got != want {
71		t.Errorf("Header.Get(%q) returned %q, want %q", header, got, want)
72	}
73}
74
75func testURLParseError(t *testing.T, err error) {
76	if err == nil {
77		t.Errorf("Expected error to be returned")
78	}
79	if err, ok := err.(*url.Error); !ok || err.Op != "parse" {
80		t.Errorf("Expected URL parse error, got %+v", err)
81	}
82}
83
84func testBody(t *testing.T, r *http.Request, want string) {
85	b, err := ioutil.ReadAll(r.Body)
86	if err != nil {
87		t.Errorf("Error reading request body: %v", err)
88	}
89	if got := string(b); got != want {
90		t.Errorf("request Body is %s, want %s", got, want)
91	}
92}
93
94// Helper function to test that a value is marshalled to XML as expected.
95func testXMLMarshal(t *testing.T, v interface{}, want string) {
96	j, err := xml.Marshal(v)
97	if err != nil {
98		t.Errorf("Unable to marshal JSON for %v", v)
99	}
100
101	w := new(bytes.Buffer)
102	err = xml.NewEncoder(w).Encode([]byte(want))
103	if err != nil {
104		t.Errorf("String is not valid json: %s", want)
105	}
106
107	if w.String() != string(j) {
108		t.Errorf("xml.Marshal(%q) returned %s, want %s", v, j, w)
109	}
110
111	// now go the other direction and make sure things unmarshal as expected
112	u := reflect.ValueOf(v).Interface()
113	if err := xml.Unmarshal([]byte(want), u); err != nil {
114		t.Errorf("Unable to unmarshal XML for %v", want)
115	}
116
117	if !reflect.DeepEqual(v, u) {
118		t.Errorf("xml.Unmarshal(%q) returned %s, want %s", want, u, v)
119	}
120}
121
122func TestNewClient(t *testing.T) {
123	c := NewClient(nil, nil)
124
125	if got, want := c.BaseURL.ServiceURL.String(), defaultServiceBaseURL; got != want {
126		t.Errorf("NewClient BaseURL is %v, want %v", got, want)
127	}
128	if got, want := c.UserAgent, userAgent; got != want {
129		t.Errorf("NewClient UserAgent is %v, want %v", got, want)
130	}
131}
132
133func TestNewBucketURL_secure_false(t *testing.T) {
134	got := NewBucketURL("bname", "idx", "ap-beijing", false).String()
135	want := "http://bname-idx.cos.ap-beijing.myqcloud.com"
136	if got != want {
137		t.Errorf("NewBucketURL is %v, want %v", got, want)
138	}
139}
140
141func TestNewBucketURL_secure_true(t *testing.T) {
142	got := NewBucketURL("bname", "idx", "ap-beijing", true).String()
143	want := "https://bname-idx.cos.ap-beijing.myqcloud.com"
144	if got != want {
145		t.Errorf("NewBucketURL is %v, want %v", got, want)
146	}
147}
148
149func TestNewBaseURL(t *testing.T) {
150	bu := "https://test-1253846586.cos.ap-beijing.myqcloud.com"
151	got, _ := NewBaseURL(bu)
152	if got.BucketURL.String() != bu {
153		t.Errorf("bucketURL want %s, but got %s", bu, got.BucketURL.String())
154	}
155	if got.ServiceURL.String() != defaultServiceBaseURL {
156		t.Errorf("serviceURL want %s, but got %s", defaultServiceBaseURL, got.ServiceURL.String())
157	}
158}
159
160func TestClient_doAPI(t *testing.T) {
161	setup()
162	defer teardown()
163
164}
165
166func TestNewAuthTime(t *testing.T) {
167	a := NewAuthTime(time.Hour)
168	if a.SignStartTime != a.KeyStartTime ||
169		a.SignEndTime != a.SignEndTime ||
170		a.SignStartTime.Add(time.Hour) != a.SignEndTime {
171		t.Errorf("NewAuthTime request got %+v is not valid", a)
172	}
173}
174
175type traceCloser struct {
176	io.Reader
177	Called bool
178}
179
180func (t traceCloser) Close() error {
181	t.Called = true
182	return nil
183}
184
185func newTraceCloser(r io.Reader) traceCloser {
186	return traceCloser{r, false}
187}
188
189func Test_doAPI_copy_body(t *testing.T) {
190	setup()
191	defer teardown()
192
193	mux.HandleFunc("/test_down", func(w http.ResponseWriter, r *http.Request) {
194		fmt.Fprint(w, `test`)
195	})
196
197	w := bytes.NewBuffer([]byte{})
198	resp, err := client.send(context.TODO(), &sendOptions{
199		baseURL: client.BaseURL.ServiceURL,
200		uri:     "/test_down",
201		method:  "GET",
202		result:  w,
203	})
204
205	if err != nil {
206		t.Errorf("Expected error == nil, got %+v", err)
207	}
208	b, _ := ioutil.ReadAll(resp.Body)
209	if len(b) != 0 || string(w.Bytes()) != "test" {
210		t.Errorf(
211			"Expected body was copy and close, got %+v, %+v",
212			string(b), string(w.Bytes()))
213	}
214}
215
216func Test_Response_header_method(t *testing.T) {
217	setup()
218	defer teardown()
219	reqID := "NTk0NTRjZjZfNTViMjM1XzlkMV9hZTZh"
220	traceID := "OGVmYzZiMmQzYjA2OWNhODk0NTRkMTBiOWVmMDAxODc0OWRkZjk0ZDM1NmI1M2E2MTRlY2MzZDhmNmI5MWI1OTBjYzE2MjAxN2M1MzJiOTdkZjMxMDVlYTZjN2FiMmI0NTk3NWFiNjAyMzdlM2RlMmVmOGNiNWIxYjYwNDFhYmQ="
221	objType := "normal"
222	storageCls := "STANDARD"
223	versionID := "xxx-v1" // ?
224	encryption := "AES256"
225
226	mux.HandleFunc("/test_down", func(w http.ResponseWriter, r *http.Request) {
227		w.Header().Set(xCosRequestID, reqID)
228		w.Header().Set(xCosTraceID, traceID)
229		w.Header().Set(xCosObjectType, objType)
230		w.Header().Set(xCosStorageClass, storageCls)
231		w.Header().Set(xCosVersionID, versionID)
232		w.Header().Set(xCosServerSideEncryption, encryption)
233		w.Header().Add("x-cos-meta-1", "1")
234		w.Header().Add("x-cos-meta-1", "11")
235		w.Header().Add("x-cos-meta-2", "2")
236		w.Header().Add("x-cos-meta-2", "22")
237		w.Header().Add("x-cos-meta-3", "33")
238		fmt.Fprint(w, `test`)
239	})
240
241	w := bytes.NewBuffer([]byte{})
242	resp, err := client.send(context.TODO(), &sendOptions{
243		baseURL: client.BaseURL.ServiceURL,
244		uri:     "/test_down",
245		method:  "GET",
246		result:  w,
247	})
248
249	if err != nil {
250		t.Errorf("Expected error == nil, got %+v", err)
251	}
252	b, _ := ioutil.ReadAll(resp.Body)
253	if len(b) != 0 || string(w.Bytes()) != "test" {
254		t.Errorf(
255			"Expected body was copy and close, got %+v, %+v",
256			string(b), string(w.Bytes()))
257	}
258	h := resp.MetaHeaders()
259	keys := []string{}
260	for k := range h {
261		keys = append(keys, strings.ToLower(k))
262	}
263	sort.Strings(keys)
264	if resp.RequestID() != reqID ||
265		resp.TraceID() != traceID ||
266		resp.ObjectType() != objType ||
267		resp.StorageClass() != storageCls ||
268		resp.VersionID() != versionID ||
269		resp.ServerSideEncryption() != encryption ||
270		!reflect.DeepEqual(keys,
271			[]string{"x-cos-meta-1", "x-cos-meta-2", "x-cos-meta-3"}) {
272		t.Errorf("result of response header method is not expected")
273	}
274	v1 := h[textproto.CanonicalMIMEHeaderKey("x-cos-meta-1")]
275	sort.Strings(v1)
276	v2 := h[textproto.CanonicalMIMEHeaderKey("x-cos-meta-2")]
277	sort.Strings(v2)
278	v3 := h[textproto.CanonicalMIMEHeaderKey("x-cos-meta-3")]
279	sort.Strings(v3)
280	if !reflect.DeepEqual(v1,
281		[]string{"1", "11"}) ||
282		!reflect.DeepEqual(v2,
283			[]string{"2", "22"}) ||
284		!reflect.DeepEqual(v3,
285			[]string{"33"}) {
286		t.Errorf("result of response meta headers is not expected, %s, %s, %s",
287			v1, v2, v3)
288	}
289}
290