textmachine/backend/internal/store/ruby_test.go

95 lines
3 KiB
Go

package store
import (
"reflect"
"testing"
)
func rr(book, base, reading string, first, occ int) RubyReading {
return RubyReading{BookID: book, Base: base, Reading: reading, FirstChapter: first, Occurrences: occ}
}
func TestRubyReadingsReplaceAndRead(t *testing.T) {
s, _ := openTemp(t)
if err := s.ReplaceRubyReadings("b", []RubyReading{
rr("b", "漢字", "かんじ", 2, 5),
rr("b", "東京", "とうきょう", 1, 3),
}); err != nil {
t.Fatal(err)
}
if err := s.ReplaceRubyReadings("other", []RubyReading{rr("other", "京", "きょう", 1, 1)}); err != nil {
t.Fatal(err)
}
got, err := s.RubyReadingsForBook("b")
if err != nil {
t.Fatal(err)
}
// Ordered by (first_chapter, base, reading): 東京(ch1) before 漢字(ch2).
want := []RubyReading{
rr("b", "東京", "とうきょう", 1, 3),
rr("b", "漢字", "かんじ", 2, 5),
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("read = %#v, want %#v (other book must not leak)", got, want)
}
}
func TestRubyReadingsReplaceIsFullAndIdempotent(t *testing.T) {
s, _ := openTemp(t)
first := []RubyReading{rr("b", "漢字", "かんじ", 3, 4), rr("b", "海", "うみ", 1, 2)}
if err := s.ReplaceRubyReadings("b", first); err != nil {
t.Fatal(err)
}
// Re-ingesting the identical aggregate is a no-op (idempotent).
if err := s.ReplaceRubyReadings("b", first); err != nil {
t.Fatal(err)
}
got, _ := s.RubyReadingsForBook("b")
if len(got) != 2 {
t.Fatalf("identical re-replace must not add rows, got %#v", got)
}
// A source edit re-ingest: 海 removed entirely, 漢字 moved to ch 1 with a new count.
// The full replace must DROP the phantom 海 and CONVERGE 漢字 — a prior upsert-only
// path would have left 海 forever (external-review #6).
if err := s.ReplaceRubyReadings("b", []RubyReading{rr("b", "漢字", "かんじ", 1, 2)}); err != nil {
t.Fatal(err)
}
got, _ = s.RubyReadingsForBook("b")
want := []RubyReading{rr("b", "漢字", "かんじ", 1, 2)}
if !reflect.DeepEqual(got, want) {
t.Fatalf("full replace must drop the removed pair and converge, got %#v", got)
}
}
func TestRubyReadingsVariantReadingsAreDistinctRows(t *testing.T) {
// The same base with two readings (variant yomi) are two rows, not a collision.
s, _ := openTemp(t)
if err := s.ReplaceRubyReadings("b", []RubyReading{
rr("b", "海", "うみ", 1, 2),
rr("b", "海", "かい", 4, 1),
}); err != nil {
t.Fatal(err)
}
got, err := s.RubyReadingsForBook("b")
if err != nil {
t.Fatal(err)
}
if len(got) != 2 {
t.Fatalf("variant readings must be distinct rows, got %#v", got)
}
}
func TestRubyReadingsReplaceEmptyClears(t *testing.T) {
// A source that dropped all its furigana must clear the book's rows.
s, _ := openTemp(t)
if err := s.ReplaceRubyReadings("b", []RubyReading{rr("b", "漢字", "かんじ", 1, 1)}); err != nil {
t.Fatal(err)
}
if err := s.ReplaceRubyReadings("b", nil); err != nil {
t.Fatal(err)
}
got, _ := s.RubyReadingsForBook("b")
if len(got) != 0 {
t.Fatalf("empty replace must clear the book, got %#v", got)
}
}