158 lines
7.2 KiB
Go
158 lines
7.2 KiB
Go
package plugins_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/pkg/jsplugin"
|
|
builtinplugins "github.com/QuantumNous/new-api/plugins"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestViduResponsesProtocol(t *testing.T) {
|
|
testVideoResponsesProtocol(t, videoResponsesTestCase{
|
|
pluginKey: "vidu",
|
|
model: "viduq2",
|
|
requestBody: map[string]any{
|
|
"model": "viduq2",
|
|
"input": []any{map[string]any{"role": "user", "content": []any{
|
|
map[string]any{"type": "input_text", "text": "move between frames"},
|
|
map[string]any{"type": "input_image", "image_url": "https://cdn.example/first.png"},
|
|
map[string]any{"type": "input_image", "image_url": "https://cdn.example/last.png"},
|
|
}}},
|
|
"seconds": 8,
|
|
"size": "720p",
|
|
},
|
|
wantAction: "first_tail_to_video",
|
|
wantRequest: map[string]any{
|
|
"model": "viduq2",
|
|
"prompt": "move between frames",
|
|
"images": []any{"https://cdn.example/first.png", "https://cdn.example/last.png"},
|
|
"duration": float64(8),
|
|
"size": "720p",
|
|
},
|
|
wantUsageKeys: []string{"credits", "duration", "resolution"},
|
|
wantSubmitUsageKeys: []string{"duration", "resolution"},
|
|
wantVendorName: "vidu",
|
|
})
|
|
}
|
|
|
|
// metadata carries Vidu's other fields but cannot replace the model, duration
|
|
// or resolution the request is billed for.
|
|
func TestViduMetadataCannotChangeWhatIsBilled(t *testing.T) {
|
|
source, err := builtinplugins.Source("vidu")
|
|
require.NoError(t, err)
|
|
plugin, err := jsplugin.NewRegistry().RegisterFactory(source, jsplugin.Options{Key: "vidu"})
|
|
require.NoError(t, err)
|
|
ctx := map[string]any{"baseUrl": "https://api.vidu.example", "apiKey": "vendor-key", "model": "vidu1.5", "upstreamModel": "vidu1.5",
|
|
"requestBody": map[string]any{"model": "vidu1.5", "prompt": "p", "duration": 4, "resolution": "360p",
|
|
"metadata": map[string]any{"model": "viduq2", "duration": 8, "resolution": "1080p", "seed": 7}}}
|
|
descriptor, err := plugin.Engine.Call(t.Context(), "buildSubmitRequest", ctx)
|
|
require.NoError(t, err)
|
|
usage, err := plugin.Engine.Call(t.Context(), "extractUsage", ctx)
|
|
require.NoError(t, err)
|
|
body := decodePluginValue(t, descriptor)["body"].(map[string]any)
|
|
assert.Equal(t, map[string]any{"duration": float64(4), "resolution": "360p"}, decodePluginValue(t, usage))
|
|
assert.Equal(t, "vidu1.5", body["model"])
|
|
assert.Equal(t, float64(4), body["duration"])
|
|
assert.Equal(t, "360p", body["resolution"])
|
|
assert.Equal(t, float64(7), body["seed"])
|
|
}
|
|
|
|
// A New API channel reaches Vidu through another gateway's /vidu routes. The
|
|
// gateway rebuilds the request this driver sent, so Vidu receives one body and
|
|
// both gateways bill the same facts, and the gateway's task reads back as the
|
|
// same state and credits.
|
|
func TestViduThroughNewAPIGateway(t *testing.T) {
|
|
source, err := builtinplugins.Source("vidu")
|
|
require.NoError(t, err)
|
|
plugin, err := jsplugin.NewRegistry().RegisterFactory(source, jsplugin.Options{Key: "vidu"})
|
|
require.NoError(t, err)
|
|
call := func(export string, members []string, args ...any) map[string]any {
|
|
t.Helper()
|
|
value, err := plugin.Engine.CallPath(t.Context(), export, members, args...)
|
|
require.NoError(t, err)
|
|
return decodePluginValue(t, value)
|
|
}
|
|
gateway := map[string]any{"kind": "new_api"}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
requestBody map[string]any
|
|
route string
|
|
}{
|
|
{
|
|
name: "text to video with vendor fields",
|
|
requestBody: map[string]any{"model": "viduq2", "prompt": "p", "duration": 8, "size": "1280x720",
|
|
"metadata": map[string]any{"bgm": true, "seed": 7, "aspect_ratio": "9:16"}},
|
|
route: "/vidu/ent/v2/text2video",
|
|
},
|
|
{
|
|
name: "two reference images stay a reference request",
|
|
requestBody: map[string]any{"model": "viduq2", "prompt": "p", "images": []any{"https://cdn.example/a.png", "https://cdn.example/b.png"},
|
|
"metadata": map[string]any{"action": "referenceGenerate"}},
|
|
route: "/vidu/ent/v2/reference2video",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
downstream := map[string]any{"requestBody": tc.requestBody, "model": "viduq2", "upstreamModel": "viduq2",
|
|
"baseUrl": "https://gateway.example", "apiKey": "sk-gateway", "upstream": gateway}
|
|
sent := call("buildSubmitRequest", nil, downstream)
|
|
assert.Equal(t, "https://gateway.example"+tc.route, sent["url"])
|
|
assert.Equal(t, map[string]any{"Accept": "application/json", "Content-Type": "application/json", "Authorization": "Bearer sk-gateway"}, sent["headers"])
|
|
|
|
intent := call("native", []string{"createTask"}, map[string]any{"path": tc.route, "body": map[string]any{"kind": "json", "value": sent["body"]}})
|
|
assert.Equal(t, sent["action"], intent["action"])
|
|
upstream := map[string]any{"requestBody": intent["requestBody"], "model": "viduq2", "upstreamModel": "viduq2",
|
|
"baseUrl": "https://api.vidu.example", "apiKey": "vendor-key", "upstream": map[string]any{"kind": "vendor"}}
|
|
forwarded := call("buildSubmitRequest", nil, upstream)
|
|
assert.Equal(t, "https://api.vidu.example"+strings.TrimPrefix(tc.route, "/vidu"), forwarded["url"])
|
|
assert.Equal(t, sent["body"], forwarded["body"])
|
|
assert.Equal(t, call("extractUsage", nil, downstream), call("extractUsage", nil, upstream))
|
|
})
|
|
}
|
|
|
|
created := call("native", []string{"taskCreated"}, map[string]any{}, map[string]any{"task_id": "task_pub", "status": "SUBMITTED",
|
|
"data": map[string]any{"task_id": "task_pub", "state": "created", "credits": 10}})
|
|
assert.Equal(t, "task_pub", call("parseSubmitResponse", nil, map[string]any{}, map[string]any{"body": created})["taskId"])
|
|
|
|
video := "https://cdn.vidu.example/v.mp4"
|
|
for _, tc := range []struct {
|
|
name string
|
|
task map[string]any
|
|
wantStatus string
|
|
wantReason string
|
|
}{
|
|
{
|
|
name: "before the first poll", wantStatus: "SUBMITTED",
|
|
task: map[string]any{"status": "SUBMITTED", "data": map[string]any{"task_id": "task_pub", "state": "created", "credits": 10}},
|
|
},
|
|
{
|
|
name: "succeeded", wantStatus: "SUCCESS",
|
|
task: map[string]any{"status": "SUCCESS", "data": map[string]any{"id": "task_pub", "state": "success", "credits": 12,
|
|
"creations": []any{map[string]any{"id": "c1", "url": video}}}},
|
|
},
|
|
{
|
|
name: "failed by the gateway poller", wantStatus: "FAILURE", wantReason: "task timed out",
|
|
task: map[string]any{"status": "FAILURE", "fail_reason": "task timed out", "data": map[string]any{"id": "task_pub", "state": "processing"}},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc.task["task_id"] = "task_pub"
|
|
rendered := call("native", []string{"taskStatus"}, map[string]any{}, tc.task)
|
|
result := call("parseTaskResult", nil, map[string]any{}, rendered)
|
|
assert.Equal(t, tc.wantStatus, result["status"])
|
|
assert.Equal(t, tc.wantReason, common.Interface2String(result["reason"]))
|
|
if tc.wantStatus != "SUCCESS" {
|
|
return
|
|
}
|
|
assert.Equal(t, map[string]any{"credits": float64(12)}, call("extractUsageOnComplete", nil, nil, nil, rendered))
|
|
content := call("buildContentRequest", nil, map[string]any{"artifactKey": "video", "data": rendered, "upstream": gateway,
|
|
"clientRequest": map[string]any{"method": "GET", "headers": map[string]any{}}})
|
|
assert.Equal(t, map[string]any{"url": video, "method": "GET", "credentialless": true}, content)
|
|
})
|
|
}
|
|
}
|