Files
HomeAgent/internal/memory/coldcontext/test/store_contract_test.go
JianFeeeee aac11f6661 test: 按模块导出接口重写黑盒判据(批次 4:coldcontext / tracker / network)
3 个文件全部在 `<module>/test/`,源码目录仍零 `_test.go`。
共 15 条判据,只调导出接口,输入 → 可观察输出。

## internal/memory/coldcontext —— NewStore/Insert/Get/Query/Consume/Blocks

    TestInsertThenGet                 空 ID 自动补齐;Get 拿回内容;时间戳填好
    TestInsertExplicitIDKeepsIt       显式 ID 不被覆盖
    TestQueryReturnsRelevantDoc       按分词器词形召回相关文档
    TestConsumeIsOneShot              消费过的文档不再返回
    TestBlocksReflectsInsertedDoc     Blocks 输出可调用

**判据写错过一次并修正**:分词器按空白切词,文档里「发票重试」是一个
token,所以「发票 重试 账单」查不到而「发票重试」能查到 —— 实测探针
确认后按真实输入契约写。判据的输入必须能走到被测路径。

## internal/tracker —— NewTracker/Init/PreAction/PostAction/Rollback

    TestFreshTrackerHasNoChanges      新 tracker 无 changeset
    TestNewChangeSetCarriesAction     Action/ID/时间
    TestPostActionWithoutFileChangeIsNoop  无文件变更不凭空造 changeset
    TestRollbackLatestWithoutChangesReportsError
    TestRollbackClearsChangeSets      Rollback 后视图清空

不碰 t.changeSets、不假设 upper/merge 目录结构。

## internal/network —— NewMonitor/CheckOnce/Status/AggregateResult

    TestCheckOnceReachableServer      2xx → Reachable=true + URL/时间
    TestCheckOnceServerError          5xx → Reachable=false
    TestCheckOnceUnreachableEndpoint  连不上 → Error 非空,不 panic
    TestCheckOnceInvalidURL           非法 URL → 失败状态
    TestFreshMonitorStatusIsEmpty     初始状态为空

用 httptest 提供真实 HTTP 输入,不 mock client、不碰 m.status。

## 变异验证(改回旧实现必须红)

    coldcontext Insert 不补 ID       → ★ Insert 没有给空 ID 的文档补 ID
    network     5xx 判可达           → ★ 5xx 端点判可达
    tracker     无变更也造 changeset → ★ 无文件变更却 HasChanges=true

全部还原后复跑全绿。

## 验证

    go build ./...                            PASS
    go vet ./...                              PASS
    gofmt -l                                  干净
    python3 .github/scripts/check-ci-gates.py PASS
    go test ./... -count=1                    17 个测试包 / 100 包无测试 / 0 FAIL
    3 个新包 -race                            全绿
    源码目录 _test.go                         0
2026-11-09 12:56:13 +08:00

110 lines
3.1 KiB
Go
Raw 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 test
// internal/memory/coldcontext 的黑盒判据:NewStore → Insert → 可观察输出。
// 不读盘上文件、不碰 s.docs。
import (
"strings"
"testing"
"github.com/JianFeeeee/HologramAgent/internal/memory/coldcontext"
)
func tok(text string) []string { return strings.Fields(text) }
func newStore(t *testing.T) *coldcontext.Store {
t.Helper()
return coldcontext.NewStore(t.TempDir(), tok)
}
// TestInsertThenGet:Insert 后 Get 拿回同一份内容,且自动补 ID/时间戳。
func TestInsertThenGet(t *testing.T) {
s := newStore(t)
doc := &coldcontext.ColdDoc{
Summary: "账单服务发票重试",
Content: "发票重试策略与对账流程",
Source: "contract",
}
if err := s.Insert(doc); err != nil {
t.Fatalf("Insert: %v", err)
}
if doc.ID == "" {
t.Fatal("★ Insert 没有给空 ID 的文档补 ID")
}
got := s.Get(doc.ID)
if got == nil {
t.Fatal("★ Get 返回 nil")
}
if got.Summary != "账单服务发票重试" || got.Content != "发票重试策略与对账流程" {
t.Fatalf("★ Get 内容不符:%+v", got)
}
if doc.CreatedAt.IsZero() || doc.LastAccess.IsZero() {
t.Fatal("★ Insert 没填时间戳")
}
}
// TestInsertExplicitIDKeepsIt:显式 ID 不被覆盖。
func TestInsertExplicitIDKeepsIt(t *testing.T) {
s := newStore(t)
doc := &coldcontext.ColdDoc{ID: "my-doc", Summary: "s", Content: "c"}
if err := s.Insert(doc); err != nil {
t.Fatal(err)
}
if s.Get("my-doc") == nil {
t.Fatal("★ 显式 ID 被覆盖")
}
}
// TestQueryReturnsRelevantDoc:Query 至少能召回与查询相关的文档。
func TestQueryReturnsRelevantDoc(t *testing.T) {
s := newStore(t)
if err := s.Insert(&coldcontext.ColdDoc{
ID: "billing", Summary: "发票重试", Content: "账单服务的对账流程",
}); err != nil {
t.Fatal(err)
}
if err := s.Insert(&coldcontext.ColdDoc{
ID: "auth", Summary: "登录会话", Content: "刷新令牌过期时间",
}); err != nil {
t.Fatal(err)
}
// 分词器按空白切词:文档里的 "发票重试" 是一个 token,
// 所以查询必须用同一词形,而不是拆成「发票 重试 账单」。
hits := s.Query("发票重试", 2)
if len(hits) == 0 {
t.Fatal("★ Query 无结果")
}
if hits[0].ID != "billing" {
t.Fatalf("★ Query 排第一的是 %q,期望 billing", hits[0].ID)
}
}
// TestConsumeIsOneShot:Consume 取过的文档再次取应为空/减少。
func TestConsumeIsOneShot(t *testing.T) {
s := newStore(t)
if err := s.Insert(&coldcontext.ColdDoc{
ID: "one", Summary: "一次性内容", Content: "一次性内容",
}); err != nil {
t.Fatal(err)
}
first := s.Consume("一次性内容", 1)
if len(first) == 0 {
t.Fatal("★ 首次 Consume 无结果")
}
second := s.Consume("一次性内容", 1)
if len(second) != 0 {
t.Fatalf("★ Consume 非一次性:第二次仍返回 %d 条", len(second))
}
}
// TestBlocksReflectsInsertedDoc:Blocks 输出反映已插入文档的块。
func TestBlocksReflectsInsertedDoc(t *testing.T) {
s := newStore(t)
if err := s.Insert(&coldcontext.ColdDoc{
ID: "with-blocks", Summary: "s", Content: "c",
}); err != nil {
t.Fatal(err)
}
_ = s.Blocks() // 输出必须可调用且不 panic
}