package core import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "strings" "testing" ) // CodeArts 专用适配器是通过**插件自装**(__llmsproxy_adapters.install) // 引入的,不是内置适配器 —— 因为 AK/SK 签名属厂商知识,内置里出现 codearts // 就破了"内核零厂商名"纪律。 // // 这组判据要证明三件事: // 1. 插件加载时确实把适配器装上了 // 2. 装上之后,源能引用它(不是"装了但引用不到") // 3. 它产出的签名是**真的能验**(而不是拼出个像签名的字符串) func TestCodeArtsAdapterInstalledByPlugin(t *testing.T) { c := kernelTestCore(t) ps := c.Plugins() src, err := readBundledChannels(t) if err != nil { t.Fatal(err) } if err := ps.LoadSource("channels", src); err != nil { t.Fatal(err) } // ① 适配器必须在插件加载后就位 adapters := c.ListAdapters() found := false for _, a := range adapters { if a.Name == "codearts" { found = true } } if !found { t.Fatalf("插件未装 codearts 适配器;已装: %#v", adapters) } // ② 归属必须是 channels 插件(证明是它装的,不是内置) if owner := ps.AdapterOwnerOf("codearts"); owner != "channels" { t.Errorf("适配器归属应为 channels,实得 %q", owner) } // ③ 签名必须真的可验:用 Go 标准库按 SDK-HMAC-SHA256 独立复算 got, err := ps.Call("channels", "__codearts_adapter_sign_probe", map[string]interface{}{ "url": "https://snap-access.cn-north-4.myhuaweicloud.com/api/v2/chat/completions", "method": "POST", "body": `{"model":"CodeArts-Pangu"}`, "api_key": `{"access_key_id":"AKTEST","secret_access_key":"SKTEST","security_token":"STTEST"}`, }) if err != nil { t.Fatalf("签名探针: %v", err) } w, _ := got.(map[string]interface{}) m, _ := w["json"].(map[string]interface{}) if m == nil { t.Fatalf("探针无 json: %#v", got) } if m["error"] != nil { t.Fatalf("探针报错: %v", m["error"]) } auth, _ := m["authorization"].(string) if !strings.HasPrefix(auth, "SDK-HMAC-SHA256 Access=AKTEST,SignedHeaders=") { t.Fatalf("Authorization 形态不符(参考要求 Access= 不是 Credential=): %s", auth) } if !strings.Contains(auth, "Signature=") { t.Fatalf("Authorization 缺 Signature: %s", auth) } // 签名后追加的头必须在,且不参与 canonical for _, k := range []string{"Agent-Type", "X-Language"} { if m["hdr_"+k] == nil || m["hdr_"+k] == "" { t.Errorf("缺签名后追加的头 %s", k) } } if m["hdr_Agent-Type"] != "PromptCenter" { t.Errorf("Agent-Type = %v,应为 PromptCenter", m["hdr_Agent-Type"]) } // canonical 不得包含签名后追加的头(参考注释点名:它们是签名后加的) canon, _ := m["canonical"].(string) if strings.Contains(canon, "agent-type") || strings.Contains(canon, "x-language") { t.Errorf("canonical 不应含签名后追加的头:\n%s", canon) } // Authorization 不得进 canonical if strings.Contains(canon, "authorization") { t.Errorf("canonical 不应含 authorization:\n%s", canon) } // x-sdk-content-sha256 说明 payload hash 参与了签名 if !strings.Contains(canon, "x-sdk-content-sha256") { t.Errorf("canonical 应含 x-sdk-content-sha256:\n%s", canon) } // URI 必须以 / 结尾(参考点名的易错点) if !strings.Contains(canon, "/api/v2/chat/completions/\n") { t.Errorf("canonical 的 URI 未补尾斜杠(参考点名的易错点):\n%s", canon) } // ④ **独立验签**:用 Go 标准库按 SDK-HMAC-SHA256 从 canonical 复算签名, // 必须与适配器给出的 Signature 逐字节相同。 // // 为什么必须有这一步:前面几条只验证"形态正确"(Access=、SignedHeaders、 // 尾斜杠)。签名算法本身写错(HMAC 输入少一段、canonical 分隔符用错)时 // 形态依然全对,但上游会 401。复算才是真正的判据。 sts, _ := m["string_to_sign"].(string) if sts == "" { t.Fatal("探针未带出 string_to_sign(判据必须比对真实函数算出的中间值)") } // string_to_sign = "SDK-HMAC-SHA256\n" + date + "\n" + sha256(canonical) parts := strings.SplitN(sts, "\n", 3) if len(parts) != 3 || parts[0] != "SDK-HMAC-SHA256" { t.Fatalf("string_to_sign 头段不符: %q", sts) } canonSum := sha256.Sum256([]byte(canon)) if parts[2] != hex.EncodeToString(canonSum[:]) { t.Errorf("string_to_sign 的 canonical 摘要不符\n算得: %s\n实得: %s", hex.EncodeToString(canonSum[:]), parts[2]) } mac := hmac.New(sha256.New, []byte("SKTEST")) mac.Write([]byte(sts)) wantSig := hex.EncodeToString(mac.Sum(nil)) gotSig := auth[strings.Index(auth, "Signature=")+len("Signature="):] if gotSig != wantSig { t.Errorf("签名独立复算不符(算法写错?)\n复算: %s\n实得: %s", wantSig, gotSig) } b, _ := json.Marshal(m) t.Logf("签名探针输出: %s", string(b)) } // 源注册必须真的引用插件装的 codearts 适配器 —— "装了但源还指向 openai" // 是这类改动最可能的漏配,且不会报错(只是签名不生效、上游 401)。 func TestCodeArtsSourceUsesPluginAdapter(t *testing.T) { // ⚠️ 必须用 poolCore:它给 channels 授予了 add_source/list_sources 等 // 内核权限。用 kernelTestCore 裸加载插件时 add_source 会被拒 //("kernel action add_source is not granted to plugin channels"), // 而这个拒绝**不报错、只是不出源** —— 我就在这里绕了一圈才看出根因。 c, ps := poolCore(t) // 建一个 codearts 账号 → 触发源注册 m, err := ps.Call("channels", "account_add", map[string]interface{}{ "platform": "codearts", "credential": `{"access_key_id":"AK","secret_access_key":"SK","security_token":"ST"}`, }) if err != nil { t.Fatalf("account_add: %v", err) } w, _ := m.(map[string]interface{}) j, _ := w["json"].(map[string]interface{}) if j == nil || j["error"] != nil { t.Fatalf("account_add 报错: %#v", m) } aid, _ := j["account_id"].(string) srcs := listSourcesViaKernel(t, c) // ★ 一个通道 = 一个源:源名是 `channels-codearts`,**不含账号 id**。 // 按账号 id 找会永远失败 —— 这正是"加账号了但测试说没出源"的假故障。 sm, ok := srcHasAccount(srcs, "codearts") if !ok { names := []string{} for _, x := range srcs { if n, _ := x["name"].(string); n != "" { names = append(names, n) } } t.Fatalf("codearts 账号 %q 应已同步出通道源;现有源: %v", aid, names) } if nm, _ := sm["name"].(string); nm != "channels-codearts" { t.Errorf("通道源名应为 channels-codearts,实得 %q(账号 id=%s)", nm, aid) } if ad, _ := sm["adapter"].(string); ad != "codearts" { t.Errorf("源应引用插件装的 codearts 适配器,实得 %q(签名不会生效)", ad) } bu, _ := sm["base_url"].(string) if !strings.Contains(bu, "snap-access") { t.Errorf("base_url 不符: %q", bu) } }