feat: complete remaining Go backend modules
This commit is contained in:
1 parent
cea2751dc5
commit
aef5a97165
145 files changed
+18376
-199
No files matched your search
@@ -0,0 +1,196 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ossRoundTripFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (f ossRoundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
||||
|
||||
func TestOSSHTTPClientSignsPutAndBuildsVirtualHostURL(t *testing.T) {
|
||||
client := newTestOSSHTTPClient(t, func(r *http.Request) *http.Response {
|
||||
if r.Method != http.MethodPut || r.URL.String() != "https://bucket-a.oss-cn-test.aliyuncs.com/photos/cat.png" {
|
||||
t.Fatalf("request = %s %s", r.Method, r.URL)
|
||||
}
|
||||
if r.Host != "bucket-a.oss-cn-test.aliyuncs.com" || r.Header.Get("Date") != "Thu, 13 Aug 2026 00:00:00 GMT" {
|
||||
t.Fatalf("host/date = %q / %q", r.Host, r.Header.Get("Date"))
|
||||
}
|
||||
if r.Header.Get("Content-Type") != "image/png" || r.Header.Get("Content-Length") != "3" || r.ContentLength != 3 {
|
||||
t.Fatalf("content type/length = %q / %q / %d", r.Header.Get("Content-Type"), r.Header.Get("Content-Length"), r.ContentLength)
|
||||
}
|
||||
if got := r.Header.Get("Authorization"); got != "OSS access-key:ZBQxu+qcAZzglIi4GbBMhAIZIYs=" {
|
||||
t.Fatalf("Authorization = %q", got)
|
||||
}
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if string(body) != "png" {
|
||||
t.Fatalf("body = %q", body)
|
||||
}
|
||||
return ossHTTPResponse(http.StatusOK, "", nil)
|
||||
})
|
||||
|
||||
err := client.Put(context.Background(), OSSPutRequest{
|
||||
Endpoint: "oss-cn-test.aliyuncs.com/", Bucket: "bucket-a", Key: "photos/cat.png",
|
||||
ContentType: "image/png", Body: strings.NewReader("png"), Size: 3,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSSHTTPClientSignsACLGetDeleteAndStreamsGetMetadata(t *testing.T) {
|
||||
var calls int
|
||||
client := newTestOSSHTTPClient(t, func(r *http.Request) *http.Response {
|
||||
calls++
|
||||
if r.URL.Host != "bucket-a.oss-cn-test.aliyuncs.com" || r.URL.Path != "/photos/cat.png" {
|
||||
t.Fatalf("URL = %s", r.URL)
|
||||
}
|
||||
switch calls {
|
||||
case 1:
|
||||
if r.Method != http.MethodPut || r.URL.RawQuery != "acl" || r.Header.Get("x-oss-object-acl") != OSSACLPublicRead {
|
||||
t.Fatalf("ACL request = %s %s headers=%v", r.Method, r.URL, r.Header)
|
||||
}
|
||||
if got := r.Header.Get("Authorization"); got != "OSS access-key:B8m2FqdnBSJTA4F6L3o7NZkkYTU=" {
|
||||
t.Fatalf("ACL Authorization = %q", got)
|
||||
}
|
||||
return ossHTTPResponse(http.StatusOK, "", nil)
|
||||
case 2:
|
||||
if r.Method != http.MethodGet || r.Header.Get("Authorization") != "OSS access-key:+do4MvonQT0wF7/Pwl+7+Eqze9g=" {
|
||||
t.Fatalf("GET request = %s auth=%q", r.Method, r.Header.Get("Authorization"))
|
||||
}
|
||||
return ossHTTPResponse(http.StatusOK, "image/png", []byte("streamed"))
|
||||
case 3:
|
||||
if r.Method != http.MethodDelete || r.Header.Get("Authorization") != "OSS access-key:ykyNN5k2axmVkPpEqzNsEVHCmGU=" {
|
||||
t.Fatalf("DELETE request = %s auth=%q", r.Method, r.Header.Get("Authorization"))
|
||||
}
|
||||
return ossHTTPResponse(http.StatusNoContent, "", nil)
|
||||
default:
|
||||
t.Fatalf("unexpected call %d", calls)
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
object := OSSObjectRequest{Endpoint: "https://bucket-a.oss-cn-test.aliyuncs.com", Bucket: "bucket-a", Key: "photos/cat.png"}
|
||||
if err := client.SetACL(ctx, OSSACLRequest{Endpoint: object.Endpoint, Bucket: object.Bucket, Key: object.Key, ACL: OSSACLPublicRead}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
blob, err := client.Get(ctx, object)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if blob.ContentType != "image/png" || blob.Size != 8 {
|
||||
t.Fatalf("blob metadata = %#v", blob)
|
||||
}
|
||||
got, _ := io.ReadAll(blob.Body)
|
||||
_ = blob.Body.Close()
|
||||
if string(got) != "streamed" {
|
||||
t.Fatalf("body = %q", got)
|
||||
}
|
||||
if err := client.Delete(ctx, object); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSSHTTPClientUsesPathStyleForExplicitBucketPathAndEscapesKey(t *testing.T) {
|
||||
client := newTestOSSHTTPClient(t, func(r *http.Request) *http.Response {
|
||||
if got := r.URL.String(); got != "https://proxy.test/storage/bucket-a/a%20b/%E7%8C%AB.png" {
|
||||
t.Fatalf("URL = %q", got)
|
||||
}
|
||||
return ossHTTPResponse(http.StatusNoContent, "", nil)
|
||||
})
|
||||
if err := client.Delete(context.Background(), OSSObjectRequest{
|
||||
Endpoint: "https://proxy.test/storage/bucket-a", Bucket: "bucket-a", Key: "a b/猫.png",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSSHTTPClientMapsServiceErrorsWithoutLeakingResponseOrCredentials(t *testing.T) {
|
||||
client := newTestOSSHTTPClient(t, func(*http.Request) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: make(http.Header),
|
||||
Body: io.NopCloser(strings.NewReader(
|
||||
`<Error><Code>NoSuchKey</Code><Message>access-key secret-key private-object</Message></Error>`,
|
||||
)),
|
||||
}
|
||||
})
|
||||
_, err := client.Get(context.Background(), OSSObjectRequest{Endpoint: "https://oss-cn-test.aliyuncs.com", Bucket: "bucket-a", Key: "private-object"})
|
||||
var ossErr *OSSError
|
||||
if !errors.As(err, &ossErr) || ossErr.Status != http.StatusNotFound || ossErr.Code != "NoSuchKey" {
|
||||
t.Fatalf("error = %#v", err)
|
||||
}
|
||||
for _, secret := range []string{"access-key", "secret-key", "private-object"} {
|
||||
if strings.Contains(err.Error(), secret) {
|
||||
t.Fatalf("error leaks %q: %v", secret, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSSHTTPClientPropagatesContextCancellation(t *testing.T) {
|
||||
client := newTestOSSHTTPClient(t, func(r *http.Request) *http.Response {
|
||||
<-r.Context().Done()
|
||||
return nil
|
||||
})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
err := client.Delete(ctx, OSSObjectRequest{Endpoint: "https://oss-cn-test.aliyuncs.com", Bucket: "bucket-a", Key: "object"})
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("error = %v, want context cancellation", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOSSHTTPClientRejectsMissingDependencies(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
id, secret string
|
||||
client *http.Client
|
||||
}{
|
||||
{"", "secret", http.DefaultClient},
|
||||
{"id", "", http.DefaultClient},
|
||||
{"id", "secret", nil},
|
||||
} {
|
||||
if _, err := NewOSSHTTPClient(test.id, test.secret, test.client, time.Now); err == nil {
|
||||
t.Fatalf("NewOSSHTTPClient(%q, %q, %v) succeeded", test.id, test.secret, test.client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTestOSSHTTPClient(t *testing.T, roundTrip func(*http.Request) *http.Response) *OSSHTTPClient {
|
||||
t.Helper()
|
||||
httpClient := &http.Client{Transport: ossRoundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
response := roundTrip(r)
|
||||
if response == nil {
|
||||
return nil, r.Context().Err()
|
||||
}
|
||||
response.Request = r
|
||||
return response, nil
|
||||
})}
|
||||
client, err := NewOSSHTTPClient("access-key", "secret-key", httpClient, func() time.Time {
|
||||
return time.Date(2026, 8, 13, 0, 0, 0, 0, time.UTC)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func ossHTTPResponse(status int, contentType string, body []byte) *http.Response {
|
||||
header := make(http.Header)
|
||||
if contentType != "" {
|
||||
header.Set("Content-Type", contentType)
|
||||
}
|
||||
return &http.Response{
|
||||
StatusCode: status,
|
||||
Header: header,
|
||||
Body: io.NopCloser(bytes.NewReader(body)),
|
||||
ContentLength: int64(len(body)),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user