diff --git a/batch.go b/batch.go
index ea10c19..3af9817 100644
--- a/batch.go
+++ b/batch.go
@@ -113,13 +113,6 @@ type BatchCreateJobResult struct {
JobId string `xml:"JobId,omitempty"`
}
-func processETag(opt *BatchCreateJobOptions) *BatchCreateJobOptions {
- if opt != nil && opt.Manifest != nil && opt.Manifest.Location != nil {
- opt.Manifest.Location.ETag = "" + opt.Manifest.Location.ETag + ""
- }
- return opt
-}
-
func (s *BatchService) CreateJob(ctx context.Context, opt *BatchCreateJobOptions, headers *BatchRequestHeaders) (*BatchCreateJobResult, *Response, error) {
var res BatchCreateJobResult
sendOpt := sendOptions{
diff --git a/bucket_origin.go b/bucket_origin.go
index 805440e..905fe43 100644
--- a/bucket_origin.go
+++ b/bucket_origin.go
@@ -12,10 +12,11 @@ type BucketPutOriginOptions struct {
}
type BucketOriginRule struct {
- OriginType string `xml:"OriginType"`
- OriginCondition *BucketOriginCondition `xml:"OriginCondition"`
- OriginParameter *BucketOriginParameter `xml:"OriginParameter"`
- OriginInfo *BucketOriginInfo `xml:"OriginInfo"`
+ RulePriority int `xml:"RulePriority,omitempty"`
+ OriginType string `xml:"OriginType,omitempty"`
+ OriginCondition *BucketOriginCondition `xml:"OriginCondition,omitempty"`
+ OriginParameter *BucketOriginParameter `xml:"OriginParameter,omitempty"`
+ OriginInfo *BucketOriginInfo `xml:"OriginInfo,omitempty"`
}
type BucketOriginCondition struct {
diff --git a/bucket_origin_test.go b/bucket_origin_test.go
new file mode 100644
index 0000000..cd10e59
--- /dev/null
+++ b/bucket_origin_test.go
@@ -0,0 +1,180 @@
+package cos
+
+import (
+ "context"
+ "encoding/xml"
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+func TestBucketService_GetOrigin(t *testing.T) {
+ setup()
+ defer teardown()
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ vs := values{
+ "origin": "",
+ }
+ testFormValues(t, r, vs)
+ fmt.Fprint(w, `
+
+ 1
+ Mirror
+
+ 404
+
+
+
+ HTTP
+ true
+
+
+
+ x-cos
+ exampleHeader
+
+
+
+
+
+
+ true
+ 302
+
+
+
+ examplebucket-1250000000.cos.ap-shanghai.myqcloud.com
+
+
+
+
+ `)
+ })
+
+ res, _, err := client.Bucket.GetOrigin(context.Background())
+ if err != nil {
+ t.Fatalf("Bucket.GetOrigin returned error %v", err)
+ }
+
+ want := &BucketGetOriginResult{
+ XMLName: xml.Name{Local: "OriginConfiguration"},
+ Rule: []BucketOriginRule{
+ {
+ OriginType: "Mirror",
+ RulePriority: 1,
+ OriginCondition: &BucketOriginCondition{
+ HTTPStatusCode: "404",
+ },
+ OriginParameter: &BucketOriginParameter{
+ Protocol: "HTTP",
+ FollowQueryString: true,
+ HttpHeader: &BucketOriginHttpHeader{
+ FollowHttpHeaders: []OriginHttpHeader{
+ {
+ Key: "exampleHeaderKey",
+ },
+ },
+ NewHttpHeaders: []OriginHttpHeader{
+ {
+ Key: "x-cos",
+ Value: "exampleHeader",
+ },
+ },
+ },
+ FollowRedirection: true,
+ HttpRedirectCode: "302",
+ },
+ OriginInfo: &BucketOriginInfo{
+ HostInfo: "examplebucket-1250000000.cos.ap-shanghai.myqcloud.com",
+ },
+ },
+ },
+ }
+
+ if !reflect.DeepEqual(res, want) {
+ t.Errorf("Bucket.GetOrigin returned %+v, want %+v", res, want)
+ }
+}
+
+func TestBucketService_PutOrigin(t *testing.T) {
+ setup()
+ defer teardown()
+
+ opt := &BucketPutOriginOptions{
+ XMLName: xml.Name{Local: "OriginConfiguration"},
+ Rule: []BucketOriginRule{
+ {
+ OriginType: "Mirror",
+ RulePriority: 1,
+ OriginCondition: &BucketOriginCondition{
+ HTTPStatusCode: "404",
+ },
+ OriginParameter: &BucketOriginParameter{
+ Protocol: "HTTP",
+ FollowQueryString: true,
+ HttpHeader: &BucketOriginHttpHeader{
+ FollowHttpHeaders: []OriginHttpHeader{
+ {
+ Key: "exampleHeaderKey",
+ },
+ },
+ NewHttpHeaders: []OriginHttpHeader{
+ {
+ Key: "x-cos",
+ Value: "exampleHeader",
+ },
+ },
+ },
+ FollowRedirection: true,
+ HttpRedirectCode: "302",
+ },
+ OriginInfo: &BucketOriginInfo{
+ HostInfo: "examplebucket-1250000000.cos.ap-shanghai.myqcloud.com",
+ },
+ },
+ },
+ }
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ vs := values{
+ "origin": "",
+ }
+ testFormValues(t, r, vs)
+
+ body := new(BucketPutOriginOptions)
+ xml.NewDecoder(r.Body).Decode(body)
+ want := opt
+ want.XMLName = xml.Name{Local: "OriginConfiguration"}
+ if !reflect.DeepEqual(body, want) {
+ t.Errorf("Bucket.PutOrigin request\n body: %+v\n, want %+v\n", body, want)
+ }
+ })
+
+ _, err := client.Bucket.PutOrigin(context.Background(), opt)
+ if err != nil {
+ t.Fatalf("Bucket.PutOrigin returned error: %v", err)
+ }
+}
+
+func TestBucketService_DeleteOrigin(t *testing.T) {
+ setup()
+ defer teardown()
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, http.MethodDelete)
+ vs := values{
+ "origin": "",
+ }
+ testFormValues(t, r, vs)
+ w.WriteHeader(http.StatusNoContent)
+ })
+ _, err := client.Bucket.DeleteOrigin(context.Background())
+ if err != nil {
+ t.Fatalf("Bucket.DeleteOrigin returned error: %v", err)
+ }
+}
diff --git a/bucket_policy_test.go b/bucket_policy_test.go
new file mode 100644
index 0000000..b6156c5
--- /dev/null
+++ b/bucket_policy_test.go
@@ -0,0 +1,121 @@
+package cos
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+func TestBucketService_GetPolicy(t *testing.T) {
+ setup()
+ defer teardown()
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ vs := values{
+ "policy": "",
+ }
+ testFormValues(t, r, vs)
+ fmt.Fprint(w, `{
+ "Statement": [
+ {
+ "Principal": {
+ "qcs": [
+ "qcs::cam::uin/100000000001:uin/100000000011"
+ ]
+ },
+ "Effect": "allow",
+ "Action": [
+ "name/cos:GetBucket"
+ ],
+ "Resource": [
+ "qcs::cos:ap-guangzhou:uid/1250000000:examplebucket-1250000000/*"
+ ]
+ }
+ ],
+ "version": "2.0"
+ }`)
+ })
+
+ res, _, err := client.Bucket.GetPolicy(context.Background())
+ if err != nil {
+ t.Fatalf("Bucket.GetPolicy returned error %v", err)
+ }
+
+ want := &BucketGetPolicyResult{
+ Statement: []BucketStatement{
+ {
+ Principal: map[string][]string{
+ "qcs": []string{"qcs::cam::uin/100000000001:uin/100000000011"},
+ },
+ Effect: "allow",
+ Action: []string{"name/cos:GetBucket"},
+ Resource: []string{"qcs::cos:ap-guangzhou:uid/1250000000:examplebucket-1250000000/*"},
+ },
+ },
+ Version: "2.0",
+ }
+
+ if !reflect.DeepEqual(res, want) {
+ t.Errorf("Bucket.GetPolicy returned %+v, want %+v", res, want)
+ }
+}
+
+func TestBucketService_PutPolicy(t *testing.T) {
+ setup()
+ defer teardown()
+
+ opt := &BucketPutPolicyOptions{
+ Statement: []BucketStatement{
+ {
+ Principal: map[string][]string{
+ "qcs": []string{"qcs::cam::uin/100000000001:uin/100000000011"},
+ },
+ Effect: "allow",
+ Action: []string{"name/cos:GetBucket"},
+ Resource: []string{"qcs::cos:ap-guangzhou:uid/1250000000:examplebucket-1250000000/*"},
+ },
+ },
+ Version: "2.0",
+ }
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ vs := values{
+ "policy": "",
+ }
+ testFormValues(t, r, vs)
+
+ body := new(BucketPutPolicyOptions)
+ json.NewDecoder(r.Body).Decode(body)
+ want := opt
+ if !reflect.DeepEqual(body, want) {
+ t.Errorf("Bucket.PutPolicy request\n body: %+v\n, want %+v\n", body, want)
+ }
+ })
+
+ _, err := client.Bucket.PutPolicy(context.Background(), opt)
+ if err != nil {
+ t.Fatalf("Bucket.PutPolicy returned error: %v", err)
+ }
+}
+
+func TestBucketService_DeletePolicy(t *testing.T) {
+ setup()
+ defer teardown()
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, http.MethodDelete)
+ vs := values{
+ "policy": "",
+ }
+ testFormValues(t, r, vs)
+ w.WriteHeader(http.StatusNoContent)
+ })
+ _, err := client.Bucket.DeletePolicy(context.Background())
+ if err != nil {
+ t.Fatalf("Bucket.DeletePolicy returned error: %v", err)
+ }
+}
diff --git a/ci.go b/ci.go
index 9632175..3958b0a 100644
--- a/ci.go
+++ b/ci.go
@@ -446,3 +446,43 @@ func (s *CIService) GenerateQRcodeToFile(ctx context.Context, filePath string, o
return res, resp, err
}
+
+// 开通 Guetzli 压缩 https://cloud.tencent.com/document/product/460/30112
+func (s *CIService) PutGuetzli(ctx context.Context) (*Response, error) {
+ sendOpt := &sendOptions{
+ baseURL: s.client.BaseURL.CIURL,
+ uri: "/?guetzli",
+ method: http.MethodPut,
+ }
+ resp, err := s.client.send(ctx, sendOpt)
+ return resp, err
+}
+
+type GetGuetzliResult struct {
+ XMLName xml.Name `xml:"GuetzliStatus"`
+ GuetzliStatus string `xml:",chardata"`
+}
+
+// 查询 Guetzli 状态 https://cloud.tencent.com/document/product/460/30111
+func (s *CIService) GetGuetzli(ctx context.Context) (*GetGuetzliResult, *Response, error) {
+ var res GetGuetzliResult
+ sendOpt := &sendOptions{
+ baseURL: s.client.BaseURL.CIURL,
+ uri: "/?guetzli",
+ method: http.MethodGet,
+ result: &res,
+ }
+ resp, err := s.client.send(ctx, sendOpt)
+ return &res, resp, err
+}
+
+// 关闭 Guetzli 压缩 https://cloud.tencent.com/document/product/460/30113
+func (s *CIService) DeleteGuetzli(ctx context.Context) (*Response, error) {
+ sendOpt := &sendOptions{
+ baseURL: s.client.BaseURL.CIURL,
+ uri: "/?guetzli",
+ method: http.MethodDelete,
+ }
+ resp, err := s.client.send(ctx, sendOpt)
+ return resp, err
+}
diff --git a/ci_test.go b/ci_test.go
new file mode 100644
index 0000000..5cc1a82
--- /dev/null
+++ b/ci_test.go
@@ -0,0 +1,497 @@
+package cos
+
+import (
+ "bytes"
+ "context"
+ "crypto/rand"
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "hash/crc64"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "reflect"
+ "strconv"
+ "testing"
+ "time"
+)
+
+func TestCIService_EncodePicOperations(t *testing.T) {
+ opt := &PicOperations{
+ IsPicInfo: 1,
+ Rules: []PicOperationsRules{
+ {
+ FileId: "example.jpg",
+ Rule: "imageView2/format/png",
+ },
+ },
+ }
+ res := EncodePicOperations(opt)
+ jsonStr := `{"is_pic_info":1,"rules":[{"fileid":"example.jpg","rule":"imageView2/format/png"}]}`
+ if jsonStr != res {
+ t.Fatalf("EncodePicOperations Failed, returned:%v, want:%v", res, jsonStr)
+ }
+}
+
+func TestCIService_ImageProcess(t *testing.T) {
+ setup()
+ defer teardown()
+ name := "test.jpg"
+
+ opt := &ImageProcessOptions{
+ IsPicInfo: 1,
+ Rules: []PicOperationsRules{
+ {
+ FileId: "format.jpg",
+ Rule: "imageView2/format/png",
+ },
+ },
+ }
+ mux.HandleFunc("/test.jpg", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ vs := values{
+ "image_process": "",
+ }
+ testFormValues(t, r, vs)
+ header := r.Header.Get("Pic-Operations")
+ body := new(ImageProcessOptions)
+ err := json.Unmarshal([]byte(header), body)
+ want := opt
+ if err != nil {
+ t.Errorf("CI.ImageProcess Failed: %v", err)
+ }
+ if !reflect.DeepEqual(want, body) {
+ t.Errorf("CI.ImageProcess Failed, wanted:%v, body:%v", want, body)
+ }
+ fmt.Fprint(w, `
+
+ test.jpg
+ example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg
+ "8894dbe5e3ebfaf761e39b9d619c28f3327b8d85"
+
+ PNG
+ 103
+ 99
+ 100
+ 0xa08162
+ 0
+
+
+
+
+
+`)
+ })
+
+ want := &ImageProcessResult{
+ XMLName: xml.Name{Local: "UploadResult"},
+ OriginalInfo: &PicOriginalInfo{
+ Key: "test.jpg",
+ Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg",
+ ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
+ ImageInfo: &PicImageInfo{
+ Format: "PNG",
+ Width: 103,
+ Height: 99,
+ Quality: 100,
+ Ave: "0xa08162",
+ Orientation: 0,
+ },
+ },
+ ProcessResults: &PicProcessObject{
+ Key: "format.jpg",
+ Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg",
+ Format: "PNG",
+ Width: 103,
+ Height: 99,
+ Size: 21351,
+ Quality: 100,
+ ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
+ },
+ }
+
+ res, _, err := client.CI.ImageProcess(context.Background(), name, opt)
+ if err != nil {
+ t.Fatalf("CI.ImageProcess returned error: %v", err)
+ }
+ if !reflect.DeepEqual(res, want) {
+ t.Errorf("CI.ImageProcess failed, return:%v, want:%v", res, want)
+ }
+}
+
+func TestCIService_ImageRecognition(t *testing.T) {
+ setup()
+ defer teardown()
+ name := "test.jpg"
+
+ detectType := "porn,terrorist,politics"
+ mux.HandleFunc("/test.jpg", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ vs := values{
+ "ci-process": "sensitive-content-recognition",
+ "detect-type": "porn,terrorist,politics",
+ }
+ testFormValues(t, r, vs)
+ fmt.Fprint(w, `
+
+ 0
+ OK
+ 0
+ 0
+
+
+
+ 0
+ OK
+ 0
+ 0
+
+
+
+ 0
+ OK
+ 0
+ 0
+
+
+`)
+ })
+
+ want := &ImageRecognitionResult{
+ XMLName: xml.Name{Local: "RecognitionResult"},
+ PornInfo: &RecognitionInfo{
+ Code: 0,
+ Msg: "OK",
+ HitFlag: 0,
+ Score: 0,
+ },
+ TerroristInfo: &RecognitionInfo{
+ Code: 0,
+ Msg: "OK",
+ HitFlag: 0,
+ Score: 0,
+ },
+ PoliticsInfo: &RecognitionInfo{
+ Code: 0,
+ Msg: "OK",
+ HitFlag: 0,
+ Score: 0,
+ },
+ }
+
+ res, _, err := client.CI.ImageRecognition(context.Background(), name, detectType)
+ if err != nil {
+ t.Fatalf("CI.ImageRecognitionreturned error: %v", err)
+ }
+ if !reflect.DeepEqual(res, want) {
+ t.Errorf("CI.ImageRecognition failed, return:%v, want:%v", res, want)
+ }
+}
+
+func TestCIService_Put(t *testing.T) {
+ setup()
+ defer teardown()
+ name := "test.jpg"
+ data := make([]byte, 1024*1024*3)
+ rand.Read(data)
+
+ pic := &ImageProcessOptions{
+ IsPicInfo: 1,
+ Rules: []PicOperationsRules{
+ {
+ FileId: "format.jpg",
+ Rule: "imageView2/format/png",
+ },
+ },
+ }
+ mux.HandleFunc("/test.jpg", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ header := r.Header.Get("Pic-Operations")
+ body := new(ImageProcessOptions)
+ err := json.Unmarshal([]byte(header), body)
+ want := pic
+ if err != nil {
+ t.Errorf("CI.Put Failed: %v", err)
+ }
+ if !reflect.DeepEqual(want, body) {
+ t.Errorf("CI.Put Failed, wanted:%v, body:%v", want, body)
+ }
+ tb := crc64.MakeTable(crc64.ECMA)
+ ht := crc64.New(tb)
+ tr := TeeReader(r.Body, ht, 0, nil)
+ bs, err := ioutil.ReadAll(tr)
+ if err != nil {
+ t.Errorf("CI.Put ReadAll Failed: %v", err)
+ }
+ if bytes.Compare(bs, data) != 0 {
+ t.Errorf("CI.Put Failed, data isn't consistent")
+ }
+ crc := tr.Crc64()
+ w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
+ fmt.Fprint(w, `
+
+ test.jpg
+ example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg
+ "8894dbe5e3ebfaf761e39b9d619c28f3327b8d85"
+
+ PNG
+ 103
+ 99
+ 100
+ 0xa08162
+ 0
+
+
+
+
+
+`)
+ })
+
+ want := &ImageProcessResult{
+ XMLName: xml.Name{Local: "UploadResult"},
+ OriginalInfo: &PicOriginalInfo{
+ Key: "test.jpg",
+ Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg",
+ ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
+ ImageInfo: &PicImageInfo{
+ Format: "PNG",
+ Width: 103,
+ Height: 99,
+ Quality: 100,
+ Ave: "0xa08162",
+ Orientation: 0,
+ },
+ },
+ ProcessResults: &PicProcessObject{
+ Key: "format.jpg",
+ Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg",
+ Format: "PNG",
+ Width: 103,
+ Height: 99,
+ Size: 21351,
+ Quality: 100,
+ ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
+ },
+ }
+
+ f := bytes.NewReader(data)
+ opt := &ObjectPutOptions{
+ nil,
+ &ObjectPutHeaderOptions{
+ XOptionHeader: &http.Header{},
+ },
+ }
+ opt.XOptionHeader.Add("Pic-Operations", EncodePicOperations(pic))
+ res, _, err := client.CI.Put(context.Background(), name, f, opt)
+ if err != nil {
+ t.Fatalf("CI.Put returned error: %v", err)
+ }
+ if !reflect.DeepEqual(res, want) {
+ t.Errorf("CI.ImageProcess failed, return:%v, want:%v", res, want)
+ }
+}
+
+func TestCIService_PutFromFile(t *testing.T) {
+ setup()
+ defer teardown()
+ name := "test.jpg"
+ filePath := "test.file" + time.Now().Format(time.RFC3339)
+ newfile, err := os.Create(filePath)
+ if err != nil {
+ t.Fatalf("creat tmp file failed")
+ }
+ defer os.Remove(filePath)
+ data := make([]byte, 1024*1024*3)
+ rand.Read(data)
+ newfile.Write(data)
+ newfile.Close()
+
+ pic := &ImageProcessOptions{
+ IsPicInfo: 1,
+ Rules: []PicOperationsRules{
+ {
+ FileId: "format.jpg",
+ Rule: "imageView2/format/png",
+ },
+ },
+ }
+ mux.HandleFunc("/test.jpg", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ header := r.Header.Get("Pic-Operations")
+ body := new(ImageProcessOptions)
+ err := json.Unmarshal([]byte(header), body)
+ want := pic
+ if err != nil {
+ t.Errorf("CI.Put Failed: %v", err)
+ }
+ if !reflect.DeepEqual(want, body) {
+ t.Errorf("CI.Put Failed, wanted:%v, body:%v", want, body)
+ }
+ tb := crc64.MakeTable(crc64.ECMA)
+ ht := crc64.New(tb)
+ tr := TeeReader(r.Body, ht, 0, nil)
+ bs, err := ioutil.ReadAll(tr)
+ if err != nil {
+ t.Errorf("CI.Put ReadAll Failed: %v", err)
+ }
+ if bytes.Compare(bs, data) != 0 {
+ t.Errorf("CI.Put Failed, data isn't consistent")
+ }
+ crc := tr.Crc64()
+ w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
+ fmt.Fprint(w, `
+
+ test.jpg
+ example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg
+ "8894dbe5e3ebfaf761e39b9d619c28f3327b8d85"
+
+ PNG
+ 103
+ 99
+ 100
+ 0xa08162
+ 0
+
+
+
+
+
+`)
+ })
+
+ want := &ImageProcessResult{
+ XMLName: xml.Name{Local: "UploadResult"},
+ OriginalInfo: &PicOriginalInfo{
+ Key: "test.jpg",
+ Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg",
+ ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
+ ImageInfo: &PicImageInfo{
+ Format: "PNG",
+ Width: 103,
+ Height: 99,
+ Quality: 100,
+ Ave: "0xa08162",
+ Orientation: 0,
+ },
+ },
+ ProcessResults: &PicProcessObject{
+ Key: "format.jpg",
+ Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg",
+ Format: "PNG",
+ Width: 103,
+ Height: 99,
+ Size: 21351,
+ Quality: 100,
+ ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
+ },
+ }
+
+ opt := &ObjectPutOptions{
+ nil,
+ &ObjectPutHeaderOptions{
+ XOptionHeader: &http.Header{},
+ },
+ }
+ opt.XOptionHeader.Add("Pic-Operations", EncodePicOperations(pic))
+ res, _, err := client.CI.PutFromFile(context.Background(), name, filePath, opt)
+ if err != nil {
+ t.Fatalf("CI.Put returned error: %v", err)
+ }
+ if !reflect.DeepEqual(res, want) {
+ t.Errorf("CI.ImageProcess failed, return:%v, want:%v", res, want)
+ }
+}
+
+func TestBucketService_GetGuetzli(t *testing.T) {
+ setup()
+ defer teardown()
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ vs := values{
+ "guetzli": "",
+ }
+ testFormValues(t, r, vs)
+ fmt.Fprint(w, `on`)
+ })
+
+ res, _, err := client.CI.GetGuetzli(context.Background())
+ if err != nil {
+ t.Fatalf("CI.GetGuetzli returned error %v", err)
+ }
+
+ want := &GetGuetzliResult{
+ XMLName: xml.Name{Local: "GuetzliStatus"},
+ GuetzliStatus: "on",
+ }
+
+ if !reflect.DeepEqual(res, want) {
+ t.Errorf("CI.GetGuetzli %+v, want %+v", res, want)
+ }
+}
+
+func TestBucketService_PutGuetzli(t *testing.T) {
+ setup()
+ defer teardown()
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ vs := values{
+ "guetzli": "",
+ }
+ testFormValues(t, r, vs)
+ })
+
+ _, err := client.CI.PutGuetzli(context.Background())
+ if err != nil {
+ t.Fatalf("CI.PutGuetzli returned error: %v", err)
+ }
+}
+
+func TestBucketService_DeleteGuetzli(t *testing.T) {
+ setup()
+ defer teardown()
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "DELETE")
+ vs := values{
+ "guetzli": "",
+ }
+ testFormValues(t, r, vs)
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ _, err := client.CI.DeleteGuetzli(context.Background())
+ if err != nil {
+ t.Fatalf("CI.PutGuetzli returned error: %v", err)
+ }
+}
diff --git a/error_test.go b/error_test.go
index d5709f8..9b79534 100644
--- a/error_test.go
+++ b/error_test.go
@@ -87,3 +87,36 @@ func Test_checkResponse_with_error(t *testing.T) {
}
}
+
+func Test_IsNotFoundError(t *testing.T) {
+ setup()
+ defer teardown()
+
+ mux.HandleFunc("/test_404", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, `
+
+ NoSuchKey
+ The specified key does not exist.
+ examplebucket-1250000000.cos.ap-guangzhou.myqcloud.com/test_404
+ NjA3OGY4NGFfNjJkMmMwYl8***
+ OGVmYzZiMmQzYjA2OWNh***
+`)
+ })
+
+ req, _ := http.NewRequest("GET", client.BaseURL.ServiceURL.String()+"/test_404", nil)
+ resp, _ := client.client.Do(req)
+ err := checkResponse(resp)
+
+ e, ok := IsCOSError(err)
+ if !ok {
+ t.Errorf("IsCOSError Return Failed")
+ }
+ ok = IsNotFoundError(e)
+ if !ok {
+ t.Errorf("IsNotFoundError Return Failed")
+ }
+ if e.Code != "NoSuchKey" {
+ t.Errorf("Expected NoSuchKey error, got %+v", e.Code)
+ }
+}
diff --git a/example/CI/compression/ci_compression.go b/example/CI/compression/ci_compression.go
new file mode 100644
index 0000000..c8f7253
--- /dev/null
+++ b/example/CI/compression/ci_compression.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+ "os"
+
+ "github.com/tencentyun/cos-go-sdk-v5"
+ "github.com/tencentyun/cos-go-sdk-v5/debug"
+)
+
+func log_status(err error) {
+ if err == nil {
+ return
+ }
+ if cos.IsNotFoundError(err) {
+ // WARN
+ fmt.Println("WARN: Resource is not existed")
+ } else if e, ok := cos.IsCOSError(err); ok {
+ fmt.Printf("ERROR: Code: %v\n", e.Code)
+ fmt.Printf("ERROR: Message: %v\n", e.Message)
+ fmt.Printf("ERROR: Resource: %v\n", e.Resource)
+ fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
+ // ERROR
+ } else {
+ fmt.Printf("ERROR: %v\n", err)
+ // ERROR
+ }
+}
+
+func main() {
+ u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
+ b := &cos.BaseURL{BucketURL: u}
+ c := cos.NewClient(b, &http.Client{
+ Transport: &cos.AuthorizationTransport{
+ SecretID: os.Getenv("COS_SECRETID"),
+ SecretKey: os.Getenv("COS_SECRETKEY"),
+ Transport: &debug.DebugRequestTransport{
+ RequestHeader: true,
+ // Notice when put a large file and set need the request body, might happend out of memory error.
+ RequestBody: false,
+ ResponseHeader: true,
+ ResponseBody: false,
+ },
+ },
+ })
+
+ name := "test.png"
+ filepath := "test1.jpg"
+ _, err := c.CI.GetToFile(context.Background(), name, filepath, "imageMogr2/format/tpg", nil)
+ log_status(err)
+
+ filepath = "test2.jpg"
+ _, err = c.CI.GetToFile(context.Background(), name, filepath, "imageMogr2/format/heif", nil)
+ log_status(err)
+}
diff --git a/example/CI/compression/guetzli.go b/example/CI/compression/guetzli.go
new file mode 100644
index 0000000..2fa5d84
--- /dev/null
+++ b/example/CI/compression/guetzli.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+ "os"
+ "time"
+
+ "github.com/tencentyun/cos-go-sdk-v5"
+ "github.com/tencentyun/cos-go-sdk-v5/debug"
+)
+
+func log_status(err error) {
+ if err == nil {
+ return
+ }
+ if cos.IsNotFoundError(err) {
+ // WARN
+ fmt.Println("WARN: Resource is not existed")
+ } else if e, ok := cos.IsCOSError(err); ok {
+ fmt.Printf("ERROR: Code: %v\n", e.Code)
+ fmt.Printf("ERROR: Message: %v\n", e.Message)
+ fmt.Printf("ERROR: Resource: %v\n", e.Resource)
+ fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
+ // ERROR
+ } else {
+ fmt.Printf("ERROR: %v\n", err)
+ // ERROR
+ }
+}
+
+func main() {
+ u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
+ cu, _ := url.Parse("http://test-1259654469.pic.ap-guangzhou.myqcloud.com")
+ b := &cos.BaseURL{BucketURL: u, CIURL: cu}
+ c := cos.NewClient(b, &http.Client{
+ Transport: &cos.AuthorizationTransport{
+ SecretID: os.Getenv("COS_SECRETID"),
+ SecretKey: os.Getenv("COS_SECRETKEY"),
+ Transport: &debug.DebugRequestTransport{
+ RequestHeader: true,
+ // Notice when put a large file and set need the request body, might happend out of memory error.
+ RequestBody: false,
+ ResponseHeader: true,
+ ResponseBody: true,
+ },
+ },
+ })
+
+ _, err := c.CI.PutGuetzli(context.Background())
+ log_status(err)
+ res, _, err := c.CI.GetGuetzli(context.Background())
+ log_status(err)
+ if res != nil && res.GuetzliStatus != "on" {
+ fmt.Printf("Error Status: %v\n", res.GuetzliStatus)
+ }
+ time.Sleep(time.Second * 3)
+ _, err = c.CI.DeleteGuetzli(context.Background())
+ log_status(err)
+ res, _, err = c.CI.GetGuetzli(context.Background())
+ log_status(err)
+ if res != nil && res.GuetzliStatus != "off" {
+ fmt.Printf("Error Status: %v\n", res.GuetzliStatus)
+ }
+
+}
diff --git a/object_test.go b/object_test.go
index 5f7ce37..45fc28a 100644
--- a/object_test.go
+++ b/object_test.go
@@ -25,6 +25,8 @@ func TestObjectService_Get(t *testing.T) {
setup()
defer teardown()
name := "test/hello.txt"
+ contentLength := 1024 * 1024 * 10
+ data := make([]byte, contentLength)
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
@@ -32,27 +34,70 @@ func TestObjectService_Get(t *testing.T) {
"response-content-type": "text/html",
}
testFormValues(t, r, vs)
- testHeader(t, r, "Range", "bytes=0-3")
- fmt.Fprint(w, `hello`)
+ strRange := r.Header.Get("Range")
+ slice1 := strings.Split(strRange, "=")
+ slice2 := strings.Split(slice1[1], "-")
+ start, _ := strconv.ParseInt(slice2[0], 10, 64)
+ end, _ := strconv.ParseInt(slice2[1], 10, 64)
+ io.Copy(w, bytes.NewBuffer(data[start:end+1]))
})
+ for i := 0; i < 3; i++ {
+ math_rand.Seed(time.Now().UnixNano())
+ rangeStart := math_rand.Intn(contentLength)
+ rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
+ if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
+ continue
+ }
+ opt := &ObjectGetOptions{
+ ResponseContentType: "text/html",
+ Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
+ }
+ resp, err := client.Object.Get(context.Background(), name, opt)
+ if err != nil {
+ t.Fatalf("Object.Get returned error: %v", err)
+ }
+ b, _ := ioutil.ReadAll(resp.Body)
+ if bytes.Compare(b, data[rangeStart:rangeEnd+1]) != 0 {
+ t.Errorf("Object.Get Failed")
+ }
+ }
+}
+
+func TestObjectService_GetToFile(t *testing.T) {
+ setup()
+ defer teardown()
+ name := "test/hello.txt"
+ data := make([]byte, 1024*1024*10)
+ rand.Read(data)
+
+ mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ vs := values{
+ "response-content-type": "text/html",
+ }
+ testFormValues(t, r, vs)
+ testHeader(t, r, "Range", "bytes=0-3")
+ io.Copy(w, bytes.NewReader(data))
+ })
opt := &ObjectGetOptions{
ResponseContentType: "text/html",
Range: "bytes=0-3",
}
-
- resp, err := client.Object.Get(context.Background(), name, opt)
+ filePath := "test.file" + time.Now().Format(time.RFC3339)
+ _, err := client.Object.GetToFile(context.Background(), name, filePath, opt)
if err != nil {
t.Fatalf("Object.Get returned error: %v", err)
}
-
- b, _ := ioutil.ReadAll(resp.Body)
- ref := string(b)
- want := "hello"
- if !reflect.DeepEqual(ref, want) {
- t.Errorf("Object.Get returned %+v, want %+v", ref, want)
+ defer os.Remove(filePath)
+ fd, err := os.Open(filePath)
+ if err != nil {
+ }
+ defer fd.Close()
+ bs, _ := ioutil.ReadAll(fd)
+ if bytes.Compare(bs, data) != 0 {
+ t.Errorf("Object.GetToFile data isn't consistent")
}
-
}
func TestObjectService_Put(t *testing.T) {
@@ -132,6 +177,7 @@ func TestObjectService_PutFromFile(t *testing.T) {
opt := &ObjectPutOptions{
ObjectPutHeaderOptions: &ObjectPutHeaderOptions{
ContentType: "text/html",
+ Listener: &DefaultProgressListener{},
},
ACLHeaderOptions: &ACLHeaderOptions{
XCosACL: "private",
@@ -590,3 +636,108 @@ func TestObjectService_Download(t *testing.T) {
t.Fatalf("Object.Upload returned error: %v", err)
}
}
+
+func TestObjectService_GetTagging(t *testing.T) {
+ setup()
+ defer teardown()
+
+ mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ vs := values{
+ "tagging": "",
+ }
+ testFormValues(t, r, vs)
+ fmt.Fprint(w, `
+
+
+ test_k2
+ test_v2
+
+
+ test_k3
+ test_vv
+
+
+`)
+ })
+
+ res, _, err := client.Object.GetTagging(context.Background(), "test")
+ if err != nil {
+ t.Fatalf("Object.GetTagging returned error %v", err)
+ }
+
+ want := &ObjectGetTaggingResult{
+ XMLName: xml.Name{Local: "Tagging"},
+ TagSet: []ObjectTaggingTag{
+ {"test_k2", "test_v2"},
+ {"test_k3", "test_vv"},
+ },
+ }
+
+ if !reflect.DeepEqual(res, want) {
+ t.Errorf("Object.GetTagging returned %+v, want %+v", res, want)
+ }
+}
+
+func TestObjectService_PutTagging(t *testing.T) {
+ setup()
+ defer teardown()
+
+ opt := &ObjectPutTaggingOptions{
+ TagSet: []ObjectTaggingTag{
+ {
+ Key: "test_k2",
+ Value: "test_v2",
+ },
+ {
+ Key: "test_k3",
+ Value: "test_v3",
+ },
+ },
+ }
+
+ mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
+ v := new(ObjectPutTaggingOptions)
+ xml.NewDecoder(r.Body).Decode(v)
+
+ testMethod(t, r, "PUT")
+ vs := values{
+ "tagging": "",
+ }
+ testFormValues(t, r, vs)
+
+ want := opt
+ want.XMLName = xml.Name{Local: "Tagging"}
+ if !reflect.DeepEqual(v, want) {
+ t.Errorf("Object.PutTagging request body: %+v, want %+v", v, want)
+ }
+
+ })
+
+ _, err := client.Object.PutTagging(context.Background(), "test", opt)
+ if err != nil {
+ t.Fatalf("Object.PutTagging returned error: %v", err)
+ }
+
+}
+
+func TestObjectService_DeleteTagging(t *testing.T) {
+ setup()
+ defer teardown()
+
+ mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, http.MethodDelete)
+ vs := values{
+ "tagging": "",
+ }
+ testFormValues(t, r, vs)
+
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ _, err := client.Object.DeleteTagging(context.Background(), "test")
+ if err != nil {
+ t.Fatalf("Object.DeleteTagging returned error: %v", err)
+ }
+
+}