116 lines
3.8 KiB
Go
116 lines
3.8 KiB
Go
package helper
|
||
|
||
import (
|
||
"net/http/httptest"
|
||
"strconv"
|
||
"testing"
|
||
|
||
"github.com/QuantumNous/new-api/common"
|
||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||
"github.com/QuantumNous/new-api/setting/ratio_setting"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
// 与用户提供的 playground JSON 等价(字段语义一致),用于演练 ModelPriceHelperVideo 的估值链路。
|
||
func seedancePlaygroundTaskReq(t *testing.T) relaycommon.TaskSubmitReq {
|
||
t.Helper()
|
||
const payload = `{
|
||
"model": "Seedance2.0",
|
||
"prompt": "生成一个小猫嗷嗷叫的视频",
|
||
"n": 1,
|
||
"size": "960x540",
|
||
"fps": 24,
|
||
"duration": 5,
|
||
"motion": 0.6,
|
||
"negative_prompt": "",
|
||
"seed": null,
|
||
"images": []
|
||
}`
|
||
var req relaycommon.TaskSubmitReq
|
||
require.NoError(t, common.UnmarshalJsonStr(payload, &req))
|
||
return req
|
||
}
|
||
|
||
// TestSeedancePlaygroundJSON_PerSecondQuota 演示:若全局为 Seedance2.0 配置了「文生视频按秒」规则,
|
||
// 则预扣额度 = ceil(duration)×每秒单价(USD)×QuotaPerUnit×分组倍率×渠道折扣。
|
||
// 单价需替换为你们控制台真实配置;此处用 0.01 USD/秒便于断言公式。
|
||
func TestSeedancePlaygroundJSON_PerSecondQuota(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
|
||
prevRules := ratio_setting.VideoPricingRules2JSONString()
|
||
defer func() { _ = ratio_setting.UpdateVideoPricingRulesByJSONString(prevRules) }()
|
||
|
||
const pricePerSecUSD = 0.01
|
||
cfg := `{"Seedance2.0":{"text_to_video_per_second":[{"resolution":"540p","has_audio":false,"price":` +
|
||
strconv.FormatFloat(pricePerSecUSD, 'g', -1, 64) + `}]}}`
|
||
require.NoError(t, ratio_setting.UpdateVideoPricingRulesByJSONString(cfg))
|
||
|
||
w := httptest.NewRecorder()
|
||
c, _ := gin.CreateTestContext(w)
|
||
|
||
req := seedancePlaygroundTaskReq(t)
|
||
c.Set("task_request", req)
|
||
|
||
relayInfo := &relaycommon.RelayInfo{
|
||
UserGroup: "test-group",
|
||
UsingGroup: "default",
|
||
OriginModelName: req.Model,
|
||
}
|
||
|
||
pd, err := ModelPriceHelperVideo(c, relayInfo)
|
||
require.NoError(t, err)
|
||
|
||
ctx := estimateVideoRequestContext(c)
|
||
require.Equal(t, videoBillingModeTextToVideo, ctx.Mode, "images 为空应为文生视频")
|
||
require.Equal(t, 960, ctx.Width)
|
||
require.Equal(t, 540, ctx.Height)
|
||
require.Equal(t, 5, ctx.DurationSec)
|
||
|
||
sec := 5
|
||
if ctx.DurationSec > 0 {
|
||
sec = ctx.DurationSec
|
||
}
|
||
want := int(common.QuotaPerUnit * float64(sec) * pricePerSecUSD * pd.GroupRatioInfo.GroupRatio)
|
||
require.Equal(t, want, pd.Quota,
|
||
"应与 ceil(秒)×单价×QuotaPerUnit×group 一致(渠道 id=0 无折扣)")
|
||
|
||
t.Logf("estimate ctx: mode=%s WxH=%dx%d durationSec=%d fps(estimate)=%d inputTextTokens≈%d",
|
||
ctx.Mode, ctx.Width, ctx.Height, ctx.DurationSec, ctx.FPS, ctx.InputTextTokens)
|
||
t.Logf("preconsume quota=%d (QuotaPerUnit=%.0f groupRatio=%.4f)",
|
||
pd.Quota, common.QuotaPerUnit, pd.GroupRatioInfo.GroupRatio)
|
||
}
|
||
|
||
func TestPickAudioPriceByResolution_RoundsUpPseudo540p(t *testing.T) {
|
||
rows := []ratio_setting.VideoResolutionAudioPriceRule{
|
||
{Resolution: "480p", HasAudio: false, Price: 9},
|
||
{Resolution: "540p", HasAudio: false, Price: 8},
|
||
{Resolution: "720p", HasAudio: false, Price: 10},
|
||
}
|
||
|
||
price, ok := pickAudioPriceByResolution(videoEstimateContext{
|
||
Width: 864,
|
||
Height: 496,
|
||
}, false, rows)
|
||
|
||
require.True(t, ok)
|
||
require.Equal(t, 8.0, price, "864x496 exceeds 480p bounds, so it should round up to 540p")
|
||
}
|
||
|
||
func TestPickAudioPriceByResolution_UsesTargetAspectRatio(t *testing.T) {
|
||
rows := []ratio_setting.VideoResolutionAudioPriceRule{
|
||
{Resolution: "480p", HasAudio: false, Price: 9},
|
||
{Resolution: "540p", HasAudio: false, Price: 8},
|
||
{Resolution: "720p", HasAudio: false, Price: 10},
|
||
}
|
||
|
||
price, ok := pickAudioPriceByResolution(videoEstimateContext{
|
||
Width: 1120,
|
||
Height: 480,
|
||
}, false, rows)
|
||
|
||
require.True(t, ok)
|
||
require.Equal(t, 9.0, price, "21:9 1120x480 should fit the 480p tier for that aspect ratio")
|
||
}
|