1/*
2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
3 * Copyright 2017-2020 MinIO, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package minio
19
20import (
21	"bytes"
22	"context"
23	"log"
24	"net/http"
25	"os"
26	"strconv"
27	"testing"
28	"time"
29
30	"math/rand"
31
32	"github.com/minio/minio-go/v7/pkg/credentials"
33)
34
35const (
36	serverEndpoint = "SERVER_ENDPOINT"
37	accessKey      = "ACCESS_KEY"
38	secretKey      = "SECRET_KEY"
39	enableSecurity = "ENABLE_HTTPS"
40)
41
42// Tests for Core GetObject() function.
43func TestGetObjectCore(t *testing.T) {
44	if testing.Short() {
45		t.Skip("skipping functional tests for the short runs")
46	}
47
48	// Seed random based on current time.
49	rand.Seed(time.Now().Unix())
50
51	// Instantiate new minio core client object.
52	c, err := NewCore(
53		os.Getenv(serverEndpoint),
54		&Options{
55			Creds:  credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
56			Secure: mustParseBool(os.Getenv(enableSecurity)),
57		})
58	if err != nil {
59		t.Fatal("Error:", err)
60	}
61
62	// Enable tracing, write to stderr.
63	// c.TraceOn(os.Stderr)
64
65	// Set user agent.
66	c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
67
68	// Generate a new random bucket name.
69	bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
70
71	// Make a new bucket.
72	err = c.MakeBucket(context.Background(), bucketName, MakeBucketOptions{Region: "us-east-1"})
73	if err != nil {
74		t.Fatal("Error:", err, bucketName)
75	}
76
77	// Generate data more than 32K
78	buf := bytes.Repeat([]byte("3"), rand.Intn(1<<20)+32*1024)
79
80	// Save the data
81	objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
82	_, err = c.Client.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), PutObjectOptions{
83		ContentType: "binary/octet-stream",
84	})
85	if err != nil {
86		t.Fatal("Error:", err, bucketName, objectName)
87	}
88
89	st, err := c.Client.StatObject(context.Background(), bucketName, objectName, StatObjectOptions{})
90	if err != nil {
91		t.Fatal("Stat error:", err, bucketName, objectName)
92	}
93	if st.Size != int64(len(buf)) {
94		t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size)
95	}
96
97	offset := int64(2048)
98
99	// read directly
100	buf1 := make([]byte, 512)
101	buf2 := make([]byte, 512)
102	buf3 := make([]byte, st.Size)
103	buf4 := make([]byte, 1)
104
105	opts := GetObjectOptions{}
106	opts.SetRange(offset, offset+int64(len(buf1))-1)
107	reader, objectInfo, _, err := c.GetObject(context.Background(), bucketName, objectName, opts)
108	if err != nil {
109		t.Fatal(err)
110	}
111	m, err := readFull(reader, buf1)
112	reader.Close()
113	if err != nil {
114		t.Fatal(err)
115	}
116
117	if objectInfo.Size != int64(m) {
118		t.Fatalf("Error: GetObject read shorter bytes before reaching EOF, want %v, got %v\n", objectInfo.Size, m)
119	}
120	if !bytes.Equal(buf1, buf[offset:offset+512]) {
121		t.Fatal("Error: Incorrect read between two GetObject from same offset.")
122	}
123	offset += 512
124
125	opts.SetRange(offset, offset+int64(len(buf2))-1)
126	reader, objectInfo, _, err = c.GetObject(context.Background(), bucketName, objectName, opts)
127	if err != nil {
128		t.Fatal(err)
129	}
130
131	m, err = readFull(reader, buf2)
132	reader.Close()
133	if err != nil {
134		t.Fatal(err)
135	}
136
137	if objectInfo.Size != int64(m) {
138		t.Fatalf("Error: GetObject read shorter bytes before reaching EOF, want %v, got %v\n", objectInfo.Size, m)
139	}
140	if !bytes.Equal(buf2, buf[offset:offset+512]) {
141		t.Fatal("Error: Incorrect read between two GetObject from same offset.")
142	}
143
144	opts.SetRange(0, int64(len(buf3)))
145	reader, objectInfo, _, err = c.GetObject(context.Background(), bucketName, objectName, opts)
146	if err != nil {
147		t.Fatal(err)
148	}
149
150	m, err = readFull(reader, buf3)
151	if err != nil {
152		reader.Close()
153		t.Fatal(err)
154	}
155	reader.Close()
156
157	if objectInfo.Size != int64(m) {
158		t.Fatalf("Error: GetObject read shorter bytes before reaching EOF, want %v, got %v\n", objectInfo.Size, m)
159	}
160	if !bytes.Equal(buf3, buf) {
161		t.Fatal("Error: Incorrect data read in GetObject, than what was previously upoaded.")
162	}
163
164	opts = GetObjectOptions{}
165	opts.SetMatchETag("etag")
166	_, _, _, err = c.GetObject(context.Background(), bucketName, objectName, opts)
167	if err == nil {
168		t.Fatal("Unexpected GetObject should fail with mismatching etags")
169	}
170	if errResp := ToErrorResponse(err); errResp.Code != "PreconditionFailed" {
171		t.Fatalf("Expected \"PreconditionFailed\" as code, got %s instead", errResp.Code)
172	}
173
174	opts = GetObjectOptions{}
175	opts.SetMatchETagExcept("etag")
176	reader, objectInfo, _, err = c.GetObject(context.Background(), bucketName, objectName, opts)
177	if err != nil {
178		t.Fatal(err)
179	}
180
181	m, err = readFull(reader, buf3)
182	reader.Close()
183	if err != nil {
184		t.Fatal(err)
185	}
186
187	if objectInfo.Size != int64(m) {
188		t.Fatalf("Error: GetObject read shorter bytes before reaching EOF, want %v, got %v\n", objectInfo.Size, m)
189	}
190	if !bytes.Equal(buf3, buf) {
191		t.Fatal("Error: Incorrect data read in GetObject, than what was previously upoaded.")
192	}
193
194	opts = GetObjectOptions{}
195	opts.SetRange(0, 0)
196	reader, objectInfo, _, err = c.GetObject(context.Background(), bucketName, objectName, opts)
197	if err != nil {
198		t.Fatal(err)
199	}
200
201	m, err = readFull(reader, buf4)
202	reader.Close()
203	if err != nil {
204		t.Fatal(err)
205	}
206
207	if objectInfo.Size != int64(m) {
208		t.Fatalf("Error: GetObject read shorter bytes before reaching EOF, want %v, got %v\n", objectInfo.Size, m)
209	}
210
211	opts = GetObjectOptions{}
212	opts.SetRange(offset, offset+int64(len(buf2))-1)
213	contentLength := len(buf2)
214	var header http.Header
215	_, _, header, err = c.GetObject(context.Background(), bucketName, objectName, opts)
216	if err != nil {
217		t.Fatal(err)
218	}
219
220	contentLengthValue, err := strconv.Atoi(header.Get("Content-Length"))
221	if err != nil {
222		t.Fatal("Error: ", err)
223	}
224	if contentLength != contentLengthValue {
225		t.Fatalf("Error: Content Length in response header %v, not equal to set content length %v\n", contentLengthValue, contentLength)
226	}
227
228	err = c.RemoveObject(context.Background(), bucketName, objectName, RemoveObjectOptions{})
229	if err != nil {
230		t.Fatal("Error: ", err)
231	}
232	err = c.RemoveBucket(context.Background(), bucketName)
233	if err != nil {
234		t.Fatal("Error:", err)
235	}
236}
237
238// Tests GetObject to return Content-Encoding properly set
239// and overrides any auto decoding.
240func TestGetObjectContentEncoding(t *testing.T) {
241	if testing.Short() {
242		t.Skip("skipping functional tests for the short runs")
243	}
244
245	// Seed random based on current time.
246	rand.Seed(time.Now().Unix())
247
248	// Instantiate new minio core client object.
249	c, err := NewCore(
250		os.Getenv(serverEndpoint),
251		&Options{
252			Creds:  credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
253			Secure: mustParseBool(os.Getenv(enableSecurity)),
254		})
255	if err != nil {
256		t.Fatal("Error:", err)
257	}
258
259	// Enable tracing, write to stderr.
260	// c.TraceOn(os.Stderr)
261
262	// Set user agent.
263	c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
264
265	// Generate a new random bucket name.
266	bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
267
268	// Make a new bucket.
269	err = c.MakeBucket(context.Background(), bucketName, MakeBucketOptions{Region: "us-east-1"})
270	if err != nil {
271		t.Fatal("Error:", err, bucketName)
272	}
273
274	// Generate data more than 32K
275	buf := bytes.Repeat([]byte("3"), rand.Intn(1<<20)+32*1024)
276
277	// Save the data
278	objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
279	_, err = c.Client.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), PutObjectOptions{
280		ContentEncoding: "gzip",
281	})
282	if err != nil {
283		t.Fatal("Error:", err, bucketName, objectName)
284	}
285
286	rwc, objInfo, _, err := c.GetObject(context.Background(), bucketName, objectName, GetObjectOptions{})
287	if err != nil {
288		t.Fatalf("Error: %v", err)
289	}
290	rwc.Close()
291	if objInfo.Size != int64(len(buf)) {
292		t.Fatalf("Unexpected size of the object %v, expected %v", objInfo.Size, len(buf))
293	}
294	value, ok := objInfo.Metadata["Content-Encoding"]
295	if !ok {
296		t.Fatalf("Expected Content-Encoding metadata to be set.")
297	}
298	if value[0] != "gzip" {
299		t.Fatalf("Unexpected content-encoding found, want gzip, got %v", value)
300	}
301
302	err = c.RemoveObject(context.Background(), bucketName, objectName, RemoveObjectOptions{})
303	if err != nil {
304		t.Fatal("Error: ", err)
305	}
306	err = c.RemoveBucket(context.Background(), bucketName)
307	if err != nil {
308		t.Fatal("Error:", err)
309	}
310}
311
312// Tests get bucket policy core API.
313func TestGetBucketPolicy(t *testing.T) {
314	if testing.Short() {
315		t.Skip("skipping functional tests for short runs")
316	}
317
318	// Seed random based on current time.
319	rand.Seed(time.Now().Unix())
320
321	// Instantiate new minio client object.
322	c, err := NewCore(
323		os.Getenv(serverEndpoint),
324		&Options{
325			Creds:  credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
326			Secure: mustParseBool(os.Getenv(enableSecurity)),
327		})
328	if err != nil {
329		t.Fatal("Error:", err)
330	}
331
332	// Enable to debug
333	// c.TraceOn(os.Stderr)
334
335	// Set user agent.
336	c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
337
338	// Generate a new random bucket name.
339	bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
340
341	// Make a new bucket.
342	err = c.MakeBucket(context.Background(), bucketName, MakeBucketOptions{Region: "us-east-1"})
343	if err != nil {
344		t.Fatal("Error:", err, bucketName)
345	}
346
347	// Verify if bucket exits and you have access.
348	var exists bool
349	exists, err = c.BucketExists(context.Background(), bucketName)
350	if err != nil {
351		t.Fatal("Error:", err, bucketName)
352	}
353	if !exists {
354		t.Fatal("Error: could not find ", bucketName)
355	}
356
357	// Asserting the default bucket policy.
358	bucketPolicy, err := c.GetBucketPolicy(context.Background(), bucketName)
359	if err != nil {
360		errResp := ToErrorResponse(err)
361		if errResp.Code != "NoSuchBucketPolicy" {
362			t.Error("Error:", err, bucketName)
363		}
364	}
365	if bucketPolicy != "" {
366		t.Errorf("Bucket policy expected %#v, got %#v", "", bucketPolicy)
367	}
368
369	err = c.RemoveBucket(context.Background(), bucketName)
370	if err != nil {
371		t.Fatal("Error:", err)
372	}
373}
374
375// Tests Core CopyObject API implementation.
376func TestCoreCopyObject(t *testing.T) {
377	if testing.Short() {
378		t.Skip("skipping functional tests for short runs")
379	}
380
381	// Seed random based on current time.
382	rand.Seed(time.Now().Unix())
383
384	// Instantiate new minio client object.
385	c, err := NewCore(
386		os.Getenv(serverEndpoint),
387		&Options{
388			Creds:  credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
389			Secure: mustParseBool(os.Getenv(enableSecurity)),
390		})
391	if err != nil {
392		t.Fatal("Error:", err)
393	}
394
395	// Enable tracing, write to stderr.
396	// c.TraceOn(os.Stderr)
397
398	// Set user agent.
399	c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
400
401	// Generate a new random bucket name.
402	bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
403
404	// Make a new bucket.
405	err = c.MakeBucket(context.Background(), bucketName, MakeBucketOptions{Region: "us-east-1"})
406	if err != nil {
407		t.Fatal("Error:", err, bucketName)
408	}
409
410	buf := bytes.Repeat([]byte("a"), 32*1024)
411
412	// Save the data
413	objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
414
415	putopts := PutObjectOptions{
416		UserMetadata: map[string]string{
417			"Content-Type": "binary/octet-stream",
418		},
419	}
420	uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", putopts)
421	if err != nil {
422		t.Fatal("Error:", err, bucketName, objectName)
423	}
424
425	st, err := c.StatObject(context.Background(), bucketName, objectName, StatObjectOptions{})
426	if err != nil {
427		t.Fatal("Error:", err, bucketName, objectName)
428	}
429
430	if st.Size != int64(len(buf)) {
431		t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size)
432	}
433
434	destBucketName := bucketName
435	destObjectName := objectName + "-dest"
436
437	cuploadInfo, err := c.CopyObject(context.Background(), bucketName, objectName, destBucketName, destObjectName, map[string]string{
438		"X-Amz-Metadata-Directive": "REPLACE",
439		"Content-Type":             "application/javascript",
440	}, CopySrcOptions{}, PutObjectOptions{})
441	if err != nil {
442		t.Fatal("Error:", err, bucketName, objectName, destBucketName, destObjectName)
443	}
444	if cuploadInfo.ETag != uploadInfo.ETag {
445		t.Fatalf("Error: expected etag to be same as source object %s, but found different etag %s", uploadInfo.ETag, cuploadInfo.ETag)
446	}
447
448	// Attempt to read from destBucketName and object name.
449	r, err := c.Client.GetObject(context.Background(), destBucketName, destObjectName, GetObjectOptions{})
450	if err != nil {
451		t.Fatal("Error:", err, bucketName, objectName)
452	}
453
454	st, err = r.Stat()
455	if err != nil {
456		t.Fatal("Error:", err, bucketName, objectName)
457	}
458
459	if st.Size != int64(len(buf)) {
460		t.Fatalf("Error: number of bytes in stat does not match, want %v, got %v\n",
461			len(buf), st.Size)
462	}
463
464	if st.ContentType != "application/javascript" {
465		t.Fatalf("Error: Content types don't match, expected: application/javascript, found: %+v\n", st.ContentType)
466	}
467
468	if st.ETag != uploadInfo.ETag {
469		t.Fatalf("Error: expected etag to be same as source object %s, but found different etag :%s", uploadInfo.ETag, st.ETag)
470	}
471
472	if err := r.Close(); err != nil {
473		t.Fatal("Error:", err)
474	}
475
476	if err := r.Close(); err == nil {
477		t.Fatal("Error: object is already closed, should return error")
478	}
479
480	err = c.RemoveObject(context.Background(), bucketName, objectName, RemoveObjectOptions{})
481	if err != nil {
482		t.Fatal("Error: ", err)
483	}
484
485	err = c.RemoveObject(context.Background(), destBucketName, destObjectName, RemoveObjectOptions{})
486	if err != nil {
487		t.Fatal("Error: ", err)
488	}
489
490	err = c.RemoveBucket(context.Background(), bucketName)
491	if err != nil {
492		t.Fatal("Error:", err)
493	}
494
495	// Do not need to remove destBucketName its same as bucketName.
496}
497
498// Test Core CopyObjectPart implementation
499func TestCoreCopyObjectPart(t *testing.T) {
500	if testing.Short() {
501		t.Skip("skipping functional tests for short runs")
502	}
503
504	// Seed random based on current time.
505	rand.Seed(time.Now().Unix())
506
507	// Instantiate new minio client object.
508	c, err := NewCore(
509		os.Getenv(serverEndpoint),
510		&Options{
511			Creds:  credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
512			Secure: mustParseBool(os.Getenv(enableSecurity)),
513		})
514	if err != nil {
515		t.Fatal("Error:", err)
516	}
517
518	// Enable tracing, write to stderr.
519	// c.TraceOn(os.Stderr)
520
521	// Set user agent.
522	c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
523
524	// Generate a new random bucket name.
525	bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
526
527	// Make a new bucket.
528	err = c.MakeBucket(context.Background(), bucketName, MakeBucketOptions{Region: "us-east-1"})
529	if err != nil {
530		t.Fatal("Error:", err, bucketName)
531	}
532
533	// Make a buffer with 5MB of data
534	buf := bytes.Repeat([]byte("abcde"), 1024*1024)
535	metadata := map[string]string{
536		"Content-Type": "binary/octet-stream",
537	}
538	putopts := PutObjectOptions{
539		UserMetadata: metadata,
540	}
541	// Save the data
542	objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
543	_, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", putopts)
544	if err != nil {
545		t.Fatal("Error:", err, bucketName, objectName)
546	}
547
548	st, err := c.StatObject(context.Background(), bucketName, objectName, StatObjectOptions{})
549	if err != nil {
550		t.Fatal("Error:", err, bucketName, objectName)
551	}
552
553	if st.Size != int64(len(buf)) {
554		t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size)
555	}
556
557	destBucketName := bucketName
558	destObjectName := objectName + "-dest"
559
560	uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, PutObjectOptions{})
561	if err != nil {
562		t.Fatal("Error:", err, bucketName, objectName)
563	}
564
565	// Content of the destination object will be two copies of
566	// `objectName` concatenated, followed by first byte of
567	// `objectName`.
568
569	// First of three parts
570	fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, nil)
571	if err != nil {
572		t.Fatal("Error:", err, destBucketName, destObjectName)
573	}
574
575	// Second of three parts
576	sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, nil)
577	if err != nil {
578		t.Fatal("Error:", err, destBucketName, destObjectName)
579	}
580
581	// Last of three parts
582	lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, nil)
583	if err != nil {
584		t.Fatal("Error:", err, destBucketName, destObjectName)
585	}
586
587	// Complete the multipart upload
588	_, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []CompletePart{fstPart, sndPart, lstPart}, PutObjectOptions{})
589	if err != nil {
590		t.Fatal("Error:", err, destBucketName, destObjectName)
591	}
592
593	// Stat the object and check its length matches
594	objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, StatObjectOptions{})
595	if err != nil {
596		t.Fatal("Error:", err, destBucketName, destObjectName)
597	}
598
599	if objInfo.Size != (5*1024*1024)*2+1 {
600		t.Fatal("Destination object has incorrect size!")
601	}
602
603	// Now we read the data back
604	getOpts := GetObjectOptions{}
605	getOpts.SetRange(0, 5*1024*1024-1)
606	r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
607	if err != nil {
608		t.Fatal("Error:", err, destBucketName, destObjectName)
609	}
610	getBuf := make([]byte, 5*1024*1024)
611	_, err = readFull(r, getBuf)
612	if err != nil {
613		t.Fatal("Error:", err, destBucketName, destObjectName)
614	}
615	if !bytes.Equal(getBuf, buf) {
616		t.Fatal("Got unexpected data in first 5MB")
617	}
618
619	getOpts.SetRange(5*1024*1024, 0)
620	r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
621	if err != nil {
622		t.Fatal("Error:", err, destBucketName, destObjectName)
623	}
624	getBuf = make([]byte, 5*1024*1024+1)
625	_, err = readFull(r, getBuf)
626	if err != nil {
627		t.Fatal("Error:", err, destBucketName, destObjectName)
628	}
629	if !bytes.Equal(getBuf[:5*1024*1024], buf) {
630		t.Fatal("Got unexpected data in second 5MB")
631	}
632	if getBuf[5*1024*1024] != buf[0] {
633		t.Fatal("Got unexpected data in last byte of copied object!")
634	}
635
636	if err := c.RemoveObject(context.Background(), destBucketName, destObjectName, RemoveObjectOptions{}); err != nil {
637		t.Fatal("Error: ", err)
638	}
639
640	if err := c.RemoveObject(context.Background(), bucketName, objectName, RemoveObjectOptions{}); err != nil {
641		t.Fatal("Error: ", err)
642	}
643
644	if err := c.RemoveBucket(context.Background(), bucketName); err != nil {
645		t.Fatal("Error: ", err)
646	}
647
648	// Do not need to remove destBucketName its same as bucketName.
649}
650
651// Test Core PutObject.
652func TestCorePutObject(t *testing.T) {
653	if testing.Short() {
654		t.Skip("skipping functional tests for short runs")
655	}
656
657	// Seed random based on current time.
658	rand.Seed(time.Now().Unix())
659
660	// Instantiate new minio client object.
661	c, err := NewCore(
662		os.Getenv(serverEndpoint),
663		&Options{
664			Creds:  credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
665			Secure: mustParseBool(os.Getenv(enableSecurity)),
666		})
667	if err != nil {
668		t.Fatal("Error:", err)
669	}
670
671	// Enable tracing, write to stderr.
672	// c.TraceOn(os.Stderr)
673
674	// Set user agent.
675	c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
676
677	// Generate a new random bucket name.
678	bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
679
680	// Make a new bucket.
681	err = c.MakeBucket(context.Background(), bucketName, MakeBucketOptions{Region: "us-east-1"})
682	if err != nil {
683		t.Fatal("Error:", err, bucketName)
684	}
685
686	buf := bytes.Repeat([]byte("a"), 32*1024)
687
688	// Save the data
689	objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
690	// Object content type
691	objectContentType := "binary/octet-stream"
692	metadata := make(map[string]string)
693	metadata["Content-Type"] = objectContentType
694	putopts := PutObjectOptions{
695		UserMetadata: metadata,
696	}
697	_, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "1B2M2Y8AsgTpgAmY7PhCfg==", "", putopts)
698	if err == nil {
699		t.Fatal("Error expected: error, got: nil(success)")
700	}
701
702	_, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", putopts)
703	if err != nil {
704		t.Fatal("Error:", err, bucketName, objectName)
705	}
706
707	// Read the data back
708	r, err := c.Client.GetObject(context.Background(), bucketName, objectName, GetObjectOptions{})
709	if err != nil {
710		t.Fatal("Error:", err, bucketName, objectName)
711	}
712
713	st, err := r.Stat()
714	if err != nil {
715		t.Fatal("Error:", err, bucketName, objectName)
716	}
717
718	if st.Size != int64(len(buf)) {
719		t.Fatalf("Error: number of bytes in stat does not match, want %v, got %v\n",
720			len(buf), st.Size)
721	}
722
723	if st.ContentType != objectContentType {
724		t.Fatalf("Error: Content types don't match, expected: %+v, found: %+v\n", objectContentType, st.ContentType)
725	}
726
727	if err := r.Close(); err != nil {
728		t.Fatal("Error:", err)
729	}
730
731	if err := r.Close(); err == nil {
732		t.Fatal("Error: object is already closed, should return error")
733	}
734
735	err = c.RemoveObject(context.Background(), bucketName, objectName, RemoveObjectOptions{})
736	if err != nil {
737		t.Fatal("Error: ", err)
738	}
739
740	err = c.RemoveBucket(context.Background(), bucketName)
741	if err != nil {
742		t.Fatal("Error:", err)
743	}
744}
745
746func TestCoreGetObjectMetadata(t *testing.T) {
747	if testing.Short() {
748		t.Skip("skipping functional tests for the short runs")
749	}
750
751	core, err := NewCore(
752		os.Getenv(serverEndpoint),
753		&Options{
754			Creds:  credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
755			Secure: mustParseBool(os.Getenv(enableSecurity)),
756		})
757	if err != nil {
758		t.Fatal(err)
759	}
760
761	// Generate a new random bucket name.
762	bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
763
764	// Make a new bucket.
765	err = core.MakeBucket(context.Background(), bucketName, MakeBucketOptions{Region: "us-east-1"})
766	if err != nil {
767		t.Fatal("Error:", err, bucketName)
768	}
769
770	metadata := map[string]string{
771		"X-Amz-Meta-Key-1": "Val-1",
772	}
773	putopts := PutObjectOptions{
774		UserMetadata: metadata,
775	}
776
777	_, err = core.PutObject(context.Background(), bucketName, "my-objectname",
778		bytes.NewReader([]byte("hello")), 5, "", "", putopts)
779	if err != nil {
780		log.Fatalln(err)
781	}
782
783	reader, objInfo, _, err := core.GetObject(context.Background(), bucketName, "my-objectname", GetObjectOptions{})
784	if err != nil {
785		log.Fatalln(err)
786	}
787	defer reader.Close()
788
789	if objInfo.Metadata.Get("X-Amz-Meta-Key-1") != "Val-1" {
790		log.Fatalln("Expected metadata to be available but wasn't")
791	}
792
793	err = core.RemoveObject(context.Background(), bucketName, "my-objectname", RemoveObjectOptions{})
794	if err != nil {
795		t.Fatal("Error: ", err)
796	}
797	err = core.RemoveBucket(context.Background(), bucketName)
798	if err != nil {
799		t.Fatal("Error:", err)
800	}
801}
802