87 lines
3 KiB
Go
87 lines
3 KiB
Go
package store
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestRubyReadingsUpsertAndRead(t *testing.T) {
|
|
s, _ := openTemp(t)
|
|
rows := []RubyReading{
|
|
{BookID: "b", Base: "漢字", Reading: "かんじ", FirstChapter: 2, Occurrences: 5},
|
|
{BookID: "b", Base: "東京", Reading: "とうきょう", FirstChapter: 1, Occurrences: 3},
|
|
{BookID: "other", Base: "京", Reading: "きょう", FirstChapter: 1, Occurrences: 1},
|
|
}
|
|
for _, rr := range rows {
|
|
if err := s.UpsertRubyReading(rr); 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{
|
|
{BookID: "b", Base: "東京", Reading: "とうきょう", FirstChapter: 1, Occurrences: 3},
|
|
{BookID: "b", Base: "漢字", Reading: "かんじ", FirstChapter: 2, Occurrences: 5},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("read = %#v, want %#v (other book must not leak)", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRubyReadingsUpsertIsIdempotentAndConverges(t *testing.T) {
|
|
s, _ := openTemp(t)
|
|
rr := RubyReading{BookID: "b", Base: "漢字", Reading: "かんじ", FirstChapter: 3, Occurrences: 4}
|
|
if err := s.UpsertRubyReading(rr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Re-ingesting the IDENTICAL full-book aggregate is a no-op (idempotent): one row,
|
|
// same values, occurrences never doubled.
|
|
if err := s.UpsertRubyReading(rr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := s.RubyReadingsForBook("b")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 1 || got[0].FirstChapter != 3 || got[0].Occurrences != 4 {
|
|
t.Fatalf("identical re-upsert must be a no-op: %#v", got)
|
|
}
|
|
// A re-ingest after a source edit (e.g. a foreword inserted) moves the pair's
|
|
// first appearance to ch 4 and changes the count. persistRuby recomputes the
|
|
// authoritative full-book aggregate, so the store must REPLACE BOTH columns —
|
|
// it must NOT MIN-pin first_chapter to the stale-early 3 where the pair no
|
|
// longer occurs (self-review finding).
|
|
if err := s.UpsertRubyReading(RubyReading{BookID: "b", Base: "漢字", Reading: "かんじ", FirstChapter: 4, Occurrences: 2}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err = s.RubyReadingsForBook("b")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 1 || got[0].FirstChapter != 4 || got[0].Occurrences != 2 {
|
|
t.Fatalf("re-ingest must REPLACE (converge) both columns, not MIN-pin: %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestRubyReadingsVariantReadingsAreDistinctRows(t *testing.T) {
|
|
// The same base with two readings (variant yomi) are two rows, not a collision.
|
|
s, _ := openTemp(t)
|
|
for _, rr := range []RubyReading{
|
|
{BookID: "b", Base: "海", Reading: "うみ", FirstChapter: 1, Occurrences: 2},
|
|
{BookID: "b", Base: "海", Reading: "かい", FirstChapter: 4, Occurrences: 1},
|
|
} {
|
|
if err := s.UpsertRubyReading(rr); 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)
|
|
}
|
|
}
|