From caee386405df2603c860bf08f6e39bc0d76663fe Mon Sep 17 00:00:00 2001 From: jojoliang Date: Wed, 28 Apr 2021 14:45:10 +0800 Subject: [PATCH] add ci guetzli && test --- batch.go | 7 - bucket_origin.go | 9 +- bucket_origin_test.go | 180 +++++++++++ bucket_policy_test.go | 121 ++++++++ ci.go | 40 +++ ci_test.go | 497 +++++++++++++++++++++++++++++++ error_test.go | 33 ++ example/CI/compression/ci_compression.go | 58 ++++ example/CI/compression/guetzli.go | 68 +++++ object_test.go | 173 ++++++++++- 10 files changed, 1164 insertions(+), 22 deletions(-) create mode 100644 bucket_origin_test.go create mode 100644 bucket_policy_test.go create mode 100644 ci_test.go create mode 100644 example/CI/compression/ci_compression.go create mode 100644 example/CI/compression/guetzli.go 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 +
+
+ +
+ exampleHeaderKey +
+
+
+ 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 + + + + + format.jpg + example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg + PNG + 103 + 99 + 21351 + 100 + "8894dbe5e3ebfaf761e39b9d619c28f3327b8d85" + + +`) + }) + + 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 + + + + + format.jpg + example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg + PNG + 103 + 99 + 21351 + 100 + "8894dbe5e3ebfaf761e39b9d619c28f3327b8d85" + + +`) + }) + + 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 + + + + + format.jpg + example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg + PNG + 103 + 99 + 21351 + 100 + "8894dbe5e3ebfaf761e39b9d619c28f3327b8d85" + + +`) + }) + + 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) + } + +}