easyai-ai-gateway/apps/api/internal/clients/volces_deyun_test.go

130 lines
4.1 KiB
Go

package clients
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
func TestVolcesClientSupportsDeyunEnvelope(t *testing.T) {
var submitted bool
var polled bool
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer deyun-secret" {
t.Fatalf("unexpected authorization header: %q", r.Header.Get("Authorization"))
}
switch r.Method + " " + r.URL.Path {
case "POST /c39/api/v3/video/tasks":
submitted = true
_ = json.NewEncoder(w).Encode(map[string]any{
"code": 0,
"request_id": "deyun-submit-request",
"data": map[string]any{"id": "deyun-task-1"},
})
case "GET /c39/api/v3/video/tasks/deyun-task-1":
polled = true
_ = json.NewEncoder(w).Encode(map[string]any{
"code": 0,
"request_id": "deyun-poll-request",
"data": map[string]any{
"id": "deyun-task-1",
"status": "succeeded",
"created_at": 123,
"content": map[string]any{
"video_url": "https://example.com/deyun.mp4",
},
},
})
default:
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
}
}))
defer server.Close()
response, err := (VolcesClient{HTTPClient: server.Client()}).Run(context.Background(), Request{
Kind: "videos.generations",
ModelType: "video_generate",
Model: "deyun-seedance-2.0-canary",
Body: map[string]any{
"prompt": "A red cube rotates on a white table",
"resolution": "480p",
"ratio": "16:9",
"duration": 4,
"generate_audio": false,
},
Candidate: store.RuntimeModelCandidate{
BaseURL: server.URL + "/c39/api/v3",
ProviderModelName: "doubao-seedance-2-0",
Credentials: map[string]any{"apiKey": "deyun-secret"},
PlatformConfig: map[string]any{
"volcesPollIntervalMs": 100,
"volcesPollTimeoutSeconds": 1,
"volcesVideoTaskPath": "/video/tasks",
},
},
})
if err != nil {
t.Fatalf("run deyun-compatible video task: %v", err)
}
if !submitted || !polled {
t.Fatalf("expected submit and poll, submitted=%v polled=%v", submitted, polled)
}
if response.RequestID != "deyun-poll-request" {
t.Fatalf("unexpected request id: %s", response.RequestID)
}
data, _ := response.Result["data"].([]any)
item, _ := data[0].(map[string]any)
if response.Result["upstream_task_id"] != "deyun-task-1" || item["url"] != "https://example.com/deyun.mp4" {
t.Fatalf("unexpected response: %+v", response.Result)
}
}
func TestNormalizeVolcesCompatibleResultPreservesNativeResponse(t *testing.T) {
native := map[string]any{"id": "native-task", "status": "queued"}
got, requestID, err := normalizeVolcesCompatibleResult(native)
if err != nil {
t.Fatalf("normalize native response: %v", err)
}
if got["id"] != "native-task" || requestID != "" {
t.Fatalf("native response changed unexpectedly: %+v requestID=%q", got, requestID)
}
}
func TestNormalizeVolcesCompatibleResultRejectsBusinessError(t *testing.T) {
_, requestID, err := normalizeVolcesCompatibleResult(map[string]any{
"code": 1004,
"message": "Authorization is expired",
"request_id": "deyun-error-request",
})
if err == nil {
t.Fatal("expected business error")
}
if requestID != "deyun-error-request" || ErrorCode(err) != "volces_1004" {
t.Fatalf("unexpected error metadata requestID=%q code=%q err=%v", requestID, ErrorCode(err), err)
}
if !strings.Contains(err.Error(), "Authorization is expired") {
t.Fatalf("unexpected error message: %v", err)
}
}
func TestNormalizeVolcesCompatibleResultRejectsHTTP200ErrorObject(t *testing.T) {
_, _, err := normalizeVolcesCompatibleResult(map[string]any{
"error": map[string]any{
"code": "ModelNotOpen",
"message": "model service is not activated",
"type": "Not Found",
},
})
if err == nil {
t.Fatal("expected HTTP 200 error object to fail")
}
if ErrorCode(err) != "ModelNotOpen" || !strings.Contains(err.Error(), "not activated") {
t.Fatalf("unexpected error: code=%q err=%v", ErrorCode(err), err)
}
}