package callmemory import ( "encoding/json" "github.com/google/uuid" "strings" "testing" "time" ) func testManifest() Manifest { now := time.Now().UnixMilli() return Manifest{ContractVersion: ContractVersion, ManifestID: uuid.NewString(), SessionID: uuid.NewString(), DeviceID: "device-test", BootID: uuid.NewString(), HookID: "hook-1", CallGeneration: 1, BindingGeneration: 1, IssuedAt: now, ExpiresAt: now + ManifestMS, Mode: "RTC", BotID: "test-bot", ProfileID: "test-profile", Purpose: "NORMAL", FirstTurnMode: "AI_WELCOME", MemoryMode: "HOT_ONLY", AudioProfileID: "test-audio", VendorParams: `{"explicit":true}`, LeaseMS: LeaseMS, LeaseExpiresAt: now + LeaseMS, MaxCallMS: 3600000} } func TestManifestAuthenticationAndSemanticBounds(t *testing.T) { key := []byte(strings.Repeat("k", 32)) m := testManifest() b, mac, e := EncodeManifest(key, m) if e != nil { t.Fatal(e) } got, e := DecodeManifest(key, b, mac) if e != nil || got.SessionID != m.SessionID { t.Fatalf("roundtrip: %v", e) } if _, e = DecodeManifest(key, b+"A", mac); e == nil { t.Fatal("accepted tampering") } m.LeaseExpiresAt = m.IssuedAt + LeaseMS + 1 b, mac, _ = EncodeManifest(key, m) if _, e = DecodeManifest(key, b, mac); e == nil { t.Fatal("accepted signed but unsafe lease") } } func TestStrictJSONRejectsAmbiguity(t *testing.T) { for _, raw := range []string{`{"action":"ADD","action":"DELETE"}`, `{"patch":{"content":"a","content":"b"}}`, `{} {}`, `{"unknown":true}`, `{"action":"ADD"} trailing`} { var in MutationInput if e := StrictJSON([]byte(raw), &in); e == nil { t.Fatalf("accepted %s", raw) } } } func TestContextLatestWholeFactsAndClosedItems(t *testing.T) { now := time.Now() facts := []ContextFact{ {ID: "old", EntityKey: "trip", Text: "明天去医院", Type: "OPEN_ITEM", ItemStatus: "OPEN", Policy: "ALLOWED", SourceTime: now.Add(-time.Hour)}, {ID: "new", EntityKey: "trip", Text: "已完成就医", Type: "OPEN_ITEM", ItemStatus: "COMPLETED", Policy: "ALLOWED", SourceTime: now}, {ID: "secret", Text: "私密事实", Policy: "RESPOND_ONLY"}, {ID: "long", Text: strings.Repeat("不", 20), Policy: "ALLOWED"}, {ID: "short", Text: "不喜欢咖啡", Policy: "ALLOWED"}, } if got := BuildContext(facts, 10); got != "不喜欢咖啡" { t.Fatalf("context lost policy or negation: %q", got) } } func TestProviderGatesAndExplicitOff(t *testing.T) { p := Profile{ID: "p", SDKVersion: "s", BotID: "b", ModelVersion: "m", Transport: "RTC", Template: map[string]any{}, Mapping: map[string]string{}, Capabilities: map[string]bool{}, Evidence: map[string]string{}} if _, e := p.Compile("OFF", "secret", "welcome", "user", "assistant", "dialog"); e == nil { t.Fatal("missing evidence accepted") } for _, g := range []string{"H01", "H02", "H04", "H05", "H06", "H07", "H09", "H11"} { p.Capabilities[g] = true p.Evidence[g] = "synthetic-test-only" } for _, name := range []string{"memory_enabled", "hot_context", "welcome_text", "user_id", "assistant_id", "dialog_id"} { p.Mapping[name] = "/" + name p.Template[name] = "stale" } raw, e := p.Compile("OFF", "secret", "welcome", "user", "assistant", "dialog") if e != nil { t.Fatal(e) } var got map[string]any json.Unmarshal([]byte(raw), &got) if got["memory_enabled"] != false || got["hot_context"] != "" || got["user_id"] != "" || got["assistant_id"] != "" { t.Fatal(got) } if e = p.Check("NATIVE_VIKING"); e == nil { t.Fatal("native mode accepted without H03/H12") } if mode, e := SelectMemoryMode(43, 41, true, true, true); e != nil || mode != "HOT_ONLY" { t.Fatal(mode, e) } } func TestMutationConstraints(t *testing.T) { str := func(s string) *string { return &s } for _, in := range []MutationInput{ {ID: uuid.NewString(), Action: "DELETE", Patch: MemoryPatch{EntityKey: str("secret")}}, {ID: uuid.NewString(), Action: "ADD", Patch: MemoryPatch{Content: str("done"), Type: str("OPEN_ITEM"), ItemStatus: str("COMPLETED")}}, {ID: uuid.NewString(), Action: "SET_USAGE", Patch: MemoryPatch{Policy: str("NEVER"), Content: str("hidden correction")}}, } { if e := validateMutation(in); e == nil { t.Fatal("accepted invalid mutation", in.Action) } } }