|
- package doubao_aiping
-
- import (
- "io"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
-
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/constant"
- "github.com/QuantumNous/new-api/model"
- relaycommon "github.com/QuantumNous/new-api/relay/common"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/require"
- )
-
- func TestBuildRequestURLUsesAipingVideosEndpoint(t *testing.T) {
- adaptor := &TaskAdaptor{}
- adaptor.Init(&relaycommon.RelayInfo{
- ChannelMeta: &relaycommon.ChannelMeta{
- ChannelType: constant.ChannelTypeDoubaoVideoCompatibleAiping,
- ChannelBaseUrl: "https://aiping.example.com",
- ApiKey: "sk-test",
- },
- })
-
- got, err := adaptor.BuildRequestURL(&relaycommon.RelayInfo{
- ChannelMeta: &relaycommon.ChannelMeta{ChannelBaseUrl: "https://aiping.example.com"},
- })
-
- require.NoError(t, err)
- require.Equal(t, "https://aiping.example.com/api/v1/videos", got)
- }
-
- func TestFetchTaskUsesAipingVideosEndpoint(t *testing.T) {
- adaptor := &TaskAdaptor{}
- req, err := adaptor.buildFetchRequest("https://aiping.example.com", "sk-test", map[string]any{"task_id": "cgt-123"})
-
- require.NoError(t, err)
- require.Equal(t, http.MethodGet, req.Method)
- require.Equal(t, "https://aiping.example.com/api/v1/videos/cgt-123", req.URL.String())
- require.Equal(t, "Bearer sk-test", req.Header.Get("Authorization"))
- }
-
- func TestBuildRequestBodyPassesNativeMetadataToUpstream(t *testing.T) {
- adaptor := &TaskAdaptor{}
- c := newTaskRequestContext(t, `{
- "model":"doubao-seedance-2-0-260128",
- "prompt":"current prompt",
- "metadata":{
- "content":[
- {"type":"video_url","video_url":{"url":"https://example.test/input.mp4"},"role":"reference_video"}
- ],
- "duration":5
- }
- }`)
- info := &relaycommon.RelayInfo{
- ChannelMeta: &relaycommon.ChannelMeta{UpstreamModelName: "doubao-seedance-2-0-260128"},
- }
-
- body, err := adaptor.BuildRequestBody(c, info)
- require.NoError(t, err)
- data, err := io.ReadAll(body)
- require.NoError(t, err)
-
- require.Contains(t, string(data), `"video_url":{"url":"https://example.test/input.mp4"}`)
- require.Contains(t, string(data), `"duration":5`)
- require.Contains(t, string(data), `"text":"current prompt"`)
- }
-
- func TestBuildRequestBodyPreservesNativeInterleavedContentOrder(t *testing.T) {
- adaptor := &TaskAdaptor{}
- c := newTaskRequestContext(t, `{
- "model":"doubao-seedance-2-0-260128",
- "metadata":{
- "content":[
- {"type":"text","text":"开场:海边日落"},
- {"type":"image_url","image_url":{"url":"asset://img1"},"role":"reference_image"},
- {"type":"text","text":"中段:车身环绕特写"},
- {"type":"video_url","video_url":{"url":"asset://vid1"},"role":"reference_video"},
- {"type":"text","text":"结尾:尾灯点亮"}
- ],
- "duration":5
- }
- }`)
- info := &relaycommon.RelayInfo{
- ChannelMeta: &relaycommon.ChannelMeta{UpstreamModelName: "doubao-seedance-2-0-260128"},
- }
-
- body, err := adaptor.BuildRequestBody(c, info)
- require.NoError(t, err)
- data, err := io.ReadAll(body)
- require.NoError(t, err)
- var payload requestPayload
- require.NoError(t, common.Unmarshal(data, &payload))
-
- require.Len(t, payload.Content, 5)
- require.Equal(t, "text", payload.Content[0].Type)
- require.Equal(t, "开场:海边日落", payload.Content[0].Text)
- require.Equal(t, "image_url", payload.Content[1].Type)
- require.Equal(t, "asset://img1", payload.Content[1].ImageURL.URL)
- require.Equal(t, "text", payload.Content[2].Type)
- require.Equal(t, "中段:车身环绕特写", payload.Content[2].Text)
- require.Equal(t, "video_url", payload.Content[3].Type)
- require.Equal(t, "asset://vid1", payload.Content[3].VideoURL.URL)
- require.Equal(t, "text", payload.Content[4].Type)
- require.Equal(t, "结尾:尾灯点亮", payload.Content[4].Text)
- }
-
- func TestConvertToRequestPayloadPreservesNativeContentTextOrder(t *testing.T) {
- adaptor := &TaskAdaptor{}
- req := &relaycommon.TaskSubmitReq{
- Model: "doubao-seedance-2-0-260128",
- Prompt: "first prompt\nsecond prompt\nthird prompt",
- Metadata: map[string]interface{}{
- "content": []interface{}{
- map[string]interface{}{"type": "text", "text": "first prompt"},
- map[string]interface{}{"type": "image_url", "image_url": map[string]interface{}{"url": "asset://img1"}, "role": "reference_image"},
- map[string]interface{}{"type": "text", "text": "second prompt"},
- map[string]interface{}{"type": "video_url", "video_url": map[string]interface{}{"url": "asset://vid1"}, "role": "reference_video"},
- map[string]interface{}{"type": "text", "text": "third prompt"},
- },
- },
- }
-
- body, err := adaptor.convertToRequestPayload(req)
-
- require.NoError(t, err)
- require.Len(t, body.Content, 5)
- require.Equal(t, "text", body.Content[0].Type)
- require.Equal(t, "first prompt", body.Content[0].Text)
- require.Equal(t, "image_url", body.Content[1].Type)
- require.Equal(t, "text", body.Content[2].Type)
- require.Equal(t, "second prompt", body.Content[2].Text)
- require.Equal(t, "video_url", body.Content[3].Type)
- require.Equal(t, "text", body.Content[4].Type)
- require.Equal(t, "third prompt", body.Content[4].Text)
- }
-
- func TestConvertToRequestPayloadPassesArkDocumentFields(t *testing.T) {
- adaptor := &TaskAdaptor{}
- req := &relaycommon.TaskSubmitReq{
- Model: "doubao-seedance-2-0-260128",
- Prompt: "documented prompt",
- Metadata: map[string]interface{}{
- "content": []interface{}{
- map[string]interface{}{"type": "text", "text": "documented prompt"},
- map[string]interface{}{"type": "image_url", "image_url": map[string]interface{}{"url": "asset://image-1"}, "role": "reference_image"},
- map[string]interface{}{"type": "video_url", "video_url": map[string]interface{}{"url": "asset://video-1"}, "role": "reference_video"},
- map[string]interface{}{"type": "audio_url", "audio_url": map[string]interface{}{"url": "asset://audio-1"}, "role": "reference_audio"},
- map[string]interface{}{"type": "draft_task", "draft_task": map[string]interface{}{"id": "cgt-draft"}},
- },
- "callback_url": "https://example.test/callback",
- "return_last_frame": true,
- "service_tier": "default",
- "execution_expires_after": float64(3600),
- "generate_audio": false,
- "draft": true,
- "tools": []interface{}{map[string]interface{}{"type": "web_search"}},
- "safety_identifier": "user-hash-1",
- "priority": float64(5),
- "resolution": "480p",
- "ratio": "1:1",
- "duration": float64(5),
- "frames": float64(29),
- "seed": float64(11),
- "camera_fixed": false,
- "watermark": true,
- },
- }
-
- body, err := adaptor.convertToRequestPayload(req)
- require.NoError(t, err)
- data, err := common.Marshal(body)
- require.NoError(t, err)
- jsonBody := string(data)
-
- require.Contains(t, jsonBody, `"image_url":{"url":"asset://image-1"}`)
- require.Contains(t, jsonBody, `"video_url":{"url":"asset://video-1"}`)
- require.Contains(t, jsonBody, `"audio_url":{"url":"asset://audio-1"}`)
- require.Contains(t, jsonBody, `"draft_task":{"id":"cgt-draft"}`)
- require.Contains(t, jsonBody, `"callback_url":"https://example.test/callback"`)
- require.Contains(t, jsonBody, `"return_last_frame":true`)
- require.Contains(t, jsonBody, `"service_tier":"default"`)
- require.Contains(t, jsonBody, `"execution_expires_after":3600`)
- require.Contains(t, jsonBody, `"generate_audio":false`)
- require.Contains(t, jsonBody, `"draft":true`)
- require.Contains(t, jsonBody, `"tools":[{"type":"web_search"}]`)
- require.Contains(t, jsonBody, `"safety_identifier":"user-hash-1"`)
- require.Contains(t, jsonBody, `"priority":5`)
- require.Contains(t, jsonBody, `"resolution":"480p"`)
- require.Contains(t, jsonBody, `"ratio":"1:1"`)
- require.Contains(t, jsonBody, `"duration":5`)
- require.Contains(t, jsonBody, `"frames":29`)
- require.Contains(t, jsonBody, `"seed":11`)
- require.Contains(t, jsonBody, `"camera_fixed":false`)
- require.Contains(t, jsonBody, `"watermark":true`)
- }
-
- func TestDoResponseRewritesIDToPublicTaskID(t *testing.T) {
- adaptor := &TaskAdaptor{}
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- info := &relaycommon.RelayInfo{
- OriginModelName: "doubao-seedance-2-0-260128",
- TaskRelayInfo: &relaycommon.TaskRelayInfo{PublicTaskID: "task_public"},
- }
- resp := &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(strings.NewReader(`{
- "id":"cgt-upstream",
- "aiping_id":"internal-aiping-id",
- "status":"queued",
- "created_at":1781496040,
- "model":"doubao-seedance-2-0-260128"
- }`)),
- }
-
- upstreamID, data, taskErr := adaptor.DoResponse(c, resp, info)
-
- require.Nil(t, taskErr)
- require.Equal(t, "cgt-upstream", upstreamID)
- require.Contains(t, string(data), `"id":"cgt-upstream"`)
- require.Equal(t, http.StatusOK, w.Code)
- require.Contains(t, w.Body.String(), `"id":"task_public"`)
- require.NotContains(t, w.Body.String(), "aiping_id")
- require.Contains(t, w.Body.String(), `"status":"queued"`)
- require.Contains(t, w.Body.String(), `"model":"doubao-seedance-2-0-260128"`)
- }
-
- func TestParseTaskResultTreatsArkTerminalStatusesAsFinished(t *testing.T) {
- adaptor := &TaskAdaptor{}
- cases := []struct {
- name string
- body string
- wantReason string
- }{
- {
- name: "expired",
- body: `{"id":"cgt-expired","status":"expired","error":{"code":"Expired","message":"task expired"}}`,
- wantReason: "task expired",
- },
- {
- name: "cancelled",
- body: `{"id":"cgt-cancelled","status":"cancelled","error":{"code":"Cancelled","message":"task cancelled"}}`,
- wantReason: "task cancelled",
- },
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- got, err := adaptor.ParseTaskResult([]byte(tc.body))
-
- require.NoError(t, err)
- require.Equal(t, model.TaskStatusFailure, got.Status)
- require.Equal(t, "100%", got.Progress)
- require.Equal(t, tc.wantReason, got.Reason)
- })
- }
- }
-
- func newTaskRequestContext(t *testing.T, body string) *gin.Context {
- t.Helper()
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- c.Request = httptest.NewRequest(http.MethodPost, "/api/v3/contents/generations/tasks", strings.NewReader(body))
- c.Request.Header.Set("Content-Type", "application/json")
- var req relaycommon.TaskSubmitReq
- require.NoError(t, common.Unmarshal([]byte(body), &req))
- relaycommon.StoreTaskRequest(c, &relaycommon.RelayInfo{}, constant.TaskActionGenerate, req)
- return c
- }
|