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) } }