Files
ttstd-infinite-canvas/backend/internal/app/storage_location_test.go

145 lines
4.8 KiB
Go

package app
import (
"errors"
"hash/crc64"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
)
func TestTencentCOSConnectionTestUsesStorageEndpointInsteadOfCDN(t *testing.T) {
t.Setenv("CANVAS_ALLOW_PRIVATE_UPSTREAMS", "true")
var storageMethods []string
storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
storageMethods = append(storageMethods, r.Method)
if !strings.HasPrefix(r.URL.Path, "/open-ai-canvas/.yingce-tests/platform/") {
t.Errorf("path = %q", r.URL.Path)
}
if !strings.Contains(r.Header.Get("Authorization"), "q-sign-algorithm=sha1") {
t.Errorf("Authorization = %q", r.Header.Get("Authorization"))
}
switch r.Method {
case http.MethodPut:
payload, err := io.ReadAll(r.Body)
if err != nil {
t.Error(err)
}
if string(payload) != "yingce-storage-test" {
t.Errorf("payload = %q", payload)
}
w.Header().Set("ETag", `"cos-test-etag"`)
w.Header().Set("x-cos-hash-crc64ecma", strconv.FormatUint(crc64.Checksum(payload, crc64.MakeTable(crc64.ECMA)), 10))
case http.MethodGet:
if r.Header.Get("Range") != "bytes=0-3" {
t.Errorf("Range = %q", r.Header.Get("Range"))
}
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Range", "bytes 0-3/19")
w.WriteHeader(http.StatusPartialContent)
_, _ = io.WriteString(w, "ying")
case http.MethodDelete:
w.WriteHeader(http.StatusNoContent)
default:
t.Errorf("unexpected method %s", r.Method)
}
}))
defer storageServer.Close()
cdnRequests := 0
cdnServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
cdnRequests++
http.Error(w, "CDN must not be used by storage connection tests", http.StatusBadGateway)
}))
defer cdnServer.Close()
err := verifyOSSConnection(ossSettingValue{
Provider: tencentCOSProvider,
Region: "ap-guangzhou",
Endpoint: storageServer.URL,
CDNBaseURL: cdnServer.URL,
Bucket: "example-1250000000",
AccessKeyID: "secret-id",
AccessKeySecret: "secret-key",
PathPrefix: defaultOSSPathPrefix,
}, defaultOSSPathPrefix+"/.yingce-tests/platform/test-id")
if err != nil {
t.Fatal(err)
}
if strings.Join(storageMethods, ",") != "PUT,GET,DELETE" {
t.Fatalf("storage methods = %v", storageMethods)
}
if cdnRequests != 0 {
t.Fatalf("CDN requests = %d", cdnRequests)
}
}
func TestOSSConnectionReadAcceptsFullObjectWhenRangeIsIgnored(t *testing.T) {
payload := []byte("yingce-storage-test")
stream := &ossObjectStream{
Body: io.NopCloser(strings.NewReader(string(payload))),
StatusCode: http.StatusOK,
ContentLength: int64(len(payload)),
}
if err := verifyOSSConnectionRead(stream, payload); err != nil {
t.Fatal(err)
}
}
func TestOSSConnectionReadRejectsUnexpectedContent(t *testing.T) {
payload := []byte("yingce-storage-test")
stream := &ossObjectStream{
Body: io.NopCloser(strings.NewReader("error page")),
StatusCode: http.StatusOK,
ContentLength: 10,
}
err := verifyOSSConnectionRead(stream, payload)
if err == nil {
t.Fatal("verifyOSSConnectionRead() error = nil")
}
if !errors.Is(err, errOSSConnectionReadMismatch) {
t.Fatalf("verifyOSSConnectionRead() error = %v", err)
}
publicErr := storageConnectionTestError(tencentCOSProvider, "Range 读取", err)
var appErr *AppError
if !errors.As(publicErr, &appErr) || !strings.Contains(appErr.Message, "忽略、改写了 Range 请求") {
t.Fatalf("storageConnectionTestError() = %#v", publicErr)
}
}
func TestTencentCOSConnectionTestReturnsActionableAuthError(t *testing.T) {
t.Setenv("CANVAS_ALLOW_PRIVATE_UPSTREAMS", "true")
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(http.StatusForbidden)
_, _ = io.WriteString(w, `<Error><Code>SignatureDoesNotMatch</Code><Message>signature mismatch</Message><RequestId>request-id</RequestId></Error>`)
}))
defer server.Close()
err := verifyOSSConnection(ossSettingValue{
Provider: tencentCOSProvider,
Region: "ap-guangzhou",
Endpoint: server.URL,
Bucket: "example-1250000000",
AccessKeyID: "secret-id",
AccessKeySecret: "secret-key",
PathPrefix: defaultOSSPathPrefix,
}, defaultOSSPathPrefix+"/.yingce-tests/platform/test-id")
if err == nil {
t.Fatal("verifyOSSConnection() error = nil")
}
var appErr *AppError
if !errors.As(err, &appErr) {
t.Fatalf("error type = %T, error = %v", err, err)
}
if appErr.Status != http.StatusBadGateway || !strings.Contains(appErr.Message, "腾讯云 COS 写入鉴权失败") {
t.Fatalf("AppError = %#v", appErr)
}
if strings.Contains(appErr.Message, "secret-key") || strings.Contains(appErr.Message, "signature mismatch") {
t.Fatalf("public error leaked upstream detail: %q", appErr.Message)
}
}