Files
HomeAgent/internal/knowledge/persist_test.go
JianFeeeee 86572d134d refactor(memory)!: 块 union 化,彻底淘汰 CAS 第一阶段(生产代码)
块自带负载(MemoryBlock.Data),删 PayloadDigest;检索纯向量,媒体随块
同生灭。生产代码全绿;CAS 专属测试将在下一提交随判据重写。

- memory: Data BLOB + migration9;blockColumns 不含 data(召回只比向量);
  MediaBlockID 内容派生 ID;BlockByIDPrefix/BlockData 按需加载
- sdk: memory_impl 去 media.Store;MemoryBlockView PayloadDigest→HasData
- core: mediaref/graphmedia/crossmodal/medialoop 全部改块语义;
  captureBlockMedia 直接产块;resolveMediaRefs 按块 ID 解析
- knowledge: 媒体字节随条目目录自带(File+Data),删 mediaGet/mediaPut
- webui: graphBlockView/pulse payload_digest→has_data
- bootstrap/registry: 去 MediaStore 接线(SetMediaStore 空实现,待⑥删包)
2026-10-12 10:13:36 +08:00

135 lines
3.6 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 knowledge
import (
"os"
"path/filepath"
"testing"
)
// 稠密向量缓存:第二次启动不得重算。
func TestDenseCacheAvoidsRecompute(t *testing.T) {
dir := t.TempDir()
mm := &fakeMM{dim: 3, fp: "fp-x", loaded: true,
text: map[string][]float64{"__default": {1, 1, 0}},
img: map[string][]float64{"__default": {0, 1, 1}},
}
s := NewStore(dir)
_ = s.Start()
s.SetDenseSpace(mm)
for _, n := range []string{"a", "b", "c"} {
_ = s.Add(n, "正文"+n)
}
if built, _ := s.ReindexDense(); built != 0 {
t.Fatalf("Add 已算过,首次 Reindex 应为 0,实为 %d", built)
}
fi, err := os.Stat(filepath.Join(dir, ".dense.json"))
if err != nil {
t.Fatalf("稠密缓存未落盘: %v", err)
}
t.Logf(".dense.json = %d 字节", fi.Size())
s.Stop()
// 重启:条目 + 缓存都在,Reindex 不该新建任何向量
s2 := NewStore(dir)
_ = s2.Start()
s2.SetDenseSpace(mm)
for _, id := range s2.List() {
if len(s2.items[id].Dense) == 0 {
t.Fatalf("重启后 %s 未从缓存恢复稠密向量", id)
}
}
if built, _ := s2.ReindexDense(); built != 0 {
t.Errorf("重启后应命中缓存(built 应为 0),实为 %d", built)
}
s2.Stop()
}
// 换模型/维度后缓存必须整体作废并重算。
func TestDenseCacheInvalidatedOnModelChange(t *testing.T) {
dir := t.TempDir()
old := &fakeMM{dim: 3, fp: "fp-old", loaded: true,
text: map[string][]float64{"__default": {1, 1, 0}},
img: map[string][]float64{"__default": {0, 1, 1}},
}
s := NewStore(dir)
_ = s.Start()
s.SetDenseSpace(old)
_ = s.Add("a", "A")
s.Stop()
// 新空间:不同 fp 与维度
newMM := &fakeMM{dim: 5, fp: "fp-new", loaded: true,
text: map[string][]float64{"__default": {1, 1, 1, 0, 0}},
img: map[string][]float64{"__default": {0, 1, 1, 0, 0}},
}
s2 := NewStore(dir)
_ = s2.Start()
s2.SetDenseSpace(newMM)
defer s2.Stop()
if len(s2.items["a"].Dense) != 0 {
t.Error("旧空间的缓存不得被新空间采用")
}
if built, _ := s2.ReindexDense(); built != 1 {
t.Errorf("换空间后应重算 1 条,实为 %d", built)
}
if len(s2.items["a"].Dense) != 5 {
t.Errorf("重算后应为 5 维,实为 %d", len(s2.items["a"].Dense))
}
}
// 媒体引用必须跨重启存活(此前只在内存,重启即静默丢失)。
func TestMediaRefsSurviveRestart(t *testing.T) {
dir := t.TempDir()
s := NewStore(dir)
_ = s.Start()
if err := s.AddWithMedia("cat", "猫的图", []KnowledgeMediaRef{
{Digest: "d1", MIME: "image/png", Kind: "image"},
}); err != nil {
t.Fatal(err)
}
s.Stop()
s2 := NewStore(dir)
_ = s2.Start()
defer s2.Stop()
k := s2.items["cat"]
if k == nil {
t.Fatalf("条目未载入,实为 %v", s2.List())
}
if len(k.Media) != 1 || k.Media[0].Digest != "d1" {
t.Fatalf("媒体引用未跨重启存活:%+v", k.Media)
}
}
// AttachMedia 新挂的媒体也必须落盘。
func TestAttachMediaPersists(t *testing.T) {
dir := t.TempDir()
s := NewStore(dir)
_ = s.Start()
_ = s.Add("k", "正文")
if err := s.AttachMedia("k", KnowledgeMediaRef{Digest: "d9", MIME: "image/jpeg"}); err != nil {
t.Fatal(err)
}
s.Stop()
s2 := NewStore(dir)
_ = s2.Start()
defer s2.Stop()
if got := s2.items["k"].Media; len(got) != 1 || got[0].Digest != "d9" {
t.Fatalf("AttachMedia 未落盘:%+v", got)
}
}
// 无媒体的条目不应产生 .media.json 残留。
func TestNoMediaSidecarForPlainEntry(t *testing.T) {
dir := t.TempDir()
s := NewStore(dir)
_ = s.Start()
defer s.Stop()
_ = s.Add("plain", "正文")
if _, err := os.Stat(filepath.Join(dir, "plain", mediaSidecarName)); err == nil {
t.Errorf("纯文本条目不该有 %s", mediaSidecarName)
}
}