test(e2e): channels device-poll 真二进制端到端 + 修正插件 HTTP 前缀

TestChannelsDevicePollE2E 用真二进制跑通完整链路(对齐参考插件形态的终局验收):

  假平台上游(设备码/token/模型/对话端点)
  → 真网关进程 + plugin_dir(SeedBundled 落地 channels)
  → PUT /api/plugins/channels/kernel 授权(运维批准动作面)
  → call/set_endpoints 把 minimax 端点指到假上游(自建代理能力复用)
  → POST /api/plugins/http/channels/login 发起 device-poll
  → GET .../login/status 轮询:先 pending(authorization_pending),
    后 completed(token 下发)且 account 取 nickname
  → 终局证据:/v1/models 出现 channels-minimax 的模型 MiniMax-M3

调试中抓到并修复:channels.lua 三处旧挂载前缀 /api/plugins/channels/http/
(正确为 /api/plugins/http/,spawnBrowser 回调路径、callback redirect_uri、
UI fetch 基址)—— UI 上点登录/回调一直会 404。

顺带:e2e 就绪探针复用 startGateway(无源时 /v1/models 空列表也 200)。
This commit is contained in:
JianFeeeee
2026-10-11 21:44:40 +08:00
parent da0282a3db
commit 2f5cde7fea
2 changed files with 10 additions and 13 deletions

View File

@ -63,10 +63,7 @@ func TestChannelsDevicePollE2E(t *testing.T) {
// ---- 真二进制 + 配置(带 plugin_dir,让 bundled channels 落地 + 授权可持久化)----
dir := t.TempDir()
addr := mustAddr(t)
pluginDir := filepath.Join(dir, "plugins")
if err := os.MkdirAll(pluginDir, 0o755); err != nil {
t.Fatal(err)
}
pluginDir := filepath.Join(dir, "plugins") // 不预建:SeedBundled 见空目录才落地内置插件
cfg := "listen: " + addr + "\n" +
"gateway_keys:\n - sk-e2e-0001\n" +
"default_model: none\n" +
@ -89,12 +86,12 @@ func TestChannelsDevicePollE2E(t *testing.T) {
}
// 1) 授权内核动作(运维批准 add_source 等)+ 把 minimax 端点指到假上游。
st, _ := call("PUT", "/api/plugins/channels/kernel",
st, m := call("PUT", "/api/plugins/channels/kernel",
`{"actions":["add_source","status","list_sources","probe_all","reset_health"],"all_read":false}`)
if st != 200 {
t.Fatalf("授权内核动作失败: status=%d", st)
t.Fatalf("授权内核动作失败: status=%d body=%v", st, m)
}
st, m := call("POST", "/api/plugins/channels/call/set_endpoints", fmt.Sprintf(
st, m = call("POST", "/api/plugins/channels/call/set_endpoints", fmt.Sprintf(
`{"platform":"minimax","base_url":%q,"device":{"code_url":%q,"token_url":%q}}`,
plat.URL+"/v1", plat.URL+"/oauth2/device/code", plat.URL+"/oauth2/token"))
if st != 200 {
@ -102,7 +99,7 @@ func TestChannelsDevicePollE2E(t *testing.T) {
}
// 2) 发起 device-poll 登录:拿到会话 + user_code。
st, m = call("POST", "/api/plugins/channels/http/channels/login", `{"platform":"minimax"}`)
st, m = call("POST", "/api/plugins/http/channels/login", `{"platform":"minimax"}`)
if st != 200 {
t.Fatalf("start_login status=%d body=%v", st, m)
}
@ -118,7 +115,7 @@ func TestChannelsDevicePollE2E(t *testing.T) {
// 3) 第一次轮询:上游 pending → 会话仍 pending。
tokenScript.Store([]scriptStep{{code: 400, body: `{"error":"authorization_pending"}`}})
atomic.StoreInt32(&tokenHits, 0)
st, m = call("GET", "/api/plugins/channels/http/channels/login/status?id="+sid, "")
st, m = call("GET", "/api/plugins/http/channels/login/status?id="+sid, "")
if m["state"] != "pending" {
t.Fatalf("轮询1 应 pending, 实得 %v", m)
}
@ -128,7 +125,7 @@ func TestChannelsDevicePollE2E(t *testing.T) {
atomic.StoreInt32(&tokenHits, 0)
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
st, m = call("GET", "/api/plugins/channels/http/channels/login/status?id="+sid, "")
st, m = call("GET", "/api/plugins/http/channels/login/status?id="+sid, "")
if m["state"] == "completed" || m["state"] == "failed" {
break
}