138 lines
4.5 KiB
Go
138 lines
4.5 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"infinite-canvas/backend/internal/model"
|
|
)
|
|
|
|
func TestProviderTaskRecoveryContextSurvivesClientCancellation(t *testing.T) {
|
|
type contextKey string
|
|
parent, cancelParent := context.WithCancel(context.WithValue(context.Background(), contextKey("trace"), "trace-1"))
|
|
cancelParent()
|
|
|
|
recovery, cancelRecovery := providerTaskRecoveryContext(parent)
|
|
defer cancelRecovery()
|
|
if recovery.Err() != nil {
|
|
t.Fatalf("recovery context inherited cancellation: %v", recovery.Err())
|
|
}
|
|
if recovery.Value(contextKey("trace")) != "trace-1" {
|
|
t.Fatal("recovery context did not preserve request values")
|
|
}
|
|
if _, ok := recovery.Deadline(); !ok {
|
|
t.Fatal("recovery context has no bounded deadline")
|
|
}
|
|
}
|
|
|
|
func TestSettleRecoveredBillingIsIdempotent(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
status model.BillingStatus
|
|
wantSettle int
|
|
wantRestore int
|
|
}{
|
|
{name: "settled", status: model.BillingStatusSettled},
|
|
{name: "running", status: model.BillingStatusRunning, wantSettle: 1},
|
|
{name: "refunded", status: model.BillingStatusRefunded, wantRestore: 1},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
repo := &taskBillingRepositoryStub{order: &model.BillingOrder{Status: tt.status}}
|
|
svc := &Service{taskBillingCoordinator: newTaskBillingCoordinator(repo)}
|
|
if err := svc.settleRecoveredBilling(&model.Task{BillingOrderID: "order-1"}, "provider-1"); err != nil {
|
|
t.Fatalf("settleRecoveredBilling() error = %v", err)
|
|
}
|
|
if got := len(repo.settleCalls); got != tt.wantSettle {
|
|
t.Fatalf("settle calls = %d, want %d", got, tt.wantSettle)
|
|
}
|
|
if got := len(repo.restoreCalls); got != tt.wantRestore {
|
|
t.Fatalf("restore calls = %d, want %d", got, tt.wantRestore)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDownloadRemoteResourceEnforcesVideoImportBounds(t *testing.T) {
|
|
t.Setenv("CANVAS_ALLOW_PRIVATE_UPSTREAMS", "true")
|
|
t.Setenv("HTTP_PROXY", "")
|
|
t.Setenv("HTTPS_PROXY", "")
|
|
t.Setenv("ALL_PROXY", "")
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/large" {
|
|
w.Header().Set("Content-Type", "video/mp4")
|
|
_, _ = w.Write([]byte("12345"))
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "video/mp4; charset=binary")
|
|
_, _ = w.Write([]byte("video"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
payload, err := downloadRemoteResource(server.URL+"/video.mp4", 16)
|
|
if err != nil {
|
|
t.Fatalf("downloadRemoteResource() error = %v", err)
|
|
}
|
|
if payload.mimeType != "video/mp4" || string(payload.data) != "video" {
|
|
t.Fatalf("payload = %#v, want video/mp4 body", payload)
|
|
}
|
|
if _, err := downloadRemoteResource(server.URL+"/large", 5); err == nil || !strings.Contains(err.Error(), "远程资源必须小于") {
|
|
t.Fatalf("oversized download error = %v", err)
|
|
}
|
|
if _, err := downloadRemoteResource("file:///tmp/video.mp4", 16); err == nil {
|
|
t.Fatal("downloadRemoteResource() accepted a non-http URL")
|
|
}
|
|
}
|
|
|
|
func TestAdminVideoRecoveryRequiresAdmin(t *testing.T) {
|
|
service := &Service{}
|
|
methods := map[string]func(*model.User) error{
|
|
"query": func(actor *model.User) error {
|
|
_, err := service.AdminQueryFailedVideoTask(context.Background(), actor, "log-1", "provider-1")
|
|
return err
|
|
},
|
|
"url": func(actor *model.User) error {
|
|
_, err := service.AdminRecoverVideoByURL(context.Background(), actor, "log-1", "https://example.com/video.mp4", "provider-1")
|
|
return err
|
|
},
|
|
"batch": func(actor *model.User) error {
|
|
_, err := service.AdminBatchQueryFailedVideoTasks(context.Background(), actor, []string{"log-1"})
|
|
return err
|
|
},
|
|
}
|
|
actors := map[string]*model.User{
|
|
"anonymous": nil,
|
|
"ordinary user": {Role: model.UserRoleUser},
|
|
}
|
|
for actorName, actor := range actors {
|
|
for methodName, invoke := range methods {
|
|
t.Run(actorName+"/"+methodName, func(t *testing.T) {
|
|
if err := invoke(actor); err == nil {
|
|
t.Fatalf("recovery method %q accepted %s actor", methodName, actorName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRetryableProtocolMediaDownload(t *testing.T) {
|
|
for _, err := range []error{
|
|
errors.New("net/http: TLS handshake timeout"),
|
|
errors.New("read: connection reset by peer"),
|
|
errors.New("unexpected EOF"),
|
|
} {
|
|
if !retryableProtocolMediaDownload(err) {
|
|
t.Fatalf("retryableProtocolMediaDownload(%v) = false", err)
|
|
}
|
|
}
|
|
for _, err := range []error{context.Canceled, context.DeadlineExceeded, errors.New("HTTP 404")} {
|
|
if retryableProtocolMediaDownload(err) {
|
|
t.Fatalf("retryableProtocolMediaDownload(%v) = true", err)
|
|
}
|
|
}
|
|
}
|