@@ -2,21 +2,34 @@ package oss
22
33import (
44 "context"
5+ "crypto/tls"
56 "net/http"
67 "net/http/httptest"
8+ "net/url"
9+ "strings"
710 "testing"
811 "time"
912
13+ awsapi "github.com/404tk/cloudtoolkit/pkg/providers/aws/api"
1014 "github.com/404tk/cloudtoolkit/pkg/providers/jdcloud/api"
1115 "github.com/404tk/cloudtoolkit/pkg/providers/jdcloud/auth"
1216)
1317
14- func TestDriverListBucketsUsesFixedRegion (t * testing.T ) {
18+ func TestDriverListBucketsUsesFixedRegionAndResolvesBucketRegions (t * testing.T ) {
1519 server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
16- if r .URL .Path != "/v1/regions/cn-north-1/buckets" {
17- t .Fatalf ("unexpected path: %s" , r .URL .Path )
20+ switch {
21+ case r .Method == http .MethodGet && r .URL .Path == "/v1/regions/cn-north-1/buckets" :
22+ _ , _ = w .Write ([]byte (`{"requestId":"req-oss","result":{"buckets":[{"name":"bucket-a"},{"name":"bucket-b"}]}}` ))
23+ case r .Method == http .MethodHead && r .URL .Path == "/v1/regions/cn-north-1/buckets/bucket-a" :
24+ w .WriteHeader (http .StatusNotFound )
25+ _ , _ = w .Write ([]byte (`{"requestId":"req-miss-a","error":{"status":"404","code":404,"message":"bucket not found"}}` ))
26+ case r .Method == http .MethodHead && r .URL .Path == "/v1/regions/cn-east-1/buckets/bucket-a" :
27+ w .WriteHeader (http .StatusOK )
28+ case r .Method == http .MethodHead && r .URL .Path == "/v1/regions/cn-north-1/buckets/bucket-b" :
29+ w .WriteHeader (http .StatusOK )
30+ default :
31+ t .Fatalf ("unexpected request: %s %s" , r .Method , r .URL .Path )
1832 }
19- _ , _ = w .Write ([]byte (`{"requestId":"req-oss","result":{"buckets":[{"name":"bucket-a"},{"name":"bucket-b"}]}}` ))
2033 }))
2134 defer server .Close ()
2235
@@ -28,6 +41,79 @@ func TestDriverListBucketsUsesFixedRegion(t *testing.T) {
2841 if len (got ) != 2 || got [0 ].BucketName != "bucket-a" || got [1 ].BucketName != "bucket-b" {
2942 t .Fatalf ("unexpected buckets: %+v" , got )
3043 }
44+ if got [0 ].Region != "cn-east-1" || got [1 ].Region != "cn-north-1" {
45+ t .Fatalf ("unexpected bucket regions: %+v" , got )
46+ }
47+ }
48+
49+ func TestDriverResolveBucketRegionUsesExplicitRegionFirst (t * testing.T ) {
50+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
51+ switch r .URL .Path {
52+ case "/v1/regions/cn-south-1/buckets/demo-bucket" :
53+ w .WriteHeader (http .StatusOK )
54+ default :
55+ t .Fatalf ("unexpected path: %s" , r .URL .Path )
56+ }
57+ }))
58+ defer server .Close ()
59+
60+ driver := & Driver {
61+ Client : newTestClient (server .URL ),
62+ Region : "cn-south-1" ,
63+ }
64+ got , err := driver .ResolveBucketRegion (context .Background (), "demo-bucket" )
65+ if err != nil {
66+ t .Fatalf ("ResolveBucketRegion() error = %v" , err )
67+ }
68+ if got != "cn-south-1" {
69+ t .Fatalf ("ResolveBucketRegion() = %q, want cn-south-1" , got )
70+ }
71+ }
72+
73+ func TestClientListObjectsV2UsesServiceHostAndContinuationToken (t * testing.T ) {
74+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
75+ if r .Host != "s3.cn-north-1.jdcloud-oss.com" {
76+ t .Fatalf ("unexpected host: %s" , r .Host )
77+ }
78+ if r .URL .Path != "/demo-bucket" {
79+ t .Fatalf ("unexpected path: %s" , r .URL .Path )
80+ }
81+ if got := r .URL .Query ().Get ("list-type" ); got != "2" {
82+ t .Fatalf ("unexpected list-type: %s" , got )
83+ }
84+ if got := r .URL .Query ().Get ("continuation-token" ); got != "page-2" {
85+ t .Fatalf ("unexpected continuation-token: %s" , got )
86+ }
87+ if got := r .URL .Query ().Get ("max-keys" ); got != "100" {
88+ t .Fatalf ("unexpected max-keys: %s" , got )
89+ }
90+ if authz := r .Header .Get ("Authorization" ); ! strings .HasPrefix (authz , "AWS4-HMAC-SHA256 Credential=AKID/20260419/cn-north-1/s3/aws4_request, SignedHeaders=" ) {
91+ t .Fatalf ("unexpected authorization: %s" , authz )
92+ }
93+ _ , _ = w .Write ([]byte (`<ListBucketResult><IsTruncated>true</IsTruncated><NextContinuationToken>page-3</NextContinuationToken><Contents><Key>logs/a.txt</Key><Size>12</Size></Contents></ListBucketResult>` ))
94+ }))
95+ defer server .Close ()
96+
97+ client := NewClient (
98+ auth .New ("AKID" , "SECRET" , "" ),
99+ awsapi .WithHTTPClient (rewriteHostClient (server .URL )),
100+ awsapi .WithClock (func () time.Time { return time .Date (2026 , 4 , 19 , 12 , 0 , 0 , 0 , time .UTC ) }),
101+ awsapi .WithRetryPolicy (awsapi.RetryPolicy {
102+ MaxAttempts : 1 ,
103+ Sleep : func (context.Context , time.Duration ) error { return nil },
104+ }),
105+ )
106+
107+ got , err := client .ListObjectsV2 (context .Background (), "demo-bucket" , "cn-north-1" , "page-2" , 100 )
108+ if err != nil {
109+ t .Fatalf ("ListObjectsV2() error = %v" , err )
110+ }
111+ if ! got .IsTruncated || got .NextContinuationToken != "page-3" {
112+ t .Fatalf ("unexpected pagination: %+v" , got )
113+ }
114+ if len (got .Objects ) != 1 || got .Objects [0 ].Key != "logs/a.txt" || got .Objects [0 ].Size != 12 {
115+ t .Fatalf ("unexpected objects: %+v" , got )
116+ }
31117}
32118
33119func newTestClient (baseURL string ) * api.Client {
@@ -42,3 +128,32 @@ func newTestClient(baseURL string) *api.Client {
42128 }),
43129 )
44130}
131+
132+ func rewriteHostClient (rawURL string ) * http.Client {
133+ target , err := url .Parse (rawURL )
134+ if err != nil {
135+ panic (err )
136+ }
137+ return & http.Client {
138+ Transport : & rewriteHostTransport {
139+ target : target ,
140+ base : & http.Transport {
141+ TLSClientConfig : & tls.Config {InsecureSkipVerify : true },
142+ },
143+ },
144+ }
145+ }
146+
147+ type rewriteHostTransport struct {
148+ target * url.URL
149+ base http.RoundTripper
150+ }
151+
152+ func (t * rewriteHostTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
153+ clone := req .Clone (req .Context ())
154+ urlCopy := * clone .URL
155+ urlCopy .Scheme = t .target .Scheme
156+ urlCopy .Host = t .target .Host
157+ clone .URL = & urlCopy
158+ return t .base .RoundTrip (clone )
159+ }
0 commit comments