1package testing
2
3import (
4	"bytes"
5	"crypto/md5"
6	"fmt"
7	"io"
8	"io/ioutil"
9	"net/http"
10	"strings"
11	"testing"
12	"time"
13
14	accountTesting "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing"
15	containerTesting "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/testing"
16	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
17	"github.com/gophercloud/gophercloud/pagination"
18	th "github.com/gophercloud/gophercloud/testhelper"
19	fake "github.com/gophercloud/gophercloud/testhelper/client"
20)
21
22func TestDownloadReader(t *testing.T) {
23	th.SetupHTTP()
24	defer th.TeardownHTTP()
25	HandleDownloadObjectSuccessfully(t)
26
27	response := objects.Download(fake.ServiceClient(), "testContainer", "testObject", nil)
28	defer response.Body.Close()
29
30	// Check reader
31	buf := bytes.NewBuffer(make([]byte, 0))
32	io.CopyN(buf, response.Body, 10)
33	th.CheckEquals(t, "Successful", string(buf.Bytes()))
34}
35
36func TestDownloadExtraction(t *testing.T) {
37	th.SetupHTTP()
38	defer th.TeardownHTTP()
39	HandleDownloadObjectSuccessfully(t)
40
41	response := objects.Download(fake.ServiceClient(), "testContainer", "testObject", nil)
42
43	// Check []byte extraction
44	bytes, err := response.ExtractContent()
45	th.AssertNoErr(t, err)
46	th.CheckEquals(t, "Successful download with Gophercloud", string(bytes))
47
48	expected := &objects.DownloadHeader{
49		ContentLength:     36,
50		ContentType:       "text/plain; charset=utf-8",
51		Date:              time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
52		StaticLargeObject: true,
53		LastModified:      time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
54	}
55	actual, err := response.Extract()
56	th.AssertNoErr(t, err)
57	th.CheckDeepEquals(t, expected, actual)
58}
59
60func TestDownloadWithLastModified(t *testing.T) {
61	th.SetupHTTP()
62	defer th.TeardownHTTP()
63	HandleDownloadObjectSuccessfully(t)
64
65	options1 := &objects.DownloadOpts{
66		IfUnmodifiedSince: time.Date(2009, time.November, 10, 22, 59, 59, 0, time.UTC),
67	}
68	response1 := objects.Download(fake.ServiceClient(), "testContainer", "testObject", options1)
69	_, err1 := response1.Extract()
70	th.AssertErr(t, err1)
71
72	options2 := &objects.DownloadOpts{
73		IfModifiedSince: time.Date(2009, time.November, 10, 23, 0, 1, 0, time.UTC),
74	}
75	response2 := objects.Download(fake.ServiceClient(), "testContainer", "testObject", options2)
76	content, err2 := response2.ExtractContent()
77	th.AssertNoErr(t, err2)
78	th.AssertEquals(t, len(content), 0)
79}
80
81func TestListObjectInfo(t *testing.T) {
82	th.SetupHTTP()
83	defer th.TeardownHTTP()
84	HandleListObjectsInfoSuccessfully(t)
85
86	count := 0
87	options := &objects.ListOpts{Full: true}
88	err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
89		count++
90		actual, err := objects.ExtractInfo(page)
91		th.AssertNoErr(t, err)
92
93		th.CheckDeepEquals(t, ExpectedListInfo, actual)
94
95		return true, nil
96	})
97	th.AssertNoErr(t, err)
98	th.CheckEquals(t, count, 1)
99}
100
101func TestListObjectSubdir(t *testing.T) {
102	th.SetupHTTP()
103	defer th.TeardownHTTP()
104	HandleListSubdirSuccessfully(t)
105
106	count := 0
107	options := &objects.ListOpts{Full: true, Prefix: "", Delimiter: "/"}
108	err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
109		count++
110		actual, err := objects.ExtractInfo(page)
111		th.AssertNoErr(t, err)
112
113		th.CheckDeepEquals(t, ExpectedListSubdir, actual)
114
115		return true, nil
116	})
117	th.AssertNoErr(t, err)
118	th.CheckEquals(t, count, 1)
119}
120
121func TestListObjectNames(t *testing.T) {
122	th.SetupHTTP()
123	defer th.TeardownHTTP()
124	HandleListObjectNamesSuccessfully(t)
125
126	// Check without delimiter.
127	count := 0
128	options := &objects.ListOpts{Full: false}
129	err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
130		count++
131		actual, err := objects.ExtractNames(page)
132		if err != nil {
133			t.Errorf("Failed to extract container names: %v", err)
134			return false, err
135		}
136
137		th.CheckDeepEquals(t, ExpectedListNames, actual)
138
139		return true, nil
140	})
141	th.AssertNoErr(t, err)
142	th.CheckEquals(t, count, 1)
143
144	// Check with delimiter.
145	count = 0
146	options = &objects.ListOpts{Full: false, Delimiter: "/"}
147	err = objects.List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
148		count++
149		actual, err := objects.ExtractNames(page)
150		if err != nil {
151			t.Errorf("Failed to extract container names: %v", err)
152			return false, err
153		}
154
155		th.CheckDeepEquals(t, ExpectedListNames, actual)
156
157		return true, nil
158	})
159	th.AssertNoErr(t, err)
160	th.CheckEquals(t, count, 1)
161}
162
163func TestCreateObject(t *testing.T) {
164	th.SetupHTTP()
165	defer th.TeardownHTTP()
166
167	content := "Did gyre and gimble in the wabe"
168
169	HandleCreateTextObjectSuccessfully(t, content)
170
171	options := &objects.CreateOpts{ContentType: "text/plain", Content: strings.NewReader(content)}
172	res := objects.Create(fake.ServiceClient(), "testContainer", "testObject", options)
173	th.AssertNoErr(t, res.Err)
174}
175
176func TestCreateObjectWithCacheControl(t *testing.T) {
177	th.SetupHTTP()
178	defer th.TeardownHTTP()
179
180	content := "All mimsy were the borogoves"
181
182	HandleCreateTextWithCacheControlSuccessfully(t, content)
183
184	options := &objects.CreateOpts{
185		CacheControl: `max-age="3600", public`,
186		Content:      strings.NewReader(content),
187	}
188	res := objects.Create(fake.ServiceClient(), "testContainer", "testObject", options)
189	th.AssertNoErr(t, res.Err)
190}
191
192func TestCreateObjectWithoutContentType(t *testing.T) {
193	th.SetupHTTP()
194	defer th.TeardownHTTP()
195
196	content := "The sky was the color of television, tuned to a dead channel."
197
198	HandleCreateTypelessObjectSuccessfully(t, content)
199
200	res := objects.Create(fake.ServiceClient(), "testContainer", "testObject", &objects.CreateOpts{Content: strings.NewReader(content)})
201	th.AssertNoErr(t, res.Err)
202}
203
204/*
205func TestErrorIsRaisedForChecksumMismatch(t *testing.T) {
206	th.SetupHTTP()
207	defer th.TeardownHTTP()
208
209	th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
210		w.Header().Set("ETag", "acbd18db4cc2f85cedef654fccc4a4d8")
211		w.WriteHeader(http.StatusCreated)
212	})
213
214	content := strings.NewReader("The sky was the color of television, tuned to a dead channel.")
215	res := Create(fake.ServiceClient(), "testContainer", "testObject", &CreateOpts{Content: content})
216
217	err := fmt.Errorf("Local checksum does not match API ETag header")
218	th.AssertDeepEquals(t, err, res.Err)
219}
220*/
221
222func TestCopyObject(t *testing.T) {
223	th.SetupHTTP()
224	defer th.TeardownHTTP()
225	HandleCopyObjectSuccessfully(t)
226
227	options := &objects.CopyOpts{Destination: "/newTestContainer/newTestObject"}
228	res := objects.Copy(fake.ServiceClient(), "testContainer", "testObject", options)
229	th.AssertNoErr(t, res.Err)
230}
231
232func TestDeleteObject(t *testing.T) {
233	th.SetupHTTP()
234	defer th.TeardownHTTP()
235	HandleDeleteObjectSuccessfully(t)
236
237	res := objects.Delete(fake.ServiceClient(), "testContainer", "testObject", nil)
238	th.AssertNoErr(t, res.Err)
239}
240
241func TestBulkDelete(t *testing.T) {
242	th.SetupHTTP()
243	defer th.TeardownHTTP()
244	HandleBulkDeleteSuccessfully(t)
245
246	expected := objects.BulkDeleteResponse{
247		ResponseStatus: "foo",
248		ResponseBody:   "bar",
249		NumberDeleted:  2,
250		Errors:         [][]string{},
251	}
252
253	resp, err := objects.BulkDelete(fake.ServiceClient(), "testContainer", []string{"testObject1", "testObject2"}).Extract()
254	th.AssertNoErr(t, err)
255	th.AssertDeepEquals(t, expected, *resp)
256}
257
258func TestUpateObjectMetadata(t *testing.T) {
259	th.SetupHTTP()
260	defer th.TeardownHTTP()
261	HandleUpdateObjectSuccessfully(t)
262
263	options := &objects.UpdateOpts{
264		Metadata:       map[string]string{"Gophercloud-Test": "objects"},
265		RemoveMetadata: []string{"Gophercloud-Test-Remove"},
266	}
267	res := objects.Update(fake.ServiceClient(), "testContainer", "testObject", options)
268	th.AssertNoErr(t, res.Err)
269}
270
271func TestGetObject(t *testing.T) {
272	th.SetupHTTP()
273	defer th.TeardownHTTP()
274	HandleGetObjectSuccessfully(t)
275
276	expected := map[string]string{"Gophercloud-Test": "objects"}
277	actual, err := objects.Get(fake.ServiceClient(), "testContainer", "testObject", nil).ExtractMetadata()
278	th.AssertNoErr(t, err)
279	th.CheckDeepEquals(t, expected, actual)
280
281	getOpts := objects.GetOpts{
282		Newest: true,
283	}
284	actualHeaders, err := objects.Get(fake.ServiceClient(), "testContainer", "testObject", getOpts).Extract()
285	th.AssertNoErr(t, err)
286	th.AssertEquals(t, actualHeaders.StaticLargeObject, true)
287}
288
289func TestETag(t *testing.T) {
290	content := "some example object"
291	createOpts := objects.CreateOpts{
292		Content: strings.NewReader(content),
293		NoETag:  true,
294	}
295
296	_, headers, _, err := createOpts.ToObjectCreateParams()
297	th.AssertNoErr(t, err)
298	_, ok := headers["ETag"]
299	th.AssertEquals(t, ok, false)
300
301	hash := md5.New()
302	io.WriteString(hash, content)
303	localChecksum := fmt.Sprintf("%x", hash.Sum(nil))
304
305	createOpts = objects.CreateOpts{
306		Content: strings.NewReader(content),
307		ETag:    localChecksum,
308	}
309
310	_, headers, _, err = createOpts.ToObjectCreateParams()
311	th.AssertNoErr(t, err)
312	th.AssertEquals(t, headers["ETag"], localChecksum)
313}
314
315func TestObjectCreateParamsWithoutSeek(t *testing.T) {
316	content := "I do not implement Seek()"
317	buf := bytes.NewBuffer([]byte(content))
318
319	createOpts := objects.CreateOpts{Content: buf}
320	reader, headers, _, err := createOpts.ToObjectCreateParams()
321
322	th.AssertNoErr(t, err)
323
324	_, ok := reader.(io.ReadSeeker)
325	th.AssertEquals(t, ok, true)
326
327	c, err := ioutil.ReadAll(reader)
328	th.AssertNoErr(t, err)
329
330	th.AssertEquals(t, content, string(c))
331
332	_, ok = headers["ETag"]
333	th.AssertEquals(t, true, ok)
334}
335
336func TestObjectCreateParamsWithSeek(t *testing.T) {
337	content := "I implement Seek()"
338	createOpts := objects.CreateOpts{Content: strings.NewReader(content)}
339	reader, headers, _, err := createOpts.ToObjectCreateParams()
340
341	th.AssertNoErr(t, err)
342
343	_, ok := reader.(io.ReadSeeker)
344	th.AssertEquals(t, ok, true)
345
346	c, err := ioutil.ReadAll(reader)
347	th.AssertNoErr(t, err)
348
349	th.AssertEquals(t, content, string(c))
350
351	_, ok = headers["ETag"]
352	th.AssertEquals(t, true, ok)
353}
354
355func TestCreateTempURL(t *testing.T) {
356	port := 33200
357	th.SetupHTTP()
358	th.SetupPersistentPortHTTP(t, port)
359	defer th.TeardownHTTP()
360
361	// Handle fetching of secret key inside of CreateTempURL
362	containerTesting.HandleGetContainerSuccessfully(t)
363	accountTesting.HandleGetAccountSuccessfully(t)
364	client := fake.ServiceClient()
365
366	// Append v1/ to client endpoint URL to be compliant with tempURL generator
367	client.Endpoint = client.Endpoint + "v1/"
368	tempURL, err := objects.CreateTempURL(client, "testContainer", "testObject/testFile.txt", objects.CreateTempURLOpts{
369		Method:    http.MethodGet,
370		TTL:       60,
371		Timestamp: time.Date(2020, 07, 01, 01, 12, 00, 00, time.UTC),
372	})
373
374	sig := "89be454a9c7e2e9f3f50a8441815e0b5801cba5b"
375	expiry := "1593565980"
376	expectedURL := fmt.Sprintf("http://127.0.0.1:%v/v1/testContainer/testObject/testFile.txt?temp_url_sig=%v&temp_url_expires=%v", port, sig, expiry)
377
378	th.AssertNoErr(t, err)
379	th.AssertEquals(t, expectedURL, tempURL)
380}
381