fix: 修复请求日志丢失、轮询日志显示和开关动画问题 (#450)

* fix(logs): 请求明细 - 隐藏轮询状态查询

* fix(ui): 开关滑块 - 恢复状态位移与过渡动画

* fix(protocol): 声明式任务 - 创建请求生成幂等 UUID

* fix(logs): 请求日志 - 辅助状态更新失败时仍保留记录
This commit is contained in:
Rou
2026-09-09 06:48:26 +08:00
committed by GitHub
parent 59ecfec3e0
commit 6f7a0674ce
7 changed files with 116 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
@@ -344,6 +345,68 @@ func TestDeclarativeProtocolRuntimeExecutesCreatePollAndDownload(t *testing.T) {
}
}
func TestDeclarativeProtocolRuntimeGeneratesPerCreateIdempotencyKey(t *testing.T) {
manifest := []byte(`{
"apiVersion":"yingce.plugin/v1",
"id":"test-idempotency-key-runtime","version":"1.0.0","name":"Test Idempotency Key","author":"Test","documentation":"# Test Idempotency Key",
"contributes":{"providers":[{"id":"test-idempotency-key-runtime","label":"Test Idempotency Key","capabilities":["video"],"scopes":["canvas"],"create":{"method":"POST","path":"/tasks","headers":{"Idempotency-Key":{"$ref":"request.extra.idempotencyKey"}},"body":{"model":{"$ref":"request.model"},"prompt":{"$ref":"request.prompt"}}},"poll":{"method":"GET","path":"/tasks/{{taskId}}"},"response":{"taskIdPaths":["id"],"statusPaths":["status"],"resultPaths":["video_url"],"resultKind":"video"}}]}
}`)
center, err := newPluginRuntime(t.TempDir())
if err != nil {
t.Fatal(err)
}
if _, err := center.install(testPluginPackage(t, manifest), "test-idempotency-key-runtime.yingce-plugin"); err != nil {
t.Fatal(err)
}
var keys []string
var server *httptest.Server
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/v1/tasks":
key := r.Header.Get("Idempotency-Key")
if !regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`).MatchString(key) {
t.Errorf("Idempotency-Key = %q, want UUID", key)
}
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatalf("decode create body: %v", err)
}
if _, ok := body["idempotencyKey"]; ok {
t.Error("host idempotency key leaked into provider JSON body")
}
keys = append(keys, key)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"task-1","status":"pending"}`))
case "/v1/tasks/task-1":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"task-1","status":"succeeded","video_url":"` + server.URL + `/media.mp4"}`))
case "/media.mp4":
w.Header().Set("Content-Type", "video/mp4")
_, _ = w.Write([]byte("video"))
default:
http.NotFound(w, r)
}
}))
defer server.Close()
config := providerConfig{BaseURL: server.URL + "/v1", APIKey: "key", Model: "test-model", APIFormat: "openai", InterfaceType: "test-idempotency-key-runtime", AllowLocalChannel: true}
ctx := withProviderOutboundPolicy(context.Background(), config)
ctx = withProtocolRegistry(ctx, center.registrySnapshot())
for i := 0; i < 2; i++ {
result, err := runDeclarativeProtocolTask(ctx, canvasGenerationInput{Mode: "video", Prompt: "a clip", Config: config})
if err != nil {
t.Fatal(err)
}
if result["mode"] != "video" {
t.Fatalf("result = %#v", result)
}
}
if len(keys) != 2 || keys[0] == keys[1] {
t.Fatalf("create idempotency keys = %#v, want two different UUIDs", keys)
}
}
func TestDeclarativeNewAPIChannel2TaskNotExistRetry(t *testing.T) {
adapter := newDeclarativeNewAPIChannel2TestAdapter(t)
createCalls := 0