From 5fe8d16ddee7d48f0e4d70340b9144b58200d95e Mon Sep 17 00:00:00 2001 From: jojoliang Date: Thu, 28 Oct 2021 11:46:25 +0800 Subject: [PATCH] add ut --- ci_media_test.go | 137 ++++++++++++++++++++ ci_test.go | 139 ++++++++++++++++++++ example/object/list_uploads.go | 4 +- helper.go | 15 ++- helper_test.go | 35 +++++ object_part.go | 17 +-- object_part_test.go | 226 ++++++++++++++++++++++++++++++++ object_test.go | 227 +++++++++++++++++++++++++++++++-- 8 files changed, 775 insertions(+), 25 deletions(-) diff --git a/ci_media_test.go b/ci_media_test.go index 13cffc5..446e9ef 100644 --- a/ci_media_test.go +++ b/ci_media_test.go @@ -1,8 +1,14 @@ package cos import ( + "bytes" "context" + "crypto/rand" + "encoding/xml" + "fmt" + "io/ioutil" "net/http" + "reflect" "testing" ) @@ -168,3 +174,134 @@ func TestCIService_DescribeMediaProcessBuckets(t *testing.T) { t.Fatalf("CI.DescribeMediaProcessBuckets returned error: %v", err) } } + +func TestCIService_GetMediaInfo(t *testing.T) { + setup() + defer teardown() + + res_xml := ` + + + 1014.950000 + 10.125000 + QuickTime / MOV + mov,mp4,m4a,3gp,3g2,mj2 + 0 + 2 + 1284547 + 0.000000 + + + + + + + +` + + mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + v := values{ + "ci-process": "videoinfo", + } + testFormValues(t, r, v) + fmt.Fprint(w, res_xml) + }) + + res, _, err := client.CI.GetMediaInfo(context.Background(), "test", nil) + if err != nil { + t.Fatalf("CI.GetMediaInfo returned error: %v", err) + } + want := &GetMediaInfoResult{} + err = xml.Unmarshal([]byte(res_xml), want) + if err != nil { + t.Errorf("Bucket.GetMediaInfo Unmarshal returned error %+v", err) + } + if !reflect.DeepEqual(res, want) { + t.Errorf("Bucket.GetMediaInfo returned %+v, want %+v", res, want) + } +} + +func TestCIService_GetSnapshot(t *testing.T) { + setup() + defer teardown() + + opt := &GetSnapshotOptions{ + Time: 1, + Height: 100, + Width: 100, + Format: "jpg", + Rotate: "auto", + Mode: "exactframe", + } + data := make([]byte, 1234*2) + rand.Read(data) + + mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + v := values{ + "ci-process": "snapshot", + "time": "1", + "format": "jpg", + "width": "100", + "height": "100", + "rotate": "auto", + "mode": "exactframe", + } + testFormValues(t, r, v) + w.Write(data) + }) + + resp, err := client.CI.GetSnapshot(context.Background(), "test", opt) + if err != nil { + t.Fatalf("CI.GetSnapshot returned error: %v", err) + } + defer resp.Body.Close() + bs, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("CI.GetSnapshot ReadAll returned error: %v", err) + } + if bytes.Compare(bs, data) != 0 { + t.Errorf("Bucket.GetSnapshot Compare failed") + } +} diff --git a/ci_test.go b/ci_test.go index ce8d332..de32100 100644 --- a/ci_test.go +++ b/ci_test.go @@ -198,6 +198,63 @@ func TestCIService_ImageRecognition(t *testing.T) { } } +func TestCIService_ImageAuditing(t *testing.T) { + setup() + defer teardown() + name := "test.jpg" + opt := &ImageRecognitionOptions{ + CIProcess: "sensitive-content-recognition", + DetectType: "porn", + MaxFrames: 1, + } + 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", + "max-frames": "1", + } + testFormValues(t, r, vs) + fmt.Fprint(w, ` + 1 + + SexBehavior + 90 + + 0 + OK + 1 + + SexBehavior + 100 + +`) + }) + + want := &ImageRecognitionResult{ + XMLName: xml.Name{Local: "RecognitionResult"}, + Result: 1, + Label: "Porn", + SubLabel: "SexBehavior", + Score: 90, + PornInfo: &RecognitionInfo{ + Code: 0, + Msg: "OK", + HitFlag: 1, + Label: "xxx", + SubLabel: "SexBehavior", + Score: 100, + }, + } + + res, _, err := client.CI.ImageAuditing(context.Background(), name, opt) + if err != nil { + t.Fatalf("CI.ImageAuditing error: %v", err) + } + if !reflect.DeepEqual(res, want) { + t.Errorf("CI.ImageAuditing failed, return:%+v, want:%+v", res, want) + } +} func TestCIService_PutVideoAuditingJob(t *testing.T) { setup() defer teardown() @@ -291,6 +348,88 @@ func TestCIService_GetAudioAuditingJob(t *testing.T) { } } +func TestCIService_PutTextAuditingJob(t *testing.T) { + setup() + defer teardown() + wantBody := `a.txtPorn,Terrorism,Politics,Ads,Illegal,Abusehttp://callback.com/b81d45f94b91a683255e9a95******` + + mux.HandleFunc("/text/auditing", func(writer http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + testHeader(t, r, "Content-Type", "application/xml") + testBody(t, r, wantBody) + }) + + opt := &PutTextAuditingJobOptions{ + InputObject: "a.txt", + Conf: &TextAuditingJobConf{ + DetectType: "Porn,Terrorism,Politics,Ads,Illegal,Abuse", + Callback: "http://callback.com/", + BizType: "b81d45f94b91a683255e9a95******", + }, + } + + _, _, err := client.CI.PutTextAuditingJob(context.Background(), opt) + if err != nil { + t.Fatalf("CI.PutTextAuditingJob returned error: %v", err) + } +} + +func TestCIService_GetTextAuditingJob(t *testing.T) { + setup() + defer teardown() + jobID := "vab1ca9fc8a3ed11ea834c525400863904" + + mux.HandleFunc("/text/auditing"+"/"+jobID, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + }) + + _, _, err := client.CI.GetTextAuditingJob(context.Background(), jobID) + if err != nil { + t.Fatalf("CI.GetTextAuditingJob returned error: %v", err) + } +} + +func TestCIService_PutDocumentAuditingJob(t *testing.T) { + setup() + defer teardown() + wantBody := `http://www.example.com/doctest.docdocPorn,Terrorism,Politics,Adshttp://www.example.com/b81d45f94b91a683255e9a9506f4****` + + mux.HandleFunc("/document/auditing", func(writer http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + testHeader(t, r, "Content-Type", "application/xml") + testBody(t, r, wantBody) + }) + + opt := &PutDocumentAuditingJobOptions{ + InputUrl: "http://www.example.com/doctest.doc", + InputType: "doc", + Conf: &DocumentAuditingJobConf{ + DetectType: "Porn,Terrorism,Politics,Ads", + Callback: "http://www.example.com/", + BizType: "b81d45f94b91a683255e9a9506f4****", + }, + } + + _, _, err := client.CI.PutDocumentAuditingJob(context.Background(), opt) + if err != nil { + t.Fatalf("CI.PutDocumentAuditingJob returned error: %v", err) + } +} + +func TestCIService_GetDocumentAuditingJob(t *testing.T) { + setup() + defer teardown() + jobID := "vab1ca9fc8a3ed11ea834c525400863904" + + mux.HandleFunc("/document/auditing"+"/"+jobID, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + }) + + _, _, err := client.CI.GetDocumentAuditingJob(context.Background(), jobID) + if err != nil { + t.Fatalf("CI.GetDocumentAuditingJob returned error: %v", err) + } +} func TestCIService_Put(t *testing.T) { setup() defer teardown() diff --git a/example/object/list_uploads.go b/example/object/list_uploads.go index 86d74c4..2bed9f5 100644 --- a/example/object/list_uploads.go +++ b/example/object/list_uploads.go @@ -82,8 +82,8 @@ func main() { uploadPart(c, name, uploadID, blockSize, i) } opt := &cos.ObjectListUploadsOptions{ - Prefix: cos.EncodeURIComponent("test/test_list_parts"), - MaxUploads: 100, + Prefix: "test/test_list_parts", + MaxUploads: 1, } v, _, err := c.Object.ListUploads(context.Background(), opt) if err != nil { diff --git a/helper.go b/helper.go index 8fe97fe..7af895c 100644 --- a/helper.go +++ b/helper.go @@ -316,15 +316,18 @@ func FormatRangeOptions(opt *RangeOptions) string { } return "" } - func GetRangeOptions(opt *ObjectGetOptions) (*RangeOptions, error) { if opt == nil || opt.Range == "" { return nil, nil } + return GetRange(opt.Range) +} + +func GetRange(rangeStr string) (*RangeOptions, error) { // bytes=M-N - slices := strings.Split(opt.Range, "=") + slices := strings.Split(rangeStr, "=") if len(slices) != 2 || slices[0] != "bytes" { - return nil, fmt.Errorf("Invalid Parameter Range: %v", opt.Range) + return nil, fmt.Errorf("Invalid Parameter Range: %v", rangeStr) } // byte=M-N, X-Y fSlice := strings.Split(slices[1], ",") @@ -334,13 +337,13 @@ func GetRangeOptions(opt *ObjectGetOptions) (*RangeOptions, error) { var ropt RangeOptions sted := strings.Split(rstr, "-") if len(sted) != 2 { - return nil, fmt.Errorf("Invalid Parameter Range: %v", opt.Range) + return nil, fmt.Errorf("Invalid Parameter Range: %v", rangeStr) } // M if len(sted[0]) > 0 { ropt.Start, err = strconv.ParseInt(sted[0], 10, 64) if err != nil { - return nil, fmt.Errorf("Invalid Parameter Range: %v,err: %v", opt.Range, err) + return nil, fmt.Errorf("Invalid Parameter Range: %v,err: %v", rangeStr, err) } ropt.HasStart = true } @@ -348,7 +351,7 @@ func GetRangeOptions(opt *ObjectGetOptions) (*RangeOptions, error) { if len(sted[1]) > 0 { ropt.End, err = strconv.ParseInt(sted[1], 10, 64) if err != nil || ropt.End == 0 { - return nil, fmt.Errorf("Invalid Parameter Range: %v,err: %v", opt.Range, err) + return nil, fmt.Errorf("Invalid Parameter Range: %v,err: %v", rangeStr, err) } ropt.HasEnd = true } diff --git a/helper_test.go b/helper_test.go index b10c723..d9ee7d3 100644 --- a/helper_test.go +++ b/helper_test.go @@ -93,5 +93,40 @@ func Test_CloneCompleteMultipartUploadOptions(t *testing.T) { if reflect.DeepEqual(res, opt) { t.Errorf("CloneCompleteMultipartUploadOptions, returned:%+v,want:%+v", res, opt) } +} +func Test_CopyOptionsToMulti(t *testing.T) { + opt := &ObjectCopyOptions{ + &ObjectCopyHeaderOptions{ + CacheControl: "max-age=1", + ContentEncoding: "gzip", + ContentType: "text/html", + }, + nil, + } + mul := CopyOptionsToMulti(opt) + if opt.ContentType != mul.ContentType { + t.Errorf("CopyOptionsToMulti, returned:%+v,want:%+v", mul, opt) + } + if opt.CacheControl != mul.CacheControl { + t.Errorf("CopyOptionsToMulti, returned:%+v,want:%+v", mul, opt) + } + if opt.ContentEncoding != mul.ContentEncoding { + t.Errorf("CopyOptionsToMulti, returned:%+v,want:%+v", mul, opt) + } +} + +func Test_CloneInitiateMultipartUploadOptions(t *testing.T) { + opt := &InitiateMultipartUploadOptions{ + &ACLHeaderOptions{}, + &ObjectPutHeaderOptions{ + CacheControl: "max-age=1", + ContentEncoding: "gzip", + ContentType: "text/html", + }, + } + res := CloneInitiateMultipartUploadOptions(opt) + if !reflect.DeepEqual(opt, res) { + t.Errorf("CloneInitiateMultipartUploadOptions, returned:%+v,want:%+v", res, opt) + } } diff --git a/object_part.go b/object_part.go index cb0fe61..80eac91 100644 --- a/object_part.go +++ b/object_part.go @@ -286,12 +286,12 @@ func (s *ObjectService) CopyPart(ctx context.Context, name, uploadID string, par } type ObjectListUploadsOptions struct { - Delimiter string `url:"Delimiter,omitempty"` - EncodingType string `url:"EncodingType,omitempty"` - Prefix string `url:"Prefix"` - MaxUploads int `url:"MaxUploads"` - KeyMarker string `url:"KeyMarker"` - UploadIdMarker string `url:"UploadIDMarker"` + Delimiter string `url:"delimiter,omitempty"` + EncodingType string `url:"encoding-type,omitempty"` + Prefix string `url:"prefix,omitempty"` + MaxUploads int `url:"max-uploads,omitempty"` + KeyMarker string `url:"key-marker,omitempty"` + UploadIdMarker string `url:"upload-id-marker,omitempty"` } type ObjectListUploadsResult struct { @@ -336,6 +336,7 @@ type MultiCopyOptions struct { OptCopy *ObjectCopyOptions PartSize int64 ThreadPoolSize int + useMulti bool // use for ut } type CopyJobs struct { @@ -386,7 +387,7 @@ func (s *ObjectService) innerHead(ctx context.Context, sourceURL string, opt *Ob return } - u, err := url.Parse(fmt.Sprintf("https://%s", surl[0])) + u, err := url.Parse(fmt.Sprintf("http://%s", surl[0])) if err != nil { return } @@ -429,7 +430,7 @@ func (s *ObjectService) MultiCopy(ctx context.Context, name string, sourceURL st if err != nil { return nil, nil, err } - if partNum == 0 || totalBytes < singleUploadMaxLength { + if partNum == 0 || (totalBytes < singleUploadMaxLength && !opt.useMulti) { if len(id) > 0 { return s.Copy(ctx, name, sourceURL, opt.OptCopy, id[0]) } else { diff --git a/object_part_test.go b/object_part_test.go index 03b1225..22ba719 100644 --- a/object_part_test.go +++ b/object_part_test.go @@ -3,6 +3,8 @@ package cos import ( "bytes" "context" + "crypto/rand" + "encoding/hex" "encoding/xml" "fmt" "hash/crc64" @@ -123,6 +125,137 @@ func TestObjectService_UploadPart(t *testing.T) { } +func TestObjectService_ListUploads(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + vs := values{ + "uploads": "", + "max-uploads": "1000", + } + testFormValues(t, r, vs) + + fmt.Fprint(w, ` + examplebucket-1250000000 + + + + 1000 + + / + false + + Object + 1484726657932bcb5b17f7a98a8cad9fc36a340ff204c79bd2f51e7dddf0b6d1da6220520c + + qcs::cam::uin/100000000001:uin/100000000001 + 100000000001 + + + qcs::cam::uin/100000000001:uin/100000000001 + 100000000001 + + Standard + Wed Jan 18 16:04:17 2017 + + + Object + 1484727158f2b8034e5407d18cbf28e84f754b791ecab607d25a2e52de9fee641e5f60707c + + qcs::cam::uin/100000000001:uin/100000000001 + 100000000001 + + + qcs::cam::uin/100000000001:uin/100000000001 + 100000000001 + + Standard + Wed Jan 18 16:12:38 2017 + + + exampleobject + 1484727270323ddb949d528c629235314a9ead80f0ba5d993a3d76b460e6a9cceb9633b08e + + qcs::cam::uin/100000000001:uin/100000000001 + 100000000001 + + + qcs::cam::uin/100000000001:uin/100000000001 + 100000000001 + + Standard + Wed Jan 18 16:14:30 2017 + +`) + }) + + opt := &ObjectListUploadsOptions{ + MaxUploads: 1000, + } + ref, _, err := client.Object.ListUploads(context.Background(), opt) + if err != nil { + t.Fatalf("Object.ListParts returned error: %v", err) + } + + want := &ObjectListUploadsResult{ + XMLName: xml.Name{Local: "ListMultipartUploadsResult"}, + Bucket: "examplebucket-1250000000", + MaxUploads: "1000", + IsTruncated: false, + Delimiter: "/", + Upload: []ListUploadsResultUpload{ + { + Key: "Object", + UploadID: "1484726657932bcb5b17f7a98a8cad9fc36a340ff204c79bd2f51e7dddf0b6d1da6220520c", + Initiator: &Initiator{ + ID: "qcs::cam::uin/100000000001:uin/100000000001", + DisplayName: "100000000001", + }, + Owner: &Owner{ + ID: "qcs::cam::uin/100000000001:uin/100000000001", + DisplayName: "100000000001", + }, + StorageClass: "Standard", + Initiated: "Wed Jan 18 16:04:17 2017", + }, + { + Key: "Object", + UploadID: "1484727158f2b8034e5407d18cbf28e84f754b791ecab607d25a2e52de9fee641e5f60707c", + Initiator: &Initiator{ + ID: "qcs::cam::uin/100000000001:uin/100000000001", + DisplayName: "100000000001", + }, + Owner: &Owner{ + ID: "qcs::cam::uin/100000000001:uin/100000000001", + DisplayName: "100000000001", + }, + StorageClass: "Standard", + Initiated: "Wed Jan 18 16:12:38 2017", + }, + { + Key: "exampleobject", + UploadID: "1484727270323ddb949d528c629235314a9ead80f0ba5d993a3d76b460e6a9cceb9633b08e", + Initiator: &Initiator{ + ID: "qcs::cam::uin/100000000001:uin/100000000001", + DisplayName: "100000000001", + }, + Owner: &Owner{ + ID: "qcs::cam::uin/100000000001:uin/100000000001", + DisplayName: "100000000001", + }, + StorageClass: "Standard", + Initiated: "Wed Jan 18 16:14:30 2017", + }, + }, + } + + if !reflect.DeepEqual(ref, want) { + t.Errorf("Object.ListUploads returned \n%+v, want \n%+v", ref, want) + } +} + func TestObjectService_ListParts(t *testing.T) { setup() defer teardown() @@ -317,3 +450,96 @@ func TestObjectService_CopyPart(t *testing.T) { t.Errorf("Object.Copy returned %+v, want %+v", r, want) } } + +func TestObjectService_MultiCopy(t *testing.T) { + setup() + defer teardown() + + totalBytes := 1024 * 1024 * 35 + b := make([]byte, totalBytes) + rand.Read(b) + + opt := &MultiCopyOptions{ + PartSize: 1, + ThreadPoolSize: 3, + useMulti: true, + } + uploadid := "test_uploadid" + optcom := &CompleteMultipartUploadOptions{} + for i := 1; i <= 35; i += 1 { + optcom.Parts = append(optcom.Parts, Object{ + PartNumber: i, + ETag: hex.EncodeToString(calMD5Digest(b[(i-1)*1024*1024 : i*1024*1024])), + }) + } + + mux.HandleFunc("/test.src.copy", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "HEAD") + w.Header().Add("Content-Length", strconv.FormatInt(int64(totalBytes), 10)) + }) + + mux.HandleFunc("/test.copy", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + // Upload Part + if r.Method == http.MethodPut { + str := r.Form.Get("partNumber") + partNumber, _ := strconv.ParseInt(str, 10, 64) + ranger, err := GetRange(r.Header.Get("x-cos-copy-source-range")) + if err != nil { + t.Errorf("Object.MultiCopy GetRange failed: %v", err) + } + if ranger.Start != (partNumber-1)*1024*1024 || ranger.End != partNumber*1024*1024-1 { + t.Errorf("Object.MultiCopy range error, range: %+v, partNumber: %+v\n", r.Header.Get("x-cos-copy-source-range"), partNumber) + } + if r.Form.Get("uploadId") != uploadid { + t.Errorf("Object.MultiCopy PartCopy returned %+v, want %+v", r.Form.Get("uploadId"), uploadid) + } + + fmt.Fprintf(w, ` + %v + +`, optcom.Parts[partNumber-1].ETag) + return + } + + testMethod(t, r, http.MethodPost) + // Complete MultiPart + if r.Form.Get("uploadId") == uploadid { + v := &CompleteMultipartUploadOptions{} + xml.NewDecoder(r.Body).Decode(v) + vs := values{ + "uploadId": uploadid, + } + testFormValues(t, r, vs) + want := optcom + want.XMLName = xml.Name{Local: "CompleteMultipartUpload"} + if !reflect.DeepEqual(v, want) { + t.Errorf("Object.MultiCopy Complete request body: %+v, want %+v", v, want) + } + fmt.Fprint(w, ` + + + + etag +`) + return + } + + // Init MultiPart + fmt.Fprintf(w, ` + + + %v +`, uploadid) + + }) + + dest := "test.copy" + soruceURL := fmt.Sprintf("%s/%s", client.BaseURL.BucketURL.Host, "test.src.copy") + _, _, err := client.Object.MultiCopy(context.Background(), dest, soruceURL, opt) + + if err != nil { + t.Errorf("Object.MultiCopy failed %v", err) + } + +} diff --git a/object_test.go b/object_test.go index 02d6299..af14d6c 100644 --- a/object_test.go +++ b/object_test.go @@ -4,11 +4,13 @@ import ( "bytes" "context" "crypto/rand" + "encoding/binary" "encoding/hex" "encoding/json" "encoding/xml" "errors" "fmt" + "hash/crc32" "hash/crc64" "io" "io/ioutil" @@ -95,6 +97,7 @@ func TestObjectService_GetToFile(t *testing.T) { defer os.Remove(filePath) fd, err := os.Open(filePath) if err != nil { + t.Errorf("Object.GetToFile open file failed: %v\n", err) } defer fd.Close() bs, _ := ioutil.ReadAll(fd) @@ -721,7 +724,14 @@ func TestObjectService_Upload(t *testing.T) { opt := &MultiUploadOptions{ ThreadPoolSize: 3, PartSize: 1, + OptIni: &InitiateMultipartUploadOptions{ + nil, + &ObjectPutHeaderOptions{ + XCosMetaXXX: &http.Header{}, + }, + }, } + opt.OptIni.XCosMetaXXX.Add("x-cos-meta-test", "test") _, _, err = client.Object.Upload(context.Background(), "test.go.upload", filePath, opt) if err != nil { t.Fatalf("Object.Upload returned error: %v", err) @@ -798,18 +808,10 @@ func TestObjectService_Download(t *testing.T) { setup() defer teardown() - filePath := "rsp.file" + time.Now().Format(time.RFC3339) - newfile, err := os.Create(filePath) - if err != nil { - t.Fatalf("create tmp file failed") - } - defer os.Remove(filePath) // 源文件内容 totalBytes := int64(1024*1024*9 + 1230) b := make([]byte, totalBytes) - _, err = rand.Read(b) - newfile.Write(b) - newfile.Close() + _, err := rand.Read(b) tb := crc64.MakeTable(crc64.ECMA) localcrc := strconv.FormatUint(crc64.Update(0, tb, b), 10) @@ -863,6 +865,38 @@ func TestObjectService_Download(t *testing.T) { if err != nil { t.Fatalf("Object.Upload returned error: %v", err) } + + totalBytes = 103 + name := "test/hello.txt" + data := make([]byte, totalBytes) + rand.Read(data) + tb = crc64.MakeTable(crc64.ECMA) + localcrc = strconv.FormatUint(crc64.Update(0, tb, data), 10) + + mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodHead { + w.Header().Add("Content-Length", strconv.FormatInt(totalBytes, 10)) + w.Header().Add("x-cos-hash-crc64ecma", localcrc) + return + } + testMethod(t, r, "GET") + w.Write(data) + }) + fp := "test.file" + time.Now().Format(time.RFC3339) + _, err = client.Object.Download(context.Background(), name, fp, opt) + if err != nil { + t.Fatalf("Object.Get returned error: %v", err) + } + defer os.Remove(fp) + fd, err := os.Open(fp) + if err != nil { + t.Errorf("Object.GetToFile open file failed: %v\n", err) + } + defer fd.Close() + bs, _ := ioutil.ReadAll(fd) + if bytes.Compare(bs, data) != 0 { + t.Errorf("Object.GetToFile data isn't consistent") + } } func TestObjectService_DownloadWithCheckPoint(t *testing.T) { @@ -1170,3 +1204,178 @@ func TestObjectService_GetFetchTask(t *testing.T) { t.Errorf("object.GetFetchTask res: %+v, want: %+v", r, res) } } + +func TestObjectService_Select(t *testing.T) { + setup() + defer teardown() + + opt := &ObjectSelectOptions{ + Expression: "Select * from COSObject", + ExpressionType: "SQL", + InputSerialization: &SelectInputSerialization{ + CSV: &CSVInputSerialization{ + FileHeaderInfo: "IGNORE", + }, + }, + OutputSerialization: &SelectOutputSerialization{ + CSV: &CSVOutputSerialization{ + RecordDelimiter: "\n", + }, + }, + RequestProgress: "TRUE", + } + + // send a frame + sendAFrame := func(header map[string]string, payload []byte) []byte { + buf := bytes.NewBuffer([]byte{}) + + var totalFrameLength, totalHeaderLength int + for k, v := range header { + totalHeaderLength += len(k) + len(v) + 4 + } + totalFrameLength = 12 + totalHeaderLength + len(payload) + 4 + + // 预响应 + binary.Write(buf, binary.BigEndian, int32(totalFrameLength)) + binary.Write(buf, binary.BigEndian, int32(totalHeaderLength)) + c := crc32.ChecksumIEEE(buf.Bytes()) + binary.Write(buf, binary.BigEndian, c) + + // headers + for k, v := range header { + binary.Write(buf, binary.BigEndian, int8(len(k))) + buf.Write([]byte(k)) + binary.Write(buf, binary.BigEndian, int8(7)) + binary.Write(buf, binary.BigEndian, int16(len(v))) + buf.Write([]byte(v)) + } + + // payload + buf.Write(payload) + + // crc + c32 := crc32.ChecksumIEEE(buf.Bytes()) + binary.Write(buf, binary.BigEndian, c32) + + return buf.Bytes() + } + + dataSize := 1222 * 3 + data := make([]byte, dataSize) + rand.Read(data) + result := ObjectSelectResult{ + ProgressFrame: ProgressFrame{ + XMLName: xml.Name{Local: "Progress"}, + BytesScanned: dataSize, + BytesProcessed: dataSize, + BytesReturned: dataSize, + }, + StatsFrame: StatsFrame{ + XMLName: xml.Name{Local: "Stats"}, + BytesScanned: dataSize, + BytesProcessed: dataSize, + BytesReturned: dataSize, + }, + ErrorFrame: &ErrorFrame{ + Code: "InternalError", + Message: "We encounted an internal error, Please try again", + }, + } + + mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { + // 检查请求 + testMethod(t, r, "POST") + vs := values{ + "select": "", + "select-type": "2", + } + testFormValues(t, r, vs) + want := opt + want.XMLName = xml.Name{Local: "SelectRequest"} + v := new(ObjectSelectOptions) + xml.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, want) { + t.Errorf("Object.Select request body: %+v, want %+v", v, want) + } + + // Continue Message + w.Write(sendAFrame(map[string]string{ + ":message-type": "event", + ":event-type": "Cont"}, []byte{})) + + // Records Message + w.Write(sendAFrame(map[string]string{ + ":message-type": "event", + ":event-type": "Records"}, data)) + + // Progress Message + pframe, _ := xml.Marshal(result.ProgressFrame) + w.Write(sendAFrame(map[string]string{ + ":message-type": "event", + ":event-type": "Progress"}, pframe)) + + // Stat Message + sframe, _ := xml.Marshal(result.StatsFrame) + w.Write(sendAFrame(map[string]string{ + ":message-type": "event", + ":event-type": "Stats"}, sframe)) + + // End Message + w.Write(sendAFrame(map[string]string{ + ":message-type": "event", + ":event-type": "End"}, []byte{})) + }) + + mux.HandleFunc("/test_error", func(w http.ResponseWriter, r *http.Request) { + // Records Message + w.Write(sendAFrame(map[string]string{ + ":message-type": "event", + ":event-type": "Records"}, data)) + + // Error Message + w.Write(sendAFrame(map[string]string{ + ":message-type": "error", + ":error-code": result.ErrorFrame.Code, + ":error-message": result.ErrorFrame.Message}, []byte{})) + }) + + // 测试正常情况 + filePath := "test.file" + time.Now().Format(time.RFC3339) + res, err := client.Object.SelectToFile(context.Background(), "test", filePath, opt) + if err != nil { + t.Errorf("Object.Select failed: %v\n", err) + } + defer os.Remove(filePath) + fd, err := os.Open(filePath) + if err != nil { + t.Errorf("Object.Select open file failed: %v\n", err) + } + defer fd.Close() + bs, err := ioutil.ReadAll(fd) + if err != nil { + t.Errorf("Object.Select read failed: %v\n", err) + } + if bytes.Compare(bs, data) != 0 { + t.Errorf("Object.Select compare failed\n") + } + if !reflect.DeepEqual(result.StatsFrame, res.Frame.StatsFrame) { + t.Errorf("Object.Select stat frame failed, return: %+v, want: %+v\n", res.Frame.StatsFrame, result.StatsFrame) + } + if !reflect.DeepEqual(result.ProgressFrame, res.Frame.ProgressFrame) { + t.Errorf("Object.Select progress frame failed, return: %+v, want: %+v\n", res.Frame.ProgressFrame, result.ProgressFrame) + } + + // 测试错误情况 + resp, err := client.Object.Select(context.Background(), "test_error", opt) + if err != nil { + t.Errorf("Object.Select failed: %v\n", err) + } + _, err = ioutil.ReadAll(resp) + ef, ok := err.(*ErrorFrame) + if !ok { + t.Errorf("Object.Select error is not ErrorFrame, %v", err) + } + if !reflect.DeepEqual(ef, result.ErrorFrame) { + t.Errorf("Object.Select error frame failed, return: %+v, want: %+v\n", res.Frame.ErrorFrame, result.ErrorFrame) + } +}