package core
import (
"encoding/json"
"strings"
"testing"
)
// ★ ZCode 3012 的判据必须是**多形态**的,且误判不是"文案不好看"——
// 领取路径会据此**短路**(跳过本可领取的 plan),而限流与风控的用户动作
// **完全相反**(稍后重试 vs 别重试)。
//
// 本判据逐行搬自参考 zcode-diagnostics.ts 的对抗性审计实测矩阵
// (2026-10-05 自审 + 审计打出的真实缺陷)。
//
// 为什么用**共用模块**验:这段逻辑被领取路径(插件主体)与推理路径
// (channels-zcode 适配器)共用,任一侧漂移都会让"请勿连续重试"只出现在
// 一条路径上。判据直接打共用模块的导出,两边用的是同一份源码文本。
func TestZcode3012ClassificationMatrix(t *testing.T) {
h := newZcodeIdentityHarness(t)
cases := []struct {
name string
body string
want bool
}{
// ── 真风控(必须命中)──
{"上游真实文案(带码)", `{"code":3012,"msg":"request has been blocked due to unusual activity."}`, true},
{"码是纯数字字符串", `{"code":"3012","msg":"x"}`, true},
{"仅正文语义(无数字)", `request has been blocked due to unusual activity.`, true},
{"3012 与 block 紧邻", `3012 blocked by waf`, true},
// ── 必须**不**命中(误判会让用户做错事)──
{"13012 不是 3012", `{"code":13012}`, false},
{"标识符 plan-3012-trust", `plan-3012-trust`, false},
{"标识符 trust_3012", `trust_3012`, false},
{"字母包裹 x3012y", `x3012y`, false},
{"限流的毫秒数", `too many requests, retry after 3012 ms`, false},
{"HTML 里的 3012", `
Error 3012
upstream connect timeout`, false},
{"限流+block 距 14", `rate limited: request blocked, retry after 3012 ms`, false},
{"block 距 12", `temporarily blocked; see error 3012`, false},
{"额度码 1005", `{"code":1005,"msg":"quota"}`, false},
// ── 隔离「词边界」规则:这些用例同时满足"block 紧邻",
// 只有词边界能区分 ⇒ 裸子串实现会把前三个判成风控。
// (最初的矩阵没覆盖这一组,变异测试因此没咬住 —— 补上后才有效。)
{"标识符+block 紧邻", `plan-3012 blocked`, false},
{"字母包裹+block 紧邻", `x3012 blocked`, false},
{"下划线+block 紧邻", `trust_3012 blocked`, false},
{"独立词+block 紧邻", `3012 blocked`, true},
{"13012 里的片段+block 紧邻", `13012 blocked`, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := h.callZD("hasUnusualActivity", tc.body)
if got != tc.want {
t.Errorf("hasUnusualActivity(%q) = %v, want %v\n"+
"(误判的后果不是文案问题:领取路径会据此短路,且限流='稍后重试'、\n"+
" 风控='别重试',两者用户动作完全相反)", tc.body, got, tc.want)
}
})
}
}
// ★ 时间差格式化必须逐档单独算:参考初版把分钟档写成 `floor(seconds % 60)`,
// 于是 90 秒被渲染成「30分30秒」(分位取的是**秒的余数**)。
// 该错误是在**真实探针输出**里发现的,纯函数单测当时没覆盖 ≥60 秒的区间。
func TestZcodeFormatGapDoesNotMisrenderMinutes(t *testing.T) {
h := newZcodeIdentityHarness(t)
for _, tc := range []struct {
ms int
want string
}{
{90000, "1分30秒"}, // 参考踩过的坑:错法会得到 "30分30秒"
{59000, "59.0s"}, // 不足一分钟
{60000, "1分"}, // 整分钟
{3600000, "1小时"}, // 整小时
{3900000, "1小时5分"}, // 小时+分
{500, "500ms"},
} {
got := h.callZDString("formatGap", tc.ms)
if got != tc.want {
t.Errorf("formatGap(%d) = %q, want %q", tc.ms, got, tc.want)
}
}
}
// ★ 诊断行**绝不能含凭据片段**(参考的设计红线之一)。
//
// 账号只以"进程内自增序号"出现。判据用「传入的账号 id 的每个子串都不得出现在
// 文案里」来钉死这条 —— 不是"看起来没有"。
func TestZcodeDiagnosticLeaksNoCredentials(t *testing.T) {
h := newZcodeIdentityHarness(t)
const secretID = "zcode-abcdef1234567890-SECRET"
h.callZDAction("accountOrdinal", secretID)
h.callZDAction2("noteRequestSent", secretID, 1000)
h.callZDAction2("noteRequestOk", secretID, 1100)
diag := h.callZDString("formatDiagnostic", map[string]interface{}{
"accountId": secretID,
"sentAt": 1200,
"now": 1300,
"status": 405,
})
if diag == "" {
t.Fatal("formatDiagnostic 返回空")
}
// 账号 id 的每个 >=4 字符子串都不得出现
for i := 0; i+4 <= len(secretID); i++ {
if strings.Contains(diag, secretID[i:i+4]) {
t.Fatalf("诊断行泄漏账号 id 片段 %q:%s", secretID[i:i+4], diag)
}
}
if !strings.Contains(diag, "账号#") {
t.Fatalf("诊断行应含「账号#序号」形态:%s", diag)
}
t.Logf("✓ 诊断行无凭据片段: %s", diag)
}
// ★ 3012 的文案必须**共用**(推理 + 领取两条路径),且必须含"请勿连续重试"。
//
// 3012 不是"重试就能过"(那是 3007),而是**有账号冷却惩罚**:
// 30 分钟起步,反复触发升级到 24 小时乃至停用。
func TestZcode3012MessageIsSharedAndWarnsAboutRetries(t *testing.T) {
h := newZcodeIdentityHarness(t)
msg := h.callZDString("formatUnusualActivityMessage", map[string]interface{}{
"subject": "领取 plan-x",
"rawResponse": "request has been blocked",
})
for _, want := range []string{"3012", "请勿连续重试", "冷却惩罚", "领取 plan-x"} {
if !strings.Contains(msg, want) {
t.Fatalf("风控文案缺 %q:%s", want, msg)
}
}
// 空 subject/diagnostic/rawResponse 时不得留下空壳("本机诊断:"后面啥都没有)
bare := h.callZDString("formatUnusualActivityMessage", map[string]interface{}{})
if strings.Contains(bare, "本机诊断:\n") || strings.Contains(bare, "原始响应:\n") {
t.Fatalf("空参数不应留下空壳段:%q", bare)
}
t.Logf("✓ 文案: %s", strings.SplitN(msg, "\n", 2)[0])
}
// ★ 请求体形态诊断必须读**真正要发出去的对象**,而不是常量。
//
// 常量只能证明"源码里写了多少字",证明不了"线上发出去多少字"。
// 两者一旦不一致(例如某条改动把 system 顶掉了),只有读实际值才看得出来
// —— 它会**如实显示 0 字符**,而不是拿常量里的 2898 把问题盖住。
func TestZcodeRequestShapeReadsActualObject(t *testing.T) {
h := newZcodeIdentityHarness(t)
// 用真实的适配器产出 → 再诊断它(而不是手搓一个假 system)
out, err := h.transformRequest(`{"model":"m","messages":[{"role":"user","content":"hi"}]}`)
if err != nil {
t.Fatal(err)
}
var req map[string]interface{}
if err := jsonUnmarshal(out, &req); err != nil {
t.Fatal(err)
}
shape := h.callZDMap("describeRequestShape", req["system"], req["messages"])
if shape == nil {
t.Fatal("describeRequestShape 返回空")
}
chars, _ := shape["identityChars"].(float64)
if chars < 2800 {
t.Fatalf("身份块实测字符数 %v < 2800(诊断必须读实际值)", chars)
}
if shape["hasDateBlock"] != true {
t.Fatalf("应检测到日期块,实得 %v", shape["hasDateBlock"])
}
if shape["firstRole"] != "user" {
t.Fatalf("firstRole 应为 user,实得 %v", shape["firstRole"])
}
t.Logf("✓ 形态诊断: 块数=%v 身份块=%v 日期块=%v role=%v",
shape["systemBlocks"], chars, shape["hasDateBlock"], shape["firstRole"])
// 反例:空 system ⇒ 必须**如实显示 0**(不能被常量盖住)
empty := h.callZDMap("describeRequestShape", []interface{}{}, req["messages"])
if c, _ := empty["identityChars"].(float64); c != 0 {
t.Fatalf("空 system 时 identityChars 必须为 0(如实反映实际值),实得 %v", c)
}
}
// ── harness 助手:经共用的探针打**那一份** ZD 实现 ──
//
// 走探针而不是让测试自己 load 一份副本:判据必须打真实被两条路径共用的实现,
// 自己 load 副本会退化成"各自测各自的",共用一旦漂移就测不出来。
func (h *zcodeIdentityHarness) callZD(fn string, a interface{}) bool {
h.t.Helper()
res, err := h.ps.Call("channels", "__zcode_diag_probe",
map[string]interface{}{"fn": fn, "a": a})
if err != nil {
h.t.Fatalf("__zcode_diag_probe(%s): %v", fn, err)
}
m := res.(map[string]interface{})["json"].(map[string]interface{})
if e, ok := m["error"]; ok {
h.t.Fatalf("探针错误(%s): %v", fn, e)
}
v, ok := m["result"].(bool)
if !ok {
h.t.Fatalf("%s 返回非布尔: %#v", fn, m["result"])
}
return v
}
func (h *zcodeIdentityHarness) callZDString(fn string, a interface{}) string {
h.t.Helper()
var res interface{}
var err error
if opts, ok := a.(map[string]interface{}); ok {
res, err = h.ps.Call("channels", "__zcode_diag_probe",
map[string]interface{}{"fn": fn, "opts": opts})
} else if fn == "formatGap" {
res, err = h.ps.Call("channels", "__zcode_diag_probe",
map[string]interface{}{"fn": fn, "a": a})
} else {
res, err = h.ps.Call("channels", "__zcode_diag_probe",
map[string]interface{}{"fn": fn, "a": a})
}
if err != nil {
h.t.Fatalf("__zcode_diag_probe(%s): %v", fn, err)
}
m := res.(map[string]interface{})["json"].(map[string]interface{})
if e, ok := m["error"]; ok {
h.t.Fatalf("探针错误(%s): %v", fn, e)
}
s, _ := m["result"].(string)
return s
}
// callZDAction / callZDAction2:调"动作型"探针(不返回结果,只看没报错)。
func (h *zcodeIdentityHarness) callZDAction(fn string, a interface{}) {
h.t.Helper()
res, err := h.ps.Call("channels", "__zcode_diag_probe",
map[string]interface{}{"fn": fn, "a": a})
if err != nil {
h.t.Fatalf("__zcode_diag_probe(%s): %v", fn, err)
}
if m := res.(map[string]interface{})["json"].(map[string]interface{}); m["error"] != nil {
h.t.Fatalf("探针错误(%s): %v", fn, m["error"])
}
}
func (h *zcodeIdentityHarness) callZDAction2(fn string, a interface{}, a2 int) {
h.t.Helper()
res, err := h.ps.Call("channels", "__zcode_diag_probe",
map[string]interface{}{"fn": fn, "a": a, "a2": a2})
if err != nil {
h.t.Fatalf("__zcode_diag_probe(%s): %v", fn, err)
}
if m := res.(map[string]interface{})["json"].(map[string]interface{}); m["error"] != nil {
h.t.Fatalf("探针错误(%s): %v", fn, m["error"])
}
}
func (h *zcodeIdentityHarness) callZDMap(fn string, args ...interface{}) map[string]interface{} {
h.t.Helper()
var res interface{}
var err error
if len(args) == 2 {
res, err = h.ps.Call("channels", "__zcode_diag_probe",
map[string]interface{}{"fn": fn, "system": args[0], "messages": args[1]})
} else {
res, err = h.ps.Call("channels", "__zcode_diag_probe",
map[string]interface{}{"fn": fn, "a": args[0]})
}
if err != nil {
h.t.Fatalf("__zcode_diag_probe(%s): %v", fn, err)
}
m := res.(map[string]interface{})["json"].(map[string]interface{})
if e, ok := m["error"]; ok {
h.t.Fatalf("探针错误(%s): %v", fn, e)
}
out, _ := m["result"].(map[string]interface{})
return out
}
// jsonUnmarshal 是本文件用的小助手(避免与其它测试文件的导入冲突)。
func jsonUnmarshal(s string, v interface{}) error {
return json.Unmarshal([]byte(s), v)
}