118 lines
6.3 KiB
Go
118 lines
6.3 KiB
Go
package extraction
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"data-terminal-cloud/internal/outbox"
|
||
|
|
"github.com/google/uuid"
|
||
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestEventComparisonRejectsUnknownStoryAndPreservesNewFacts(t *testing.T) {
|
||
|
|
in := GenerationInput{PreviousStories: []PreviousStory{{ID: "old", Content: "我1970年进厂。"}}}
|
||
|
|
r := GenerationResult{Meaningful: true, Stories: []EventStory{{Title: "进厂", Content: "进厂的故事", FactContent: "1970年进厂", MatchID: "other"}}}
|
||
|
|
if normalizeEvents(&r, in) == nil {
|
||
|
|
t.Fatal("unknown matched story accepted")
|
||
|
|
}
|
||
|
|
r.Stories[0].MatchID = "old"
|
||
|
|
r.Stories[0].NewDetails = []string{"我1970年进厂。", "娘把杯子洗好了。"}
|
||
|
|
if e := normalizeEvents(&r, in); e != nil || len(r.Stories[0].NewDetails) != 1 {
|
||
|
|
t.Fatalf("result=%v err=%v", r, e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
func TestDailyEventsExcludePlansHabitsAndPast(t *testing.T) {
|
||
|
|
r := GenerationResult{DailyEvents: []DailyEvent{
|
||
|
|
{Title: "晚饭", Content: "今天做了白菜炖土豆。", TimeScope: "today_actual"},
|
||
|
|
{Title: "明天上班", Content: "明天去厂里。", TimeScope: "future"},
|
||
|
|
{Title: "做手套", Content: "前几天答应做手套,还没动手。", TimeScope: "past"},
|
||
|
|
{Title: "习惯", Content: "通常下班会喝水。", TimeScope: "habit"},
|
||
|
|
}}
|
||
|
|
if e := normalizeEvents(&r, GenerationInput{}); e != nil || len(r.DailyEvents) != 1 || r.DailyEvents[0].Title != "晚饭" {
|
||
|
|
t.Fatalf("daily=%v err=%v", r.DailyEvents, e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
func TestImportedEventsSplitDeduplicateAndSupplement(t *testing.T) {
|
||
|
|
if os.Getenv("TEST_DATABASE_URL") == "" {
|
||
|
|
t.Skip("TEST_DATABASE_URL is not set")
|
||
|
|
}
|
||
|
|
ctx := context.Background()
|
||
|
|
pool, e := pgxpool.New(ctx, os.Getenv("TEST_DATABASE_URL"))
|
||
|
|
if e != nil {
|
||
|
|
t.Fatal(e)
|
||
|
|
}
|
||
|
|
defer pool.Close()
|
||
|
|
subject := uuid.NewString()
|
||
|
|
tenant := "10000000-0000-0000-0000-000000000001"
|
||
|
|
user := "10000000-0000-0000-0000-000000000101"
|
||
|
|
exec := func(sql string, args ...any) {
|
||
|
|
t.Helper()
|
||
|
|
if _, e := pool.Exec(ctx, sql, args...); e != nil {
|
||
|
|
t.Fatal(e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
exec(`INSERT INTO subject_profile(id,tenant_id,household_id,preferred_name) VALUES($1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000201','回忆录测试')`, subject, tenant)
|
||
|
|
exec(`INSERT INTO consent_record(tenant_id,subject_id,consent_type,policy_version,notice_digest,actor_user_id,granted_at) SELECT $1::uuid,$2::uuid,k,'test','test',$3::uuid,now()-interval '1 hour' FROM unnest(ARRAY['transcript_storage','story_extraction']) k`, tenant, subject, user)
|
||
|
|
t.Cleanup(func() {
|
||
|
|
for _, sql := range []string{`DELETE FROM story_generation_event WHERE story_id IN(SELECT id FROM story_draft WHERE subject_id=$1::uuid)`, `DELETE FROM fact_candidate WHERE subject_id=$1::uuid`, `UPDATE story_draft SET current_revision_id=NULL WHERE subject_id=$1::uuid`, `DELETE FROM story_revision WHERE story_id IN(SELECT id FROM story_draft WHERE subject_id=$1::uuid)`, `DELETE FROM story_draft WHERE subject_id=$1::uuid`, `DELETE FROM chat_daily_event WHERE subject_id=$1::uuid`, `DELETE FROM memoir_import_turn WHERE import_id IN(SELECT id FROM memoir_chat_import WHERE subject_id=$1::uuid)`, `DELETE FROM memoir_chat_import WHERE subject_id=$1::uuid`, `DELETE FROM consent_record WHERE subject_id=$1::uuid`, `DELETE FROM subject_profile WHERE id=$1::uuid`} {
|
||
|
|
_, _ = pool.Exec(ctx, sql, subject)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
makeImport := func(text string) string {
|
||
|
|
id := uuid.NewString()
|
||
|
|
exec(`INSERT INTO memoir_chat_import(id,tenant_id,subject_id,created_by,title,transcript_digest)VALUES($1::uuid,$2::uuid,$3::uuid,$4::uuid,'test','test')`, id, tenant, subject, user)
|
||
|
|
exec(`INSERT INTO memoir_import_turn(import_id,sequence_no,speaker,content)VALUES($1::uuid,1,'subject',$2),($1::uuid,2,'agent','助手编造:开过飞船')`, id, text)
|
||
|
|
return id
|
||
|
|
}
|
||
|
|
mode := 0
|
||
|
|
generator := directStoryGeneratorFunc(func(_ context.Context, in GenerationInput) (GenerationResult, error) {
|
||
|
|
if strings.Contains(in.Content, "飞船") {
|
||
|
|
t.Fatal("assistant speech included")
|
||
|
|
}
|
||
|
|
sid := strings.Split(strings.Split(in.Content, "]")[0], "[")[1]
|
||
|
|
stories := []EventStory{{Title: "进厂第一天", Content: "1970年我第一次进厂。", FactContent: "1970年进厂", EventTime: "1970年", SourceIDs: []string{sid}}, {Title: "结婚搬家", Content: "1973年我搬了家。", FactContent: "1973年搬家", EventTime: "1973年", SourceIDs: []string{sid}}}
|
||
|
|
if mode > 0 {
|
||
|
|
stories = stories[:1]
|
||
|
|
for _, old := range in.PreviousStories {
|
||
|
|
if old.Title == "进厂第一天" {
|
||
|
|
stories[0].MatchID = old.ID
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if mode == 2 {
|
||
|
|
stories[0].NewDetails = []string{"第一天下班围裙上沾满线头。"}
|
||
|
|
}
|
||
|
|
return GenerationResult{Meaningful: true, StoryWorthy: true, AllowStory: true, Confidence: 0.9, ModelRef: "test", PromptVersion: "events-test", Stories: stories, DailyEvents: []DailyEvent{{Title: "今天晚饭", Content: "今天做了白菜炖土豆。", SourceIDs: []string{sid}}}}, nil
|
||
|
|
})
|
||
|
|
p, _ := NewProcessor(pool, generator)
|
||
|
|
run := func(id string) {
|
||
|
|
t.Helper()
|
||
|
|
if e := p.HandleImport(ctx, outbox.Event{AggregateID: id}); e != nil {
|
||
|
|
t.Fatal(e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
first := makeImport("1970年我进厂,1973年搬家,今天吃了晚饭。")
|
||
|
|
run(first)
|
||
|
|
run(first)
|
||
|
|
mode = 1
|
||
|
|
run(makeImport("1970年我进厂。"))
|
||
|
|
var count int
|
||
|
|
var version int64
|
||
|
|
var content string
|
||
|
|
if e = pool.QueryRow(ctx, `SELECT count(*) FROM story_draft WHERE subject_id=$1::uuid`, subject).Scan(&count); e != nil || count != 2 {
|
||
|
|
t.Fatalf("count=%d err=%v", count, e)
|
||
|
|
}
|
||
|
|
mode = 2
|
||
|
|
run(makeImport("1970年我进厂,第一天下班围裙上沾满线头。"))
|
||
|
|
if e = pool.QueryRow(ctx, `SELECT sd.version,sr.content FROM story_draft sd JOIN story_revision sr ON sr.id=sd.current_revision_id WHERE sd.subject_id=$1::uuid AND sd.title='进厂第一天'`, subject).Scan(&version, &content); e != nil || version != 2 || !strings.Contains(content, "1970") || !strings.Contains(content, "线头") {
|
||
|
|
t.Fatalf("version=%d content=%s err=%v", version, content, e)
|
||
|
|
}
|
||
|
|
// A merged story becomes unavailable if any contributing original is erased.
|
||
|
|
exec(`UPDATE memoir_import_turn SET content='[deleted]' WHERE import_id=$1::uuid AND speaker='subject'`, first)
|
||
|
|
var available bool
|
||
|
|
if e = pool.QueryRow(ctx, `SELECT call_story_available(id) FROM story_draft WHERE subject_id=$1::uuid AND title='进厂第一天'`, subject).Scan(&available); e != nil || available {
|
||
|
|
t.Fatalf("erased source still available: %v %v", available, e)
|
||
|
|
}
|
||
|
|
}
|