97 lines
4.1 KiB
Go
97 lines
4.1 KiB
Go
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|