195 lines
8.2 KiB
Go
195 lines
8.2 KiB
Go
package itemoccurrences
|
|
|
|
import (
|
|
"sort"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
|
)
|
|
|
|
func TestValidHolderCombination(t *testing.T) {
|
|
valid := []struct {
|
|
kind dnd.ItemOccurrenceKind
|
|
from, to string
|
|
}{
|
|
{dnd.ItemOccurrenceKindDiscovered, "", ""},
|
|
{dnd.ItemOccurrenceKindAcquired, "", "party"},
|
|
{dnd.ItemOccurrenceKindLost, "party", ""},
|
|
{dnd.ItemOccurrenceKindConsumed, "party", ""},
|
|
{dnd.ItemOccurrenceKindTransferred, "Aria", "Borin"},
|
|
}
|
|
for _, test := range valid {
|
|
if !ValidHolderCombination(test.kind, test.from, test.to) {
|
|
t.Fatalf("ValidHolderCombination(%q, %q, %q) = false", test.kind, test.from, test.to)
|
|
}
|
|
}
|
|
|
|
invalid := []struct {
|
|
kind dnd.ItemOccurrenceKind
|
|
from, to string
|
|
}{
|
|
{dnd.ItemOccurrenceKindDiscovered, "Aria", ""},
|
|
{dnd.ItemOccurrenceKindAcquired, "", ""},
|
|
{dnd.ItemOccurrenceKindLost, "", ""},
|
|
{dnd.ItemOccurrenceKindConsumed, "", "Borin"},
|
|
{dnd.ItemOccurrenceKindTransferred, "party", "Borin"},
|
|
{dnd.ItemOccurrenceKindTransferred, "Aria", "Party"},
|
|
{dnd.ItemOccurrenceKindTransferred, "Aria", "aria"},
|
|
{dnd.ItemOccurrenceKindTransferred, "Åria", "Åria"},
|
|
{"unsupported", "", ""},
|
|
}
|
|
for _, test := range invalid {
|
|
if ValidHolderCombination(test.kind, test.from, test.to) {
|
|
t.Fatalf("ValidHolderCombination(%q, %q, %q) = true", test.kind, test.from, test.to)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLessUsesEveryCanonicalTieBreaker(t *testing.T) {
|
|
order := testOrder()
|
|
ref := func(start, end int) []source.SourceRef {
|
|
return []source.SourceRef{{SourceID: "session", StartUnitID: start, EndUnitID: end}}
|
|
}
|
|
quantity := func(value int) *int { return &value }
|
|
base := dnd.ItemOccurrence{ItemID: "item", Name: "Amulet", Kind: dnd.ItemOccurrenceKindAcquired, To: "Borin", SourceRefs: ref(20, 20)}
|
|
tests := []struct {
|
|
name string
|
|
left, right dnd.ItemOccurrence
|
|
}{
|
|
{"earlier evidence", withRefs(base, ref(10, 10)), base},
|
|
{"valid evidence before malformed", base, withRefs(base, ref(999, 999))},
|
|
{"normalized name", withName(base, "Amulet"), withName(base, "Blade")},
|
|
{"exact trimmed name", withName(base, "Amulet"), withName(base, "amulet")},
|
|
{"kind", withKind(base, dnd.ItemOccurrenceKindAcquired), withKind(base, dnd.ItemOccurrenceKindLost)},
|
|
{"from presence", withFrom(base, ""), withFrom(base, "Aria")},
|
|
{"from normalized value", withFrom(base, "Aria"), withFrom(base, "Borin")},
|
|
{"from exact value", withFrom(base, "Aria"), withFrom(base, "aria")},
|
|
{"to presence", withoutTo(base), withTo(base, "Borin")},
|
|
{"to normalized value", withTo(base, "Aria"), withTo(base, "Borin")},
|
|
{"to exact value", withTo(base, "Aria"), withTo(base, "aria")},
|
|
{"quantity presence", withQuantity(base, nil), withQuantity(base, quantity(1))},
|
|
{"quantity value", withQuantity(base, quantity(1)), withQuantity(base, quantity(2))},
|
|
{"canonical source reference sequence", withRefs(base, ref(20, 20)), withRefs(base, ref(30, 30))},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if !Less(order, test.left, test.right) || Less(order, test.right, test.left) {
|
|
t.Fatalf("Less() did not order %#v before %#v", test.left, test.right)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLessAndExactEqualityCanonicalizeEvidence(t *testing.T) {
|
|
order := testOrder()
|
|
first := dnd.ItemOccurrence{ItemID: "item",
|
|
Name: " Silver Coin ", Kind: dnd.ItemOccurrenceKindAcquired, To: " party ",
|
|
SourceRefs: []source.SourceRef{
|
|
{SourceID: "session", StartUnitID: 30, EndUnitID: 30},
|
|
{SourceID: "session", StartUnitID: 10, EndUnitID: 10},
|
|
{SourceID: "session", StartUnitID: 10, EndUnitID: 10},
|
|
},
|
|
}
|
|
second := dnd.ItemOccurrence{ItemID: "item",
|
|
Name: "Silver Coin", Kind: dnd.ItemOccurrenceKindAcquired, To: "party",
|
|
SourceRefs: []source.SourceRef{
|
|
{SourceID: "session", StartUnitID: 10, EndUnitID: 10},
|
|
{SourceID: "session", StartUnitID: 30, EndUnitID: 30},
|
|
},
|
|
}
|
|
if !ExactEqual(order, first, second) {
|
|
t.Fatal("ExactEqual() = false, want canonical duplicate")
|
|
}
|
|
if Less(order, first, second) || Less(order, second, first) {
|
|
t.Fatal("Less() distinguished canonically equal records")
|
|
}
|
|
if ExactIdentity(order, first) != ExactIdentity(order, second) {
|
|
t.Fatal("ExactIdentity() differs for canonical duplicates")
|
|
}
|
|
canonicalFirst := first
|
|
canonicalFirst.SourceRefs = order.Canonicalize(first.SourceRefs)
|
|
if !CanonicalExactEqual(canonicalFirst, second) || CanonicalExactIdentity(canonicalFirst) != CanonicalExactIdentity(second) {
|
|
t.Fatal("canonical helpers did not recognize canonical duplicates")
|
|
}
|
|
if CanonicalExactEqual(first, second) || CanonicalExactIdentity(first) == CanonicalExactIdentity(second) {
|
|
t.Fatal("canonical helpers repaired noncanonical source references")
|
|
}
|
|
|
|
quantity := 1
|
|
differentQuantity := second
|
|
differentQuantity.Quantity = &quantity
|
|
differentEvidence := second
|
|
differentEvidence.SourceRefs = append([]source.SourceRef(nil), second.SourceRefs...)
|
|
differentEvidence.SourceRefs[1].EndUnitID = 20
|
|
if ExactEqual(order, second, differentQuantity) || ExactEqual(order, second, differentEvidence) {
|
|
t.Fatal("ExactEqual() collapsed distinct optional field or evidence values")
|
|
}
|
|
}
|
|
|
|
func TestSourceReferenceHelpers(t *testing.T) {
|
|
refs := []source.SourceRef{{SourceID: "session", StartUnitID: 10, EndUnitID: 20}}
|
|
if !SourceRefsEqual(refs, append([]source.SourceRef(nil), refs...)) || SourceRefsEqual(nil, []source.SourceRef{}) {
|
|
t.Fatal("SourceRefsEqual() did not preserve source-reference representation")
|
|
}
|
|
doc := &source.SourceDocument{ID: "session", Units: []source.SourceUnit{{ID: 10}, {ID: 20}, {ID: 30}}}
|
|
if !ValidSourceRefs(source.NewDocumentIndex(doc), refs) {
|
|
t.Fatal("ValidSourceRefs() = false, want valid reference")
|
|
}
|
|
if ValidSourceRefs(source.NewDocumentIndex(doc), []source.SourceRef{{SourceID: "other", StartUnitID: 10, EndUnitID: 20}}) {
|
|
t.Fatal("ValidSourceRefs() = true, want invalid source identifier rejection")
|
|
}
|
|
nilEvidence := dnd.ItemOccurrence{ItemID: "item"}
|
|
emptyEvidence := nilEvidence
|
|
emptyEvidence.SourceRefs = []source.SourceRef{}
|
|
if CanonicalExactIdentity(nilEvidence) == CanonicalExactIdentity(emptyEvidence) {
|
|
t.Fatal("CanonicalExactIdentity() conflated nil and empty source references")
|
|
}
|
|
}
|
|
|
|
func TestLessSortsMalformedReferencesDeterministically(t *testing.T) {
|
|
order := testOrder()
|
|
occurrences := []dnd.ItemOccurrence{{ItemID: "item", Name: "A", Kind: dnd.ItemOccurrenceKindDiscovered, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 999, EndUnitID: 999}}},
|
|
{ItemID: "item", Name: "A", Kind: dnd.ItemOccurrenceKindDiscovered, SourceRefs: []source.SourceRef{{SourceID: "other", StartUnitID: 1, EndUnitID: 1}}},
|
|
{ItemID: "item", Name: "A", Kind: dnd.ItemOccurrenceKindDiscovered, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 10, EndUnitID: 10}}},
|
|
}
|
|
sort.SliceStable(occurrences, func(left, right int) bool { return Less(order, occurrences[left], occurrences[right]) })
|
|
if occurrences[0].SourceRefs[0].StartUnitID != 10 || occurrences[1].SourceRefs[0].SourceID != "other" || occurrences[2].SourceRefs[0].StartUnitID != 999 {
|
|
t.Fatalf("canonical sort = %#v", occurrences)
|
|
}
|
|
}
|
|
|
|
func testOrder() shared.SourceRefOrder {
|
|
return shared.NewSourceRefOrder(&source.SourceDocument{ID: "session", Units: []source.SourceUnit{{ID: 10}, {ID: 20}, {ID: 30}}})
|
|
}
|
|
|
|
func withName(occurrence dnd.ItemOccurrence, value string) dnd.ItemOccurrence {
|
|
occurrence.Name = value
|
|
return occurrence
|
|
}
|
|
func withKind(occurrence dnd.ItemOccurrence, value dnd.ItemOccurrenceKind) dnd.ItemOccurrence {
|
|
occurrence.Kind = value
|
|
return occurrence
|
|
}
|
|
func withFrom(occurrence dnd.ItemOccurrence, value string) dnd.ItemOccurrence {
|
|
occurrence.From = value
|
|
return occurrence
|
|
}
|
|
func withTo(occurrence dnd.ItemOccurrence, value string) dnd.ItemOccurrence {
|
|
occurrence.To = value
|
|
return occurrence
|
|
}
|
|
func withoutTo(occurrence dnd.ItemOccurrence) dnd.ItemOccurrence {
|
|
occurrence.To = ""
|
|
return occurrence
|
|
}
|
|
func withQuantity(occurrence dnd.ItemOccurrence, value *int) dnd.ItemOccurrence {
|
|
occurrence.Quantity = value
|
|
return occurrence
|
|
}
|
|
func withRefs(occurrence dnd.ItemOccurrence, value []source.SourceRef) dnd.ItemOccurrence {
|
|
occurrence.SourceRefs = value
|
|
return occurrence
|
|
}
|