Files
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

168 lines
6.6 KiB
Go

package callmemory
import (
"context"
"encoding/json"
"strings"
"testing"
"data-terminal-cloud/internal/deviceauth"
"data-terminal-cloud/internal/volcsubtitle"
"github.com/google/uuid"
)
func TestDirectStoryTrackingBoundaries(t *testing.T) {
state := storyContext{Version: storyContextVersion}
sid := uuid.NewString()
state.observe(sid, "q", "ASSISTANT", "您以前在厂里做什么工作呢?", 2000)
state.observe(sid, "short", "USER", "嗯", 3000)
if state.UserTurns != 0 || state.EarlyInvitations != 1 || state.Questions[0].Reply != nil {
t.Fatal(state)
}
state.observe(sid, "quoted", "USER", "他说不想聊以前的工作", 4000)
if len(state.Paused) != 0 {
t.Fatal("third-party quotation became a refusal")
}
state.observe(sid, "q2", "ASSISTANT", "您以前在厂里做什么工作呢?", 190000)
state.observe(sid, "no", "USER", "我不想聊以前的工作", 190100)
if len(state.Paused) != 1 || state.Paused[0].Topic != "work" || state.Pending != nil {
t.Fatal(state)
}
state.observe(sid, "own", "USER", "我年轻时在老家种过菜", 191000)
state.observe(sid, "q3", "ASSISTANT", "老家以前怎么种菜呢?", 192000)
if state.Invitations != 2 || len(state.Paused) != 1 {
t.Fatal("user-led story counted as invitation or erased prior boundary", state)
}
raw, _ := json.Marshal(state)
if strings.Contains(string(raw), "不想聊") || strings.Contains(string(raw), "种过菜") {
t.Fatal("duplicated transcript in snapshot", string(raw))
}
state.observe(sid, "all", "USER", "我不想聊以前的事", 193000)
if state.Paused[len(state.Paused)-1].Topic != "all" {
t.Fatal("global boundary not tracked")
}
}
func storyFixture(t *testing.T) (fixture, deviceauth.Identity, *string) {
f, d := directFixture(t)
f.add(t)
brief := ""
f.s.direct.Prepare = func(_ context.Context, _, _, _, text string) error { brief = text; return nil }
return f, d, &brief
}
func storyStart(t *testing.T, f fixture, d deviceauth.Identity) string {
t.Helper()
sid := uuid.NewString()
if _, e := f.s.DirectRequest(context.Background(), d, "start", DirectInput{SessionID: sid}); e != nil {
t.Fatal(e)
}
return sid
}
func storyStop(t *testing.T, f fixture, d deviceauth.Identity, sid string) {
t.Helper()
if _, e := f.s.DirectRequest(context.Background(), d, "end", DirectInput{SessionID: sid, Reason: "hangup", SDKStopped: true}); e != nil {
t.Fatal(e)
}
}
func storySubtitle(t *testing.T, f fixture, sid, speaker, text string, seq int64) {
t.Helper()
round := int64(1)
if e := f.s.DirectSubtitle(context.Background(), sid, volcsubtitle.Packet{Type: "subtitle", Data: []volcsubtitle.Segment{{UserID: speaker, RoundID: &round, Sequence: &seq, Text: text, Definite: true}}}); e != nil {
t.Fatal(e)
}
}
func storyRead(t *testing.T, f fixture, sid string) storyContext {
t.Helper()
var raw []byte
if e := f.p.QueryRow(context.Background(), `SELECT budget_snapshot->'story_context' FROM call_session WHERE id=$1::uuid`, sid).Scan(&raw); e != nil {
t.Fatal(e)
}
var state storyContext
if e := json.Unmarshal(raw, &state); e != nil {
t.Fatal(e)
}
return state
}
func TestDirectStorySourceContextAndRevocation(t *testing.T) {
f, d, brief := storyFixture(t)
sid := storyStart(t, f, d)
storySubtitle(t, f, sid, "output", "您以前在厂里做什么工作呢?", 1)
storySubtitle(t, f, sid, "input", "我在厂里负责修机器", 2)
storyStop(t, f, d, sid)
next := storyStart(t, f, d)
if !strings.Contains(*brief, "recently_asked_questions") || !strings.Contains(*brief, "我在厂里负责修机器") {
t.Fatal("source-backed question/reply not loaded", *brief)
}
if len(storyRead(t, f, next).Questions) != 1 {
t.Fatal("question history not carried forward")
}
frozen := *brief
if _, e := f.s.DirectRequest(context.Background(), d, "start", DirectInput{SessionID: next}); e != nil {
t.Fatal(e)
}
if *brief != frozen {
t.Fatal("renewal changed the frozen background")
}
storyStop(t, f, d, next)
f.exec(t, `UPDATE consent_record SET withdrawn_at=now() WHERE subject_id=$1::uuid AND consent_type='personalized_memory'`, f.subject)
storyStart(t, f, d)
if *brief != "" {
t.Fatal("memory revoked but history sent to provider", *brief)
}
}
func TestDirectStoryRefusalPersistsAndDuplicateCallbackIsIdempotent(t *testing.T) {
f, d, brief := storyFixture(t)
sid := storyStart(t, f, d)
storySubtitle(t, f, sid, "output", "您以前在厂里做什么工作呢?", 1)
storySubtitle(t, f, sid, "output", "您以前在厂里做什么工作呢?", 1)
storySubtitle(t, f, sid, "input", "我不想聊以前的工作", 2)
state := storyRead(t, f, sid)
if state.Invitations != 1 || len(state.Paused) != 1 || len(state.Questions) != 1 || state.Questions[0].Reply != nil {
t.Fatal(state)
}
storyStop(t, f, d, sid)
next := storyStart(t, f, d)
if !strings.Contains(*brief, `"topics_not_to_open_proactively":["work"]`) {
t.Fatal("explicit boundary missing", *brief)
}
storySubtitle(t, f, next, "input", "我年轻时在厂里修过机器", 3)
storyStop(t, f, d, next)
last := storyStart(t, f, d)
if len(storyRead(t, f, last).Paused) != 1 || !strings.Contains(*brief, `"work"`) {
t.Fatal("voluntary story erased future boundary")
}
}
func TestDirectStoryIsolationAndDeletedSource(t *testing.T) {
f, d, brief := storyFixture(t)
sid := storyStart(t, f, d)
storySubtitle(t, f, sid, "output", "您以前在厂里做什么工作呢?", 1)
storySubtitle(t, f, sid, "input", "我不想聊以前的工作", 2)
storyStop(t, f, d, sid)
f.exec(t, `UPDATE call_utterance SET text_content='[deleted]' WHERE session_id=$1::uuid`, sid)
next := storyStart(t, f, d)
if strings.Contains(*brief, "recently_asked_questions") || len(storyRead(t, f, next).Paused) != 0 {
t.Fatal("deleted sources still used", *brief)
}
storyStop(t, f, d, next)
g, gd, _ := storyFixture(t)
foreign := storyStart(t, g, gd)
storySubtitle(t, g, foreign, "output", "您以前在厂里做什么工作呢?", 1)
storySubtitle(t, g, foreign, "input", "我不想聊以前的工作", 2)
storyStop(t, g, gd, foreign)
forged := storyRead(t, g, foreign)
f.exec(t, `UPDATE call_session SET budget_snapshot=jsonb_set(budget_snapshot,'{story_context}',$2::jsonb) WHERE id=$1::uuid`, next, jsonBytes(forged))
last := storyStart(t, f, d)
if len(storyRead(t, f, last).Paused) != 0 || strings.Contains(*brief, "recently_asked_questions") {
t.Fatal("foreign elder source leaked", *brief)
}
storyStop(t, f, d, last)
f.exec(t, `UPDATE memory_user_state SET privacy_epoch=privacy_epoch+1 WHERE subject_id=$1::uuid`, f.subject)
epochCall := storyStart(t, f, d)
if len(storyRead(t, f, epochCall).Questions) != 0 {
t.Fatal("old epoch history survived")
}
}