Reject unsafe currency reconciliation proposals
This commit is contained in:
@@ -84,7 +84,7 @@ func TestNormalizeConsolidatesEqualNamesAcrossEvidenceWithoutMutation(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeAppliesSafeAliasProposalAndPreservesCurrency(t *testing.T) {
|
||||
func TestNormalizeAppliesSafeAliasProposal(t *testing.T) {
|
||||
doc := semanticDocument()
|
||||
input := dnd.ItemRegistry{Items: []dnd.Item{
|
||||
{Name: "Star Compass", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}}},
|
||||
@@ -104,11 +104,108 @@ func TestNormalizeAppliesSafeAliasProposalAndPreservesCurrency(t *testing.T) {
|
||||
if strings.Contains(encoded, doc.ID) || !strings.Contains(encoded, "candidate-000001") || strings.Contains(encoded, merged.ID) {
|
||||
t.Fatalf("private inputs = %s", encoded)
|
||||
}
|
||||
currencyClient := &recordingNormalizerClient{response: `{"duplicate_groups":[{"members":["candidate-000001","candidate-000002"],"canonical":"candidate-000001"}]}`}
|
||||
currency := dnd.ItemRegistry{Items: []dnd.Item{{Name: "Gold Pieces", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}}}, {Name: "Silver Pieces", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 20}}}}}
|
||||
result, err = newNormalizer(t, currencyClient).Normalize(context.Background(), normalizeRequestWithSource(currency, doc))
|
||||
if err != nil || result.Retry == nil || len(result.Value.Items) != 2 || !hasWarning(result.Warnings, ReasonCodeItemSemanticProposalInvalid) {
|
||||
t.Fatalf("currency result = %#v, %v; want denominations retained", result, err)
|
||||
}
|
||||
|
||||
func TestNormalizeAppliesCurrencyReconciliationSafely(t *testing.T) {
|
||||
doc := semanticDocument()
|
||||
ref := func(unitID int) []source.SourceRef {
|
||||
return []source.SourceRef{{SourceID: doc.ID, StartUnitID: unitID, EndUnitID: unitID}}
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
items []dnd.Item
|
||||
response string
|
||||
wantNames []string
|
||||
wantRefCounts []int
|
||||
wantRetry bool
|
||||
warning string
|
||||
}{
|
||||
{
|
||||
name: "same denomination aliases",
|
||||
items: []dnd.Item{
|
||||
{Name: "GP", SourceRefs: ref(10)},
|
||||
{Name: "Gold Piece", SourceRefs: ref(20)},
|
||||
{Name: "Gold Pieces", SourceRefs: ref(30)},
|
||||
},
|
||||
response: `{"duplicate_groups":[{"members":["candidate-000001","candidate-000002","candidate-000003"],"canonical":"candidate-000002"}]}`,
|
||||
wantNames: []string{"Gold Piece"},
|
||||
wantRefCounts: []int{3},
|
||||
warning: ReasonCodeDuplicateItemCollapsed,
|
||||
},
|
||||
{
|
||||
name: "different denominations",
|
||||
items: []dnd.Item{
|
||||
{Name: "Gold Pieces", SourceRefs: ref(10)},
|
||||
{Name: "Silver Pieces", SourceRefs: ref(20)},
|
||||
},
|
||||
response: `{"duplicate_groups":[{"members":["candidate-000001","candidate-000002"],"canonical":"candidate-000001"}]}`,
|
||||
wantNames: []string{"Gold Pieces", "Silver Pieces"},
|
||||
wantRefCounts: []int{1, 1},
|
||||
wantRetry: true,
|
||||
warning: ReasonCodeItemSemanticProposalInvalid,
|
||||
},
|
||||
{
|
||||
name: "currency plus ordinary item",
|
||||
items: []dnd.Item{
|
||||
{Name: "Gold Pieces", SourceRefs: ref(10)},
|
||||
{Name: "Longsword", SourceRefs: ref(20)},
|
||||
},
|
||||
response: `{"duplicate_groups":[{"members":["candidate-000001","candidate-000002"],"canonical":"candidate-000001"}]}`,
|
||||
wantNames: []string{"Gold Pieces", "Longsword"},
|
||||
wantRefCounts: []int{1, 1},
|
||||
wantRetry: true,
|
||||
warning: ReasonCodeItemSemanticProposalInvalid,
|
||||
},
|
||||
{
|
||||
name: "ordinary items",
|
||||
items: []dnd.Item{
|
||||
{Name: "Star Compass", SourceRefs: ref(10)},
|
||||
{Name: "Compass of the Stars", SourceRefs: ref(20)},
|
||||
},
|
||||
response: `{"duplicate_groups":[{"members":["candidate-000001","candidate-000002"],"canonical":"candidate-000002"}]}`,
|
||||
wantNames: []string{"Compass of the Stars"},
|
||||
wantRefCounts: []int{2},
|
||||
warning: ReasonCodeDuplicateItemCollapsed,
|
||||
},
|
||||
{
|
||||
name: "currency plus ordinary canonical item",
|
||||
items: []dnd.Item{
|
||||
{Name: "Gold Pieces", SourceRefs: ref(10)},
|
||||
{Name: "Longsword", SourceRefs: ref(20)},
|
||||
},
|
||||
response: `{"duplicate_groups":[{"members":["candidate-000001","candidate-000002"],"canonical":"candidate-000002"}]}`,
|
||||
wantNames: []string{"Gold Pieces", "Longsword"},
|
||||
wantRefCounts: []int{1, 1},
|
||||
wantRetry: true,
|
||||
warning: ReasonCodeItemSemanticProposalInvalid,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
input := dnd.ItemRegistry{Items: make([]dnd.Item, len(test.items))}
|
||||
for index, item := range test.items {
|
||||
input.Items[index] = cloneItem(item)
|
||||
}
|
||||
before := dnd.ItemRegistry{Items: make([]dnd.Item, len(input.Items))}
|
||||
for index, item := range input.Items {
|
||||
before.Items[index] = cloneItem(item)
|
||||
}
|
||||
|
||||
result, err := newNormalizer(t, &recordingNormalizerClient{response: test.response}).Normalize(context.Background(), normalizeRequestWithSource(input, doc))
|
||||
if err != nil || (result.Retry != nil) != test.wantRetry || !reflect.DeepEqual(input, before) || !hasWarning(result.Warnings, test.warning) {
|
||||
t.Fatalf("Normalize() = %#v, %v", result, err)
|
||||
}
|
||||
if len(result.Value.Items) != len(test.wantNames) {
|
||||
t.Fatalf("items = %#v, want names %#v", result.Value.Items, test.wantNames)
|
||||
}
|
||||
for index, item := range result.Value.Items {
|
||||
if item.Name != test.wantNames[index] || len(item.SourceRefs) != test.wantRefCounts[index] {
|
||||
t.Fatalf("item %d = %#v, want name %q with %d source refs", index, item, test.wantNames[index], test.wantRefCounts[index])
|
||||
}
|
||||
}
|
||||
if test.wantRetry && (len(result.Retry.FallbackWarnings) != 1 || result.Retry.FallbackWarnings[0].ReasonCode != ReasonCodeItemSemanticReconciliationExhausted) {
|
||||
t.Fatalf("retry = %#v, want preserved-group fallback", result.Retry)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user