Files
Cyrene_v2/cmd/cyrene/websearch_new_providers_test.go
AskaEth ce9af42945
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(search): 新增腾讯云·元宝搜索与秘塔搜索两家 provider
接入位表六家扩到八家,端点形状与错误码一字未改(契约 v0.87,仅两处名单)。

- tencent_wsa:POST {base}/SearchPro + Authorization Bearer,零签名。
  两条官方口径决定实现:HTTP 状态码恒 200(判失败只能读 Response.Error.Code);
  Pages 是「JSON 字符串数组」需二次解析。错误码按官方表分类
  (UnauthorizedOperation/AuthFailure.* = 凭据、ResourceUnavailable = 欠费、
  RequestLimitExceeded* = 限流),文案只带 code 不带上游 Message。
- metaso:POST {base}/api/v1/search,响应 schema 仅第三方来源且互相打架,
  实现走宽松解析(数组候选与字段名回退),逐处标「未实测」。
- 零新 env 键:两家只走 $DATA_DIR/config/plugins/web-search.json,
  .env / .env.example 未动;组合根 websearch_providers.go 零功能改动。
- 契约 v0.87:values.provider 合法闭集与 plugin.providers 候选各加两条,
  计数不变(14 + 25 + 40 = 79)。
2026-10-07 13:42:41 +08:00

92 lines
3.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
// 本文件钉住本片新增的两家(`tencent_wsa` / `metaso`)在**组合根里真的接通了**:
// 它们**没有 `.env` 字段**(`.env` 已冻结),因此凭据与地址只能来自面板文件
// `$DATA_DIR/config/plugins/web-search.json`,而组合根**零改动**地按接入位表把它们投影出来
// (`resolveSearchProviders` 逐家走 `search.Descriptors()`)。
//
// 为什么值得端到端一条:`cmd/cyrene/websearch_providers.go` 里**一行都没改**——这正是
// 「按接入位表投影」的设计意图;没有这条用例,「零改动」就只是一句没有证据的话。
//
// 夹具(假上游、`wsSurfaceApp`、`wsExecuteSearch`)在 websearch_testenv_test.go /
// websearch_projection_test.go;`.env` 字段表的完整性由 wire_web_search_*_test.go 钉住。
import (
"net/http"
"strings"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
"git.yeij.top/AskaEth/Cyrene_v2/plugins/tools/io/search"
)
// TestWebSearchPanelOnlyProvidersWiredEndToEnd 是判据:面板文件里配好一家**没有 env 字段**
// 的家 ⇒ 工具进注册面 ⇒ 真的把请求打到面板里那个地址、带上面板里那把 key,且结果里如实
// 标注 provider。
func TestWebSearchPanelOnlyProvidersWiredEndToEnd(t *testing.T) {
t.Parallel()
cases := []struct {
name string
path string
fixture string
}{
{
name: search.NameTencentWSA,
path: "/SearchPro",
// Pages 的元素是 JSON **字符串**(官方 Array of String 形状)。
fixture: `{"Response":{"Pages":["{\"title\":\"元宝结果\",\"url\":\"https://t.example/1\",` +
`\"passage\":\"元宝摘要\"}"],"Query":"昔涟","RequestId":"r"}}`,
},
{
name: search.NameMetaso,
path: "/api/v1/search",
fixture: `{"webpages":[{"title":"秘塔结果","link":"https://m.example/1","snippet":"秘塔摘要"}]}`,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
const key = "test-panel-only-key"
up := newWSFakeUpstream(t, http.StatusOK, tc.fixture)
// `.env` 只给「谁当主家」:这两家在 `.env` 里没有字段,凭据与地址只能来自面板文件。
cfg := wsLoadConfig(t, "WEB_SEARCH_PROVIDER="+tc.name)
// 前提:它确实不在 `.env` 字段表里(否则这条用例会退化成既有 6 家的回归)。
for _, p := range searchProviderVector(cfg.WebSearch) {
if strings.EqualFold(p.Name, tc.name) {
t.Fatalf("前提被破坏:%s 不该出现在 .env 的字段表里", tc.name)
}
}
// 前提:接入位表里确有它(否则组合根根本投影不出来)。
if !search.Known(tc.name) {
t.Fatalf("前提被破坏:%s 应已在接入位表里登记", tc.name)
}
wsSavePanel(t, cfg, runtimeconfig.WebSearchFile{
Provider: tc.name,
Providers: []runtimeconfig.ProviderEntry{
{Provider: tc.name, APIKey: key, BaseURL: up.url, Enabled: runtimeconfig.EnableFlag(true)},
},
})
a := wsSurfaceApp(t, cfg, nil)
if !wsHasTool(a, webSearchToolName) {
t.Fatalf("面板文件里配好的 %s 应让工具进注册面,实际工具面 = %v", tc.name, toolNames(a))
}
if got := wsExecuteSearch(t, a, "昔涟"); got["provider"] != tc.name {
t.Fatalf("provider = %v, want %q(如实标注实际应答的家)", got["provider"], tc.name)
}
select {
case r := <-up.hits:
if r.URL.Path != tc.path {
t.Errorf("请求路径 = %q, want %q(各家固定路径)", r.URL.Path, tc.path)
}
if got := r.Header.Get("Authorization"); got != "Bearer "+key {
t.Errorf("Authorization = %q,want 面板文件里那把 key(凭据必须以文件为准)", got)
}
default:
t.Fatalf("%s 一个请求都没收到 —— 面板里的配置没接到 provider 上", tc.name)
}
})
}
}