package lua import ( "strings" "testing" ) // ★ P1-2:Cline 的推理端点要 `Authorization: Bearer workos:`。 // // 真实缺陷:参考实现(cline.ts)发头时直接拼 `workos:${token}`,而我们池里 // 存的是**第二步 register 换回来的 Cline token**(裸 token,不带前缀) // ⇒ 之前 channels-cline 干脆没有 build_headers,内核兜底发 // `Authorization: Bearer <裸token>` ⇒ 少了协议要求的前缀 ⇒ 上游只回泛化的 // 认证失败(与 trae 修前同型:头发了,但形式不对)。 // // 判据咬**真实产出**(vm.BuildHeaders 就是内核发请求前走的那条路径), // 不造 HTTP 假上游(那需要 URL 覆盖/凭据 ref/路径匹配全部对齐,任一环错 // 都会让「没发请求」伪装成「头不对」,plan §3 第 4 条)。 func TestClineBuildHeadersAddsWorkOSPrefix(t *testing.T) { vm, ps, _ := runtimePlugins(t) src, err := bundledAdapters.ReadFile("plugins/channels.lua") if err != nil { t.Fatal(err) } if err := ps.LoadSource("channels", string(src)); err != nil { t.Fatal(err) } if !vm.HasFn("channels-cline", "build_headers") { t.Fatal("★ channels-cline 没有 build_headers —— 内核会兜底发裸 Bearer," + "而 Cline 推理端点要 `Bearer workos:`") } cases := []struct { name string tok string want string }{ { name: "无前缀 → 补上 workos:", tok: "eyJhbGciOiJIUzI1NiJ9.jwt-payload.sig", want: "Bearer workos:eyJhbGciOiJIUzI1NiJ9.jwt-payload.sig", }, { name: "已有 workos: 前缀 → 不重复拼", tok: "workos:eyJhbGciOiJIUzI1NiJ9.jwt-payload.sig", want: "Bearer workos:eyJhbGciOiJIUzI1NiJ9.jwt-payload.sig", }, { name: "前缀大小写不同(WorkOS:)→ 视为已带,不重复拼", tok: "WorkOS:abc123", want: "Bearer WorkOS:abc123", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { hdrs, err := vm.BuildHeaders("channels-cline", map[string]interface{}{ "api_key": tc.tok, "url": "https://api.cline.bot/api/v1/chat/completions", "method": "POST", "body": `{"model":"x"}`, }) if err != nil { t.Fatalf("BuildHeaders: %v", err) } auth := hdrs["Authorization"] if auth != tc.want { t.Fatalf("★ Authorization 应为 %q,实得 %q\n"+ " Cline 要 `Bearer workos:`;缺前缀或重复拼都会认证失败", tc.want, auth) } // 不得出现 workos:workos: 这种重复前缀(旧数据可能已带前缀) if strings.Count(strings.ToLower(auth), "workos:") != 1 { t.Fatalf("★ workos: 前缀出现 %d 次(必须恰好 1 次): %q", strings.Count(strings.ToLower(auth), "workos:"), auth) } t.Logf("✓ %s → %s", tc.name, auth) }) } } // 配对判据:没有凭据时不得发出 `Bearer workos:` 这种空凭据头 // (空凭据头会让上游回认证失败,而真因是「没配置 token」,会把人带偏)。 func TestClineBuildHeadersWithoutToken(t *testing.T) { vm, ps, _ := runtimePlugins(t) src, err := bundledAdapters.ReadFile("plugins/channels.lua") if err != nil { t.Fatal(err) } if err := ps.LoadSource("channels", string(src)); err != nil { t.Fatal(err) } hdrs, err := vm.BuildHeaders("channels-cline", map[string]interface{}{}) if err != nil { t.Fatalf("BuildHeaders 不该因缺凭据报错: %v", err) } if auth, has := hdrs["Authorization"]; has { t.Fatalf("★ 无凭据时不得发 Authorization(会变成 `Bearer workos:`),实得 %q", auth) } } // ★ 单一实现:前缀规则只在 with_workos_prefix 里写一次。 // 判据直接咬那个函数(经适配器导出),确保 build_headers 用的是同一份逻辑 —— // 若哪天有人把规则复制到 build_headers 里内联、而导出版留着旧的,这条会红。 func TestClineWorkOSPrefixHelperIsSingleSource(t *testing.T) { vm, ps, _ := runtimePlugins(t) src, err := bundledAdapters.ReadFile("plugins/channels.lua") if err != nil { t.Fatal(err) } if err := ps.LoadSource("channels", string(src)); err != nil { t.Fatal(err) } if !vm.HasFn("channels-cline", "with_workos_prefix") { t.Fatal("★ channels-cline 应导出 with_workos_prefix 供判据咬同一份实现" + "(否则判据只能自算前缀规则,真实函数被改坏时会继续绿)") } // 用适配器池里真实的那份脚本再算一遍,与 build_headers 的产出对照。 // 这里直接调 Transform 不方便(不是 transform 钩子),故用 build_headers // 的两种输入反推:带前缀的 token 必须**原样**出现在 Authorization 里。 hdrs, err := vm.BuildHeaders("channels-cline", map[string]interface{}{ "api_key": "workos:XYZ", }) if err != nil { t.Fatal(err) } if got := hdrs["Authorization"]; got != "Bearer workos:XYZ" { t.Fatalf("已带前缀的 token 必须原样透传,实得 %q", got) } }