test(acceptance): 验证厂商三类额度与优先路由
为 provider-burst 固定三平台并发、RPM、TPM 和优先级组合,使用稳定 token 用量的本地视频负载验证同级按容量分流及低优先级溢出。\n\n报告升级为 v2,强制校验三类额度峰值精确命中上限、不越界、队列回收、三 Worker 分发以及重复提交和回调安全。\n\n验证:\n- env -u AI_GATEWAY_TEST_DATABASE_URL go test ./... -count=1\n- go vet ./...\n- bash -n scripts/acceptance/provider-burst.sh scripts/acceptance/run-local-acceptance.sh\n- shellcheck -x scripts/acceptance/provider-burst.sh scripts/acceptance/run-local-acceptance.sh
This commit is contained in:
@@ -279,7 +279,7 @@ func parseOptions() (options, error) {
|
||||
return options{}, errors.New("requests must be between 0 and 10000")
|
||||
}
|
||||
switch opts.profile {
|
||||
case "simulated-smoke", "simulated-all", "gemini-baseline", "gemini-multi-image", "gemini-large", "gemini-peak", "video-throughput", "video-capacity", "video-recovery",
|
||||
case "simulated-smoke", "simulated-all", "gemini-baseline", "gemini-multi-image", "gemini-large", "gemini-peak", "video-throughput", "video-capacity", "video-provider-quota", "video-recovery",
|
||||
"mixed-soak", "mixed-overload":
|
||||
if opts.emulatorURL == "" {
|
||||
return options{}, errors.New("AI_GATEWAY_ACCEPTANCE_EMULATOR_URL is required for simulated profiles")
|
||||
@@ -371,6 +371,12 @@ func run(ctx context.Context, opts options) ([]phaseResult, error) {
|
||||
requests = opts.requestCount
|
||||
}
|
||||
result = runVideo(ctx, client, opts, name, opts.shardRequestCount(requests), false, opts.emulatorFixtureURLs(), false, opts.shardIndex, opts.shardCount)
|
||||
case "video-provider-quota":
|
||||
requests := 24
|
||||
if opts.requestCount > 0 {
|
||||
requests = opts.requestCount
|
||||
}
|
||||
result = runVideo(ctx, client, opts, name, opts.shardRequestCount(requests), false, opts.emulatorFixtureURLs(), false, opts.shardIndex, opts.shardCount)
|
||||
case "video-recovery":
|
||||
result = runVideo(ctx, client, opts, name, opts.shardRequestCount(96), true, opts.emulatorFixtureURLs(), false, opts.shardIndex, opts.shardCount)
|
||||
case "mixed-soak", "mixed-overload":
|
||||
@@ -657,18 +663,21 @@ func runVideo(
|
||||
if len(combo) > imageCount {
|
||||
combo = combo[:imageCount]
|
||||
}
|
||||
if !realUpstream && logicalIndex%4 == 0 {
|
||||
if !realUpstream && name != "video-provider-quota" && logicalIndex%4 == 0 {
|
||||
combo[0] = imageURLs[12+(logicalIndex/4)%4]
|
||||
}
|
||||
content := make([]any, 0, len(combo)+1)
|
||||
prompt := "多参考图生成连续运镜视频,保持人物、服装和场景一致"
|
||||
if name == "video-provider-quota" {
|
||||
prompt = "acceptance-provider-quota"
|
||||
}
|
||||
if longRun {
|
||||
prompt += " acceptance-long-recovery"
|
||||
}
|
||||
if name == "video-capacity" {
|
||||
prompt += " acceptance-capacity-ladder"
|
||||
}
|
||||
if !realUpstream && logicalIndex%4 == 0 {
|
||||
if !realUpstream && name != "video-provider-quota" && logicalIndex%4 == 0 {
|
||||
prompt += " acceptance-force-conversion"
|
||||
}
|
||||
content = append(content, map[string]any{"type": "text", "text": prompt})
|
||||
@@ -716,7 +725,7 @@ func runVideo(
|
||||
err = withOperation("video_submit", err)
|
||||
mu.Lock()
|
||||
latencies[index] = time.Since(requestStarted)
|
||||
if !realUpstream && logicalIndex%4 == 0 {
|
||||
if !realUpstream && name != "video-provider-quota" && logicalIndex%4 == 0 {
|
||||
forcedConversions++
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -78,11 +78,12 @@ type Report struct {
|
||||
}
|
||||
|
||||
type videoTask struct {
|
||||
ID string
|
||||
Model string
|
||||
CreatedAt time.Time
|
||||
ReadyAt time.Time
|
||||
ImageRefs []imageReference
|
||||
ID string
|
||||
Model string
|
||||
CreatedAt time.Time
|
||||
ReadyAt time.Time
|
||||
UsageTokens int
|
||||
ImageRefs []imageReference
|
||||
}
|
||||
|
||||
type imageReference struct {
|
||||
@@ -243,7 +244,7 @@ func (s *Server) submitVideo(w http.ResponseWriter, r *http.Request) {
|
||||
id := "acceptance-video-" + strconv.FormatUint(s.nextID.Add(1), 10)
|
||||
task := videoTask{
|
||||
ID: id, Model: strings.TrimSpace(stringValue(body["model"])),
|
||||
CreatedAt: s.now(), ReadyAt: s.now().Add(delay), ImageRefs: refs,
|
||||
CreatedAt: s.now(), ReadyAt: s.now().Add(delay), UsageTokens: videoUsageTokens(body), ImageRefs: refs,
|
||||
}
|
||||
s.mu.Lock()
|
||||
if existingID := s.videoByIdempotency[idempotencyKey]; existingID != "" {
|
||||
@@ -298,7 +299,7 @@ func (s *Server) getVideo(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"id": id, "model": task.Model, "status": status, "content": content,
|
||||
"created_at": task.CreatedAt.Unix(),
|
||||
"usage": map[string]any{"completion_tokens": 1, "total_tokens": 1},
|
||||
"usage": map[string]any{"completion_tokens": task.UsageTokens, "total_tokens": task.UsageTokens},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -674,12 +675,22 @@ func videoDelay(body map[string]any, longRun bool) time.Duration {
|
||||
if longRun {
|
||||
return 2*time.Minute + time.Duration(seed%61)*time.Second
|
||||
}
|
||||
if videoPromptContains(body, "acceptance-provider-quota") {
|
||||
return 5*time.Second + time.Duration(seed%6)*time.Second
|
||||
}
|
||||
if videoPromptContains(body, "acceptance-capacity-ladder") {
|
||||
return 30*time.Second + time.Duration(seed%16)*time.Second
|
||||
}
|
||||
return 5*time.Second + time.Duration(seed%11)*time.Second
|
||||
}
|
||||
|
||||
func videoUsageTokens(body map[string]any) int {
|
||||
if videoPromptContains(body, "acceptance-provider-quota") {
|
||||
return 7
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func videoPromptContains(body map[string]any, marker string) bool {
|
||||
content, _ := body["content"].([]any)
|
||||
for _, raw := range content {
|
||||
|
||||
@@ -242,6 +242,23 @@ func TestVideoCapacityDelay(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVideoProviderQuotaDelayAndUsage(t *testing.T) {
|
||||
body := map[string]any{
|
||||
"seed": json.Number("5"),
|
||||
"content": []any{map[string]any{
|
||||
"type": "text",
|
||||
"text": "acceptance-provider-quota",
|
||||
}},
|
||||
}
|
||||
delay := videoDelay(body, false)
|
||||
if delay < 5*time.Second || delay > 10*time.Second {
|
||||
t.Fatalf("video provider quota delay=%s, want 5s..10s", delay)
|
||||
}
|
||||
if tokens := videoUsageTokens(body); tokens != 7 {
|
||||
t.Fatalf("video provider quota usage tokens=%d, want 7", tokens)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolcesProtocolAcceptsThreeSixAndNineReferenceImages(t *testing.T) {
|
||||
server := New(Config{Wait: func(context.Context, time.Duration) error { return nil }})
|
||||
httpServer := httptest.NewServer(server.Handler())
|
||||
|
||||
@@ -134,3 +134,15 @@ func TestReservationsFromPolicySkipsNonPositiveLimits(t *testing.T) {
|
||||
t.Fatalf("expected concurrent reservation with limit 2, got %+v", reservations[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderQuotaPromptReservesSevenTokens(t *testing.T) {
|
||||
body := map[string]any{
|
||||
"content": []any{map[string]any{
|
||||
"type": "text",
|
||||
"text": "acceptance-provider-quota",
|
||||
}},
|
||||
}
|
||||
if got := estimateRequestTokens(body); got != 7 {
|
||||
t.Fatalf("provider quota prompt tokens=%d, want 7", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user