feat(memory): M4 记忆完善(三类作用域访问规则+项目索引分层+LLM异步提取+衰减溯源+向量接入,兼容既有57用例)
This commit is contained in:
1 parent
5b37f5a551
commit
d947ec41a9
10 files changed
+1349
-53
No files matched your search
@@ -0,0 +1,217 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStore_ListScope_User_全局共享(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
for _, p := range []string{"persona.a", "persona.b"} {
|
||||
if _, err := s.AddScoped(ctx, p, MemoryItem{
|
||||
PersonaID: p, Scope: ScopeUser, Kind: KindRelationship,
|
||||
Content: "用户习惯-" + p,
|
||||
}); err != nil {
|
||||
t.Fatalf("AddScoped user for %s: %v", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
// persona.a sees both personas' user-scope items (globally shared).
|
||||
items, err := s.ListScope(ctx, ScopeUser, "persona.a")
|
||||
if err != nil {
|
||||
t.Fatalf("ListScope user: %v", err)
|
||||
}
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("ListScope(user) = %d items, want 2 (globally shared)", len(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_ListScope_Persona_其他角色只读(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
if _, err := s.AddScoped(ctx, "persona.a", MemoryItem{
|
||||
Scope: ScopePersona, Kind: KindWorldEvent, Content: "a的日常",
|
||||
}); err != nil {
|
||||
t.Fatalf("AddScoped persona.a: %v", err)
|
||||
}
|
||||
|
||||
// persona.b may READ persona.a's persona-scope item (read-only rule).
|
||||
items, err := s.ListScope(ctx, ScopePersona, "persona.b")
|
||||
if err != nil {
|
||||
t.Fatalf("ListScope persona for b: %v", err)
|
||||
}
|
||||
if len(items) != 1 || items[0].PersonaID != "persona.a" {
|
||||
t.Fatalf("ListScope(persona) for persona.b = %+v, want persona.a's item", items)
|
||||
}
|
||||
// AddScoped auto-filled PersonaID from the acting actor.
|
||||
if items[0].PersonaID != "persona.a" {
|
||||
t.Errorf("owner persona = %q, want persona.a", items[0].PersonaID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_AddScoped_Persona_跨角色写_异常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
if _, err := s.AddScoped(ctx, "persona.a", MemoryItem{
|
||||
Scope: ScopePersona, Kind: KindRelationship, Content: "a的日常",
|
||||
}); err != nil {
|
||||
t.Fatalf("AddScoped persona.a: %v", err)
|
||||
}
|
||||
|
||||
// persona.b cannot WRITE persona.a's persona-scope item.
|
||||
_, err := s.AddScoped(ctx, "persona.b", MemoryItem{
|
||||
PersonaID: "persona.a", Scope: ScopePersona, Kind: KindRelationship,
|
||||
Content: "b想改a的日常",
|
||||
})
|
||||
if !errors.Is(err, ErrReadOnly) {
|
||||
t.Fatalf("AddScoped cross-persona err = %v, want wrap of ErrReadOnly", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_AddScoped_UserProject_人人写(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
// Any persona may write a user- or project-scope item.
|
||||
for _, sc := range []Scope{ScopeUser, ScopeProject} {
|
||||
item, err := s.AddScoped(ctx, "persona.b", MemoryItem{
|
||||
Scope: sc, Kind: KindWorldEvent, Content: "共享-" + string(sc),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("AddScoped %s: %v", sc, err)
|
||||
}
|
||||
// PersonaID was auto-filled with the acting actor.
|
||||
if item.PersonaID != "persona.b" {
|
||||
t.Errorf("AddScoped %s owner = %q, want persona.b", sc, item.PersonaID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_AddScoped_MissingActor_异常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
if _, err := s.AddScoped(ctx, "", MemoryItem{
|
||||
Content: "x", Scope: ScopeUser,
|
||||
}); !errors.Is(err, ErrMissingPersona) {
|
||||
t.Fatalf("AddScoped(empty actor) err = %v, want ErrMissingPersona", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_AddScoped_InvalidScope_异常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
if _, err := s.AddScoped(ctx, "persona.a", MemoryItem{
|
||||
Content: "x", Scope: "bogus",
|
||||
}); !errors.Is(err, ErrInvalidScope) {
|
||||
t.Fatalf("AddScoped(bogus scope) err = %v, want ErrInvalidScope", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_ListScope_前置校验_异常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
if _, err := s.ListScope(ctx, ScopeUser, ""); !errors.Is(err, ErrMissingPersona) {
|
||||
t.Fatalf("ListScope(empty persona) err = %v, want ErrMissingPersona", err)
|
||||
}
|
||||
if _, err := s.ListScope(ctx, "bogus", "persona.a"); !errors.Is(err, ErrInvalidScope) {
|
||||
t.Fatalf("ListScope(bogus scope) err = %v, want ErrInvalidScope", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_DeleteScoped_跨角色_异常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
stored, err := s.AddScoped(ctx, "persona.a", MemoryItem{
|
||||
Scope: ScopePersona, Kind: KindRelationship, Content: "a的日常",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("AddScoped persona.a: %v", err)
|
||||
}
|
||||
|
||||
// persona.a deletes its own item.
|
||||
if err := s.DeleteScoped(ctx, "persona.a", stored.ID); err != nil {
|
||||
t.Fatalf("DeleteScoped owner: %v", err)
|
||||
}
|
||||
|
||||
// Re-add, then persona.b cannot delete persona.a's item.
|
||||
stored2, err := s.AddScoped(ctx, "persona.a", MemoryItem{
|
||||
Scope: ScopePersona, Kind: KindRelationship, Content: "a的日常2",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("AddScoped persona.a: %v", err)
|
||||
}
|
||||
if err := s.DeleteScoped(ctx, "persona.b", stored2.ID); !errors.Is(err, ErrReadOnly) {
|
||||
t.Fatalf("DeleteScoped cross-persona err = %v, want ErrReadOnly", err)
|
||||
}
|
||||
// The foreign item survives.
|
||||
if _, err := s.Get(ctx, stored2.ID); err != nil {
|
||||
t.Fatalf("foreign item removed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_DeleteScoped_BlankActor_异常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
if err := s.DeleteScoped(ctx, "", "some-id"); !errors.Is(err, ErrMissingPersona) {
|
||||
t.Fatalf("DeleteScoped(empty actor) err = %v, want ErrMissingPersona", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_ListProject_项目记忆(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
if _, err := s.AddScoped(ctx, "persona.a", MemoryItem{
|
||||
Scope: ScopeProject, Kind: KindWorldEvent, Content: "项目Alpha事实",
|
||||
ProjectID: "project.alpha",
|
||||
}); err != nil {
|
||||
t.Fatalf("AddScoped project: %v", err)
|
||||
}
|
||||
if _, err := s.AddScoped(ctx, "persona.b", MemoryItem{
|
||||
Scope: ScopeProject, Kind: KindWorldEvent, Content: "项目Beta事实",
|
||||
ProjectID: "project.beta",
|
||||
}); err != nil {
|
||||
t.Fatalf("AddScoped project: %v", err)
|
||||
}
|
||||
|
||||
items, err := s.ListProject(ctx, "project.alpha")
|
||||
if err != nil {
|
||||
t.Fatalf("ListProject: %v", err)
|
||||
}
|
||||
if len(items) != 1 || items[0].ProjectID != "project.alpha" {
|
||||
t.Fatalf("ListProject(alpha) = %+v, want only alpha item", items)
|
||||
}
|
||||
if items[0].Content != "项目Alpha事实" {
|
||||
t.Errorf("item content = %q, want the alpha item", items[0].Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_ListProject_BlankID_异常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
if _, err := s.ListProject(ctx, ""); !errors.Is(err, ErrMissingProjectID) {
|
||||
t.Fatalf("ListProject(blank) err = %v, want ErrMissingProjectID", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user