package memoir import ( "context" "encoding/json" "strings" "testing" ) type recoveringEditor struct{ calls int } func (g *recoveringEditor) GenerateJSON(ctx context.Context, input, prompt string) (json.RawMessage, error) { g.calls++ if g.calls == 1 { return json.RawMessage(`{"broken":`), nil } return (testEditor{}).GenerateJSON(ctx, input, prompt) } func TestMemoirRetriesInvalidProviderJSON(t *testing.T) { g := &recoveringEditor{} s := Service{Generator: g} sources := []Source{{ID: "a", Title: "进厂", Content: "1970年我进厂。", EventTime: "1970年"}} d, e := s.generateBook(context.Background(), sources) if e != nil || g.calls != 2 || len(d.Chapters) != 1 { t.Fatalf("calls=%d doc=%v err=%v", g.calls, d, e) } } func TestParseTranscriptUsesSpeakerLabels(t *testing.T) { turns, e := ParseTranscript("我:1970年我进厂。\n还有一行。\n豆包:你当时是不是开飞船?\n我:今天做了晚饭。") if e != nil || len(turns) != 3 || turns[0].Speaker != "subject" || !strings.Contains(turns[0].Content, "还有一行") || turns[1].Speaker != "agent" { t.Fatalf("turns=%v err=%v", turns, e) } if _, e = ParseTranscript("没有说话人标签的记录"); e == nil { t.Fatal("unattributed speech accepted") } } func TestDocumentRequiresEverySourceExactlyOnce(t *testing.T) { sources := []Source{{ID: "a", EventTime: "1970年"}, {ID: "b", EventTime: "1973年"}} d := Document{Title: "回忆录", Chapters: []Chapter{{StoryID: "a", EventTime: "1970年", Title: "进厂", Paragraphs: []string{"我进厂了。"}, IllustrationPrompt: "车间插画"}, {StoryID: "b", EventTime: "1973年", Title: "搬家", Paragraphs: []string{"我搬家了。"}, IllustrationPrompt: "搬家插画"}}} if e := ValidateDocument(d, sources); e != nil { t.Fatal(e) } d.Chapters[1].StoryID = "a" if ValidateDocument(d, sources) == nil { t.Fatal("duplicate source accepted") } d.Chapters[1].StoryID = "b" d.Chapters[1].EventTime = "2026年" if ValidateDocument(d, sources) == nil { t.Fatal("invented event date accepted") } }