package gates import ( "strings" "testing" "time" ) func lookupWith(ids map[string][]byte) ArtifactLookup { return func(id string) (string, []byte, bool) { c, ok := ids[id] return id + ".md", c, ok } } func TestClaimGateForgedMarkerAlwaysBlocks(t *testing.T) { block, why := ClaimGate("结果如下 [调用工具 write_file]", nil, nil) if !block || why == "" { t.Fatalf("block=%v why=%q", block, why) } } func TestClaimGateProducedAndExistingDoNotBlock(t *testing.T) { produced := []ArtifactRef{{ID: "art_aaaabbbbcccc"}} if block, _ := ClaimGate("已生成 art_aaaabbbbcccc 请查收", produced, nil); block { t.Fatal("produced reference blocked") } lookup := lookupWith(map[string][]byte{"art_dddd11112222": []byte("x")}) if block, _ := ClaimGate("之前的 art_dddd11112222 里有结论", nil, lookup); block { t.Fatal("existing artifact reference blocked") } } func TestClaimGateUnknownArtifactBlocks(t *testing.T) { block, why := ClaimGate("见附件 art_000011112222", nil, lookupWith(nil)) if !block || !strings.Contains(why, "不存在") { t.Fatalf("block=%v why=%q", block, why) } } func TestClaimGateBareClaimWithoutArtifactsBlocks(t *testing.T) { for _, body := range []string{ "已生成报告.md,请查收", "文件已生成好了", } { if block, _ := ClaimGate(body, nil, nil); !block { t.Fatalf("claim not blocked: %q", body) } } // 如实说明没有生成则不拦。 if block, _ := ClaimGate("这一轮没有生成文件", nil, nil); block { t.Fatal("honest note blocked") } } func TestCheckArtifactsReasons(t *testing.T) { lookup := func(id string) (string, []byte, bool) { switch id { case "ok": return "ok.md", []byte("# 正常"), true case "empty": return "empty.md", nil, true default: return "", nil, false } } scorer := func(name string, content []byte) (bool, string) { return false, "标题缺失" } refs := []ArtifactRef{ {ID: ""}, {ID: "missing", Name: "缺失.md"}, {ID: "empty"}, {ID: "ok"}, } reasons := CheckArtifacts(refs, lookup, scorer) joined := strings.Join(reasons, ";") for _, want := range []string{"缺少 ID", "缺失.md 不存在", "empty.md 内容为空", "ok.md:标题缺失"} { if !strings.Contains(joined, want) { t.Fatalf("reasons %q 缺 %q", joined, want) } } } func TestCheckArtifactsNilScorerSkipsQuality(t *testing.T) { lookup := lookupWith(map[string][]byte{"a": []byte("x")}) if reasons := CheckArtifacts([]ArtifactRef{{ID: "a"}}, lookup, nil); len(reasons) != 0 { t.Fatalf("reasons = %v", reasons) } } func TestParseVerdictForms(t *testing.T) { v, err := ParseVerdict("```json\n{\"passed\":true,\"reason\":\"ok\"}\n```") if err != nil || !v.Passed { t.Fatalf("v=%+v err=%v", v, err) } if _, err := ParseVerdict("没有 JSON"); err == nil { t.Fatal("garbage accepted") } if _, err := ParseVerdict("{\"reason\":\"no passed key\"}"); err == nil { t.Fatal("missing passed accepted") } } func TestProgressReminderThrottleAndFormat(t *testing.T) { r := &ProgressReminder{} now := time.Now() first := r.Remind([]string{"写第三章"}, []string{"评审"}, now) if len(first) != 1 { t.Fatalf("first = %v", first) } text := first[0] for _, want := range []string{"已连续 3 个工具轮", "- 待办:写第三章", "- 计划:评审", "请更新计划或待办"} { if !strings.Contains(text, want) { t.Fatalf("text 缺 %q:\n%s", want, text) } } // 内容相同且计数不同:key 不同,应继续发出直到封顶。 if got := r.Remind([]string{"写第三章"}, []string{"评审"}, now); len(got) != 1 { t.Fatalf("second = %v", got) } if got := r.Remind([]string{"写第三章"}, []string{"评审"}, now); len(got) != 1 { t.Fatalf("third = %v", got) } if got := r.Remind([]string{"写第三章"}, []string{"评审"}, now); got != nil { t.Fatalf("fourth should be capped: %v", got) } if r.Sent() != 3 { t.Fatalf("sent = %d", r.Sent()) } // 无内容不发。 if got := (&ProgressReminder{}).Remind(nil, nil, now); got != nil { t.Fatalf("empty = %v", got) } }