mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
113 lines
5.9 KiB
Go
113 lines
5.9 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func seedanceTestAccount() *Account {
|
|
return &Account{ID: 9, Platform: PlatformOpenAI, Type: AccountTypeAPIKey, Credentials: map[string]any{
|
|
"api_key": "ark-secret", "base_url": "https://ark.cn-beijing.volces.com/api/v3",
|
|
"openai_capabilities": []string{"seedance"},
|
|
"model_mapping": map[string]any{"video": "ep-seedance"},
|
|
}}
|
|
}
|
|
|
|
func TestSeedanceNativeForwarding(t *testing.T) {
|
|
body := []byte(`{"model":"video","content":[{"type":"text","text":"waves"},{"type":"image_url","image_url":{"url":"https://example.com/first.png"},"role":"first_frame"},{"type":"audio_url","audio_url":{"url":"https://example.com/audio.mp3"}}],"duration":-1,"generate_audio":true,"future_field":{"keep":true}}`)
|
|
upstream := &grokMediaContentUpstreamStub{response: grokMediaContentStatusResponse(`{"id":"task-1"}`)}
|
|
svc := &OpenAIGatewayService{httpUpstream: upstream}
|
|
c, w := grokMediaContentTestContext(http.MethodPost, "/api/v3/contents/generations/tasks", nil)
|
|
result, err := svc.ForwardSeedance(context.Background(), c, seedanceTestAccount(), SeedanceEndpointCreate, "", body)
|
|
require.NoError(t, err)
|
|
require.JSONEq(t, `{"id":"task-1"}`, w.Body.String())
|
|
require.Equal(t, "seedance:task-1", result.ResponseID)
|
|
require.Zero(t, result.Usage.OutputTokens)
|
|
require.Equal(t, "video", result.BillingModel)
|
|
require.Equal(t, "ep-seedance", result.UpstreamModel)
|
|
require.Equal(t, "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks", upstream.request.URL.String())
|
|
require.Equal(t, "Bearer ark-secret", upstream.request.Header.Get("Authorization"))
|
|
forwarded, err := io.ReadAll(upstream.request.Body)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "ep-seedance", gjson.GetBytes(forwarded, "model").String())
|
|
for _, field := range []string{"content", "duration", "generate_audio", "future_field"} {
|
|
require.Equal(t, gjson.GetBytes(body, field).Raw, gjson.GetBytes(forwarded, field).Raw)
|
|
}
|
|
}
|
|
|
|
func TestSeedanceStatusAndDelete(t *testing.T) {
|
|
for _, status := range []string{"queued", "running", "failed", "cancelled", "expired", "succeeded"} {
|
|
t.Run(status, func(t *testing.T) {
|
|
body := `{"id":"task-1","status":"` + status + `","model":"ep-seedance","content":{"video_url":"https://cdn.example/video.mp4"},"usage":{"completion_tokens":12345}}`
|
|
upstream := &grokMediaContentUpstreamStub{response: grokMediaContentStatusResponse(body)}
|
|
svc := &OpenAIGatewayService{httpUpstream: upstream}
|
|
c, w := grokMediaContentTestContext(http.MethodGet, "/api/v3/contents/generations/tasks/task-1", nil)
|
|
result, err := svc.ForwardSeedance(context.Background(), c, seedanceTestAccount(), SeedanceEndpointStatus, "seedance:task-1", nil)
|
|
require.NoError(t, err)
|
|
require.JSONEq(t, body, w.Body.String())
|
|
require.Equal(t, "/api/v3/contents/generations/tasks/task-1", upstream.request.URL.Path)
|
|
if status == "succeeded" {
|
|
require.Equal(t, 12345, result.Usage.OutputTokens)
|
|
} else {
|
|
require.Zero(t, result.Usage.OutputTokens)
|
|
}
|
|
require.Zero(t, result.VideoCount, "must use token billing, not Grok seconds")
|
|
})
|
|
}
|
|
upstream := &grokMediaContentUpstreamStub{response: grokMediaContentStatusResponse("")}
|
|
upstream.response.StatusCode = http.StatusNoContent
|
|
svc := &OpenAIGatewayService{httpUpstream: upstream}
|
|
c, w := grokMediaContentTestContext(http.MethodDelete, "/api/v3/contents/generations/tasks/task-1", nil)
|
|
_, err := svc.ForwardSeedance(context.Background(), c, seedanceTestAccount(), SeedanceEndpointDelete, "seedance:task-1", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.MethodDelete, upstream.request.Method)
|
|
require.Equal(t, http.StatusNoContent, w.Code)
|
|
}
|
|
|
|
func TestSeedanceValidationAndCapability(t *testing.T) {
|
|
for _, body := range []string{`{`, `[]`, `{}`, `{"model":12,"content":[{}]}`, `{"model":"x","content":[]}`} {
|
|
_, err := ParseSeedanceRequest([]byte(body))
|
|
require.Error(t, err)
|
|
}
|
|
info, err := ParseSeedanceRequest([]byte(`{"model":"x","content":[{"type":"text","text":"first"},{"type":"text","text":"second"},{"type":"image_url","image_url":{"url":"https://example.com/a.png"}}]}`))
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(info.ModerationBody()), "first")
|
|
require.Contains(t, string(info.ModerationBody()), "second")
|
|
require.Contains(t, string(info.ModerationBody()), "https://example.com/a.png")
|
|
for _, id := range []string{"", "..", "a/b", "a?b", "a#b", "%2e%2e"} {
|
|
_, err := buildSeedanceURL("https://example.com", SeedanceEndpointStatus, id)
|
|
require.Error(t, err, id)
|
|
}
|
|
for _, base := range []string{"https://example.com", "https://example.com/api/v3/", "https://example.com/v3"} {
|
|
url, err := buildSeedanceURL(base, SeedanceEndpointCreate, "")
|
|
require.NoError(t, err)
|
|
require.NotContains(t, url, "/v3/api/v3")
|
|
}
|
|
a := seedanceTestAccount()
|
|
require.True(t, a.SupportsOpenAIEndpointCapability(OpenAIEndpointCapabilitySeedance))
|
|
a.Type = AccountTypeOAuth
|
|
require.False(t, a.SupportsOpenAIEndpointCapability(OpenAIEndpointCapabilitySeedance))
|
|
a.Type = AccountTypeAPIKey
|
|
delete(a.Credentials, "openai_capabilities")
|
|
require.False(t, a.SupportsOpenAIEndpointCapability(OpenAIEndpointCapabilitySeedance))
|
|
}
|
|
|
|
func TestSeedancePreservesUpstreamErrorsWithoutRetry(t *testing.T) {
|
|
upstream := &grokMediaContentUpstreamStub{response: grokMediaContentStatusResponse(`{"error":{"code":"QuotaExceeded","message":"quota exhausted"}}`)}
|
|
upstream.response.StatusCode = 429
|
|
svc := &OpenAIGatewayService{httpUpstream: upstream}
|
|
c, w := grokMediaContentTestContext(http.MethodPost, "/api/v3/contents/generations/tasks", nil)
|
|
_, err := svc.ForwardSeedance(context.Background(), c, seedanceTestAccount(), SeedanceEndpointCreate, "", []byte(`{"model":"video","content":[{"type":"text","text":"waves"}]}`))
|
|
require.Error(t, err)
|
|
require.Equal(t, 429, w.Code)
|
|
require.Contains(t, w.Body.String(), "QuotaExceeded")
|
|
require.Len(t, upstream.requests, 1)
|
|
}
|