Files
story/internal/callmemory/source_cleanup_integration_test.go
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

93 lines
6.2 KiB
Go

package callmemory
import (
"context"
"testing"
"github.com/google/uuid"
)
func addCallSourceStory(t *testing.T, f fixture, session, utterance string, generation int) (string, string) {
t.Helper()
story, revision, fact := uuid.NewString(), uuid.NewString(), uuid.NewString()
f.exec(t, `INSERT INTO story_draft(id,public_id,tenant_id,subject_id,call_session_id,call_privacy_epoch,title,generation_version) VALUES($1::uuid,$1::uuid,$2::uuid,$3::uuid,$4::uuid,0,'private story',$5)`, story, f.tenant, f.subject, session, generation)
f.exec(t, `INSERT INTO story_revision(id,story_id,revision_no,content,author_type,edit_reason) VALUES($1::uuid,$2::uuid,1,'private story content','model','private edit reason')`, revision, story)
f.exec(t, `INSERT INTO story_source(story_revision_id,call_session_id,call_utterance_id,call_source_revision,source_role) VALUES($1::uuid,$2::uuid,$3,1,'subject')`, revision, session, utterance)
f.exec(t, `UPDATE story_draft SET current_revision_id=$2::uuid WHERE id=$1::uuid`, story, revision)
f.exec(t, `INSERT INTO fact_candidate(id,tenant_id,subject_id,story_id,fact_type,content,statement_basis,source_turn_ids,generation_version) VALUES($1::uuid,$2::uuid,$3::uuid,$4::uuid,'event','private fact','subject_statement',jsonb_build_array($5::text),$6)`, fact, f.tenant, f.subject, story, utterance, generation)
f.exec(t, `INSERT INTO fact_candidate_revision(fact_candidate_id,version_no,fact_type,content,sensitivity,status,edit_reason) VALUES($1::uuid,1,'event','private historical fact','normal','CANDIDATE','private edit reason')`, fact)
return story, revision
}
func TestCallSourceDeletionRedactsLinkedStoryAndPreservesUnrelatedTurns(t *testing.T) {
for _, refType := range []string{"utterance", "story", "session"} {
t.Run(refType, func(t *testing.T) {
f := setup(t)
ctx := context.Background()
memory, _ := f.add(t)
call, _, _ := f.call(t)
for _, utterance := range []string{"removed", "kept"} {
f.exec(t, `INSERT INTO call_utterance(session_id,utterance_id,source_revision,speaker,text_content,interrupted,source_at) VALUES($1::uuid,$2,1,'USER',$2,false,now())`, call.SessionID, utterance)
}
f.exec(t, `INSERT INTO call_text_fragment(session_id,utterance_id,source_revision,part_index,part_count,speaker,text_content,content_sha256,is_final,source_at) VALUES($1::uuid,'fragment-only',1,0,2,'USER','private partial text',repeat('a',64),true,now())`, call.SessionID)
f.exec(t, `INSERT INTO call_event(session_id,event_id,seq,event_type,digest,payload) VALUES($1::uuid,'source-test-event',1,'TRANSCRIPT_FINAL',repeat('a',64),'{"payload":{"utterance_id":"removed","text":"private event text"}}'::jsonb)`, call.SessionID)
story, revision := addCallSourceStory(t, f, call.SessionID, "removed", 1)
unrelatedStory, _ := addCallSourceStory(t, f, call.SessionID, "kept", 2)
// Both utterances occur in the first story. Deleting just one source
// invalidates that story, without erasing its other original source.
if refType == "utterance" {
f.exec(t, `INSERT INTO story_source(story_revision_id,call_session_id,call_utterance_id,call_source_revision,source_role) VALUES($1::uuid,$2::uuid,'kept',1,'subject')`, revision, call.SessionID)
}
refs := []map[string]any{{"session_id": call.SessionID, "utterance_id": "removed"}}
if refType == "story" {
refs = []map[string]any{{"type": "story", "story_id": story, "revision_no": 1}}
} else if refType == "session" {
refs = []map[string]any{{"session_id": call.SessionID}}
}
f.exec(t, `INSERT INTO memory_source_suppression(subject_id,memory_id,source_refs,privacy_epoch,scope) SELECT subject_id,id,$2::jsonb,0,'SOURCE_AND_DERIVED' FROM curated_memory WHERE public_id=$1::uuid`, memory, jsonBytes(refs))
tx, err := f.p.Begin(ctx)
if err != nil {
t.Fatal(err)
}
defer tx.Rollback(ctx)
if err = f.s.cleanSources(ctx, tx, f.subject, jsonBytes([]string{memory}), "SOURCE_AND_DERIVED"); err != nil {
t.Fatal(err)
}
if err = tx.Commit(ctx); err != nil {
t.Fatal(err)
}
var removed, kept, title, status, content, fact, historicalFact string
var editReason *string
if err = f.p.QueryRow(ctx, `SELECT text_content FROM call_utterance WHERE session_id=$1::uuid AND utterance_id='removed'`, call.SessionID).Scan(&removed); err != nil {
t.Fatal(err)
}
if err = f.p.QueryRow(ctx, `SELECT text_content FROM call_utterance WHERE session_id=$1::uuid AND utterance_id='kept'`, call.SessionID).Scan(&kept); err != nil {
t.Fatal(err)
}
if err = f.p.QueryRow(ctx, `SELECT sd.title,sd.status,sr.content,fc.content,fcr.content,sr.edit_reason FROM story_draft sd JOIN story_revision sr ON sr.id=sd.current_revision_id JOIN fact_candidate fc ON fc.story_id=sd.id JOIN fact_candidate_revision fcr ON fcr.fact_candidate_id=fc.id WHERE sd.id=$1::uuid`, story).Scan(&title, &status, &content, &fact, &historicalFact, &editReason); err != nil {
t.Fatal(err)
}
if removed != "[deleted]" || title != "[deleted]" || status != "SUPERSEDED" || content != "[deleted]" || fact != "[deleted]" || historicalFact != "[deleted]" || editReason != nil {
t.Fatalf("source or derived content survived: %q %q %q %q %q %q %v", removed, title, status, content, fact, historicalFact, editReason)
}
var unrelatedStatus, fragment, event string
if err = f.p.QueryRow(ctx, `SELECT status FROM story_draft WHERE id=$1::uuid`, unrelatedStory).Scan(&unrelatedStatus); err != nil {
t.Fatal(err)
}
if err = f.p.QueryRow(ctx, `SELECT text_content FROM call_text_fragment WHERE session_id=$1::uuid`, call.SessionID).Scan(&fragment); err != nil {
t.Fatal(err)
}
if err = f.p.QueryRow(ctx, `SELECT payload::text FROM call_event WHERE session_id=$1::uuid`, call.SessionID).Scan(&event); err != nil || event != `{"redacted": true}` {
t.Fatalf("source event survived deletion: %q %v", event, err)
}
if refType == "session" {
if kept != "[deleted]" || unrelatedStatus != "SUPERSEDED" || fragment != "[deleted]" {
t.Fatalf("session deletion left a source, story or partial transcript: %q %q %q", kept, unrelatedStatus, fragment)
}
} else if kept != "kept" || unrelatedStatus != "CANDIDATE" || fragment != "private partial text" {
t.Fatalf("unrelated source or story changed: %q %q %q", kept, unrelatedStatus, fragment)
}
})
}
}