2026-07-25 21:52:59 +00:00
package shape
import (
"context"
2026-08-28 14:37:22 +00:00
"fmt"
2026-07-25 21:52:59 +00:00
"reflect"
"strings"
"testing"
"unicode/utf8"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
)
2026-08-05 20:00:20 +00:00
func TestValidatorAcceptsEveryOccurrenceRuleAndNormalizableWhitespace ( t * testing . T ) {
2026-07-25 21:52:59 +00:00
quantity := 1
2026-08-05 19:55:34 +00:00
value := dnd . ItemOccurrenceList { Occurrences : [ ] dnd . ItemOccurrence { { ItemID : "item" , Name : " Hidden Cache " , Kind : dnd . ItemOccurrenceKindDiscovered , From : " " , To : " " , SourceRefs : refs ( 1 , 1 ) } ,
{ ItemID : "item" , Name : "Gold Pieces" , Kind : dnd . ItemOccurrenceKindAcquired , Quantity : & quantity , To : " party " , SourceRefs : refs ( 2 , 2 ) } ,
{ ItemID : "item" , Name : "Torch" , Kind : dnd . ItemOccurrenceKindLost , From : " party " , SourceRefs : refs ( 3 , 3 ) } ,
{ ItemID : "item" , Name : "Potion" , Kind : dnd . ItemOccurrenceKindConsumed , From : " Aria " , SourceRefs : refs ( 4 , 4 ) } ,
{ ItemID : "item" , Name : "Moonblade" , Kind : dnd . ItemOccurrenceKindTransferred , From : "Aria" , To : "Borin" , SourceRefs : refs ( 5 , 5 ) } ,
2026-07-25 21:52:59 +00:00
} }
2026-08-05 19:55:34 +00:00
result , err := New ( Options { } ) . Validate ( context . Background ( ) , contracts . TypedValidationRequest [ dnd . ItemOccurrenceList ] { Value : value } )
2026-07-25 21:52:59 +00:00
if err != nil || ! result . Approved {
t . Fatalf ( "Validate() = %#v, %v" , result , err )
}
2026-08-05 19:55:34 +00:00
empty , err := New ( Options { } ) . Validate ( context . Background ( ) , contracts . TypedValidationRequest [ dnd . ItemOccurrenceList ] { Value : dnd . ItemOccurrenceList { Occurrences : [ ] dnd . ItemOccurrence { } } } )
2026-07-25 21:52:59 +00:00
if err != nil || ! empty . Approved {
t . Fatalf ( "empty list result = %#v, %v" , empty , err )
}
}
func TestValidatorRejectsOwnedSemanticBoundaries ( t * testing . T ) {
valid := validList ( )
zero := 0
negative := - 1
tests := [ ] struct {
name string
2026-08-05 19:55:34 +00:00
value dnd . ItemOccurrenceList
2026-07-25 21:52:59 +00:00
want string
} {
2026-08-05 20:00:20 +00:00
{ "missing occurrences" , dnd . ItemOccurrenceList { } , "occurrences must be present" } ,
2026-08-05 19:55:34 +00:00
{ "blank name" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : " \t" , Kind : dnd . ItemOccurrenceKindDiscovered , SourceRefs : refs ( 1 , 1 ) } ) , "name must not be empty" } ,
{ "unsupported kind" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : "unknown" , SourceRefs : refs ( 1 , 1 ) } ) , "kind is unsupported" } ,
{ "discovered holder" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindDiscovered , From : "Aria" , SourceRefs : refs ( 1 , 1 ) } ) , "incompatible" } ,
{ "acquired without holder" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindAcquired , SourceRefs : refs ( 1 , 1 ) } ) , "incompatible" } ,
{ "lost destination" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindLost , From : "Aria" , To : "Borin" , SourceRefs : refs ( 1 , 1 ) } ) , "incompatible" } ,
{ "consumed without holder" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindConsumed , SourceRefs : refs ( 1 , 1 ) } ) , "incompatible" } ,
{ "party transfer from" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindTransferred , From : "party" , To : "Borin" , SourceRefs : refs ( 1 , 1 ) } ) , "incompatible" } ,
{ "party transfer to" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindTransferred , From : "Aria" , To : " PARTY " , SourceRefs : refs ( 1 , 1 ) } ) , "incompatible" } ,
{ "case equivalent transfer" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindTransferred , From : "Aria" , To : "aria" , SourceRefs : refs ( 1 , 1 ) } ) , "incompatible" } ,
{ "unicode equivalent transfer" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindTransferred , From : "Åria" , To : "Åria" , SourceRefs : refs ( 1 , 1 ) } ) , "incompatible" } ,
{ "zero quantity" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindAcquired , Quantity : & zero , To : "party" , SourceRefs : refs ( 1 , 1 ) } ) , "quantity must be positive" } ,
{ "negative quantity" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindAcquired , Quantity : & negative , To : "party" , SourceRefs : refs ( 1 , 1 ) } ) , "quantity must be positive" } ,
{ "missing source refs" , listWith ( dnd . ItemOccurrence { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindDiscovered } ) , "source_refs must contain" } ,
2026-07-25 21:52:59 +00:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2026-08-05 19:55:34 +00:00
result , err := New ( Options { } ) . Validate ( context . Background ( ) , contracts . TypedValidationRequest [ dnd . ItemOccurrenceList ] { Value : test . value } )
2026-07-25 21:52:59 +00:00
if err != nil || result . Approved || result . ReasonCode != ReasonCode || ! strings . Contains ( result . Message , test . want ) {
t . Fatalf ( "Validate() = %#v, %v; want %q" , result , err , test . want )
}
} )
}
2026-08-05 19:55:34 +00:00
if result , err := New ( Options { } ) . Validate ( context . Background ( ) , contracts . TypedValidationRequest [ dnd . ItemOccurrenceList ] { Value : valid } ) ; err != nil || ! result . Approved {
2026-07-25 21:52:59 +00:00
t . Fatalf ( "valid result = %#v, %v" , result , err )
}
}
2026-08-28 14:37:22 +00:00
func TestValidatorProvidesActionableContextualHolderGuidance ( t * testing . T ) {
tests := [ ] struct {
name string
occurrence dnd . ItemOccurrence
wantGuidance [ ] string
} {
{
name : "discovered" ,
occurrence : dnd . ItemOccurrence { ItemID : "item:sha256:opaque" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindDiscovered , From : "Aria" , SourceRefs : refs ( 1 , 2 ) } ,
wantGuidance : [ ] string { "For `discovered` occurrences" , "both `from` and `to` to JSON null" } ,
} ,
{
name : "acquired" ,
occurrence : dnd . ItemOccurrence { ItemID : "item:sha256:opaque" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindAcquired , From : "Merchant" , To : "party" , SourceRefs : refs ( 1 , 2 ) } ,
wantGuidance : [ ] string { "For `acquired` occurrences" , "`from` to JSON null" , "named party member gaining possession" } ,
} ,
{
name : "lost" ,
occurrence : dnd . ItemOccurrence { ItemID : "item:sha256:opaque" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindLost , From : "party" , To : "Merchant" , SourceRefs : refs ( 1 , 2 ) } ,
wantGuidance : [ ] string { "For `lost` occurrences" , "named party member losing possession" , "`to` to JSON null" } ,
} ,
{
name : "consumed" ,
occurrence : dnd . ItemOccurrence { ItemID : "item:sha256:opaque" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindConsumed , SourceRefs : refs ( 1 , 2 ) } ,
wantGuidance : [ ] string { "For `consumed` occurrences" , "named party member consuming the item" , "`to` to JSON null" } ,
} ,
{
name : "transferred" ,
occurrence : dnd . ItemOccurrence { ItemID : "item:sha256:opaque" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindTransferred , From : "party" , To : "Borin" , SourceRefs : refs ( 1 , 2 ) } ,
wantGuidance : [ ] string { "For `transferred` occurrences" , "two distinct named party members" , "never use `party`" } ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
result , err := New ( Options { } ) . Validate ( context . Background ( ) , contracts . TypedValidationRequest [ dnd . ItemOccurrenceList ] { Value : listWith ( test . occurrence ) } )
if err != nil || result . Approved {
t . Fatalf ( "Validate() = %#v, %v; want rejection" , result , err )
}
for _ , fragment := range append ( test . wantGuidance , "item \"Ring\"" , "source units 1-2" ) {
if ! strings . Contains ( result . CorrectionGuidance , fragment ) {
t . Fatalf ( "CorrectionGuidance = %q, want %q" , result . CorrectionGuidance , fragment )
}
}
for _ , forbidden := range [ ] string { test . occurrence . ItemID , "sha256" , Key , ReasonCode , "occurrences[0]" } {
if strings . Contains ( result . CorrectionGuidance , forbidden ) {
t . Fatalf ( "CorrectionGuidance leaked implementation identifier %q: %q" , forbidden , result . CorrectionGuidance )
}
}
if ! strings . Contains ( result . Message , "expected" ) || ! strings . Contains ( result . Message , "got from" ) {
t . Fatalf ( "operator message = %q, want expected and observed holders" , result . Message )
}
} )
}
}
func TestValidatorGroupsRepeatedHolderCorrectionsAndRetainsDistinctRules ( t * testing . T ) {
value := dnd . ItemOccurrenceList { Occurrences : [ ] dnd . ItemOccurrence {
{ ItemID : "item-1" , Name : "Silver Pieces" , Kind : dnd . ItemOccurrenceKindAcquired , From : "Orc" , To : "party" , SourceRefs : refs ( 1 , 1 ) } ,
{ ItemID : "item-2" , Name : "Torch" , Kind : dnd . ItemOccurrenceKindAcquired , From : "Chest" , To : "Aria" , SourceRefs : refs ( 2 , 2 ) } ,
{ ItemID : "item-3" , Name : "Potion" , Kind : dnd . ItemOccurrenceKindConsumed , SourceRefs : refs ( 3 , 3 ) } ,
} }
result , err := New ( Options { } ) . Validate ( context . Background ( ) , contracts . TypedValidationRequest [ dnd . ItemOccurrenceList ] { Value : value } )
if err != nil || result . Approved {
t . Fatalf ( "Validate() = %#v, %v; want rejection" , result , err )
}
if strings . Count ( result . CorrectionGuidance , "For `acquired` occurrences" ) != 1 || strings . Count ( result . CorrectionGuidance , "For `consumed` occurrences" ) != 1 {
t . Fatalf ( "CorrectionGuidance = %q, want one rule per affected kind" , result . CorrectionGuidance )
}
for _ , fragment := range [ ] string { "item \"Silver Pieces\"" , "source unit 1" , "item \"Torch\"" , "source unit 2" , "item \"Potion\"" , "source unit 3" } {
if ! strings . Contains ( result . CorrectionGuidance , fragment ) {
t . Fatalf ( "CorrectionGuidance = %q, want contextual fragment %q" , result . CorrectionGuidance , fragment )
}
}
}
2026-07-25 21:52:59 +00:00
func TestValidatorAggregatesBoundedIndexedDiagnosticsAndRegistration ( t * testing . T ) {
2026-08-05 19:55:34 +00:00
value := dnd . ItemOccurrenceList { Occurrences : make ( [ ] dnd . ItemOccurrence , 24 ) }
for index := range value . Occurrences {
2026-08-28 14:37:22 +00:00
value . Occurrences [ index ] = dnd . ItemOccurrence { ItemID : "item" , Name : fmt . Sprintf ( "Item %d" , index ) , Kind : "unsupported" }
2026-07-25 21:52:59 +00:00
}
2026-08-05 19:55:34 +00:00
result , err := New ( Options { } ) . Validate ( context . Background ( ) , contracts . TypedValidationRequest [ dnd . ItemOccurrenceList ] { Value : value } )
if err != nil || result . Approved || result . ReasonCode != ReasonCode || len ( [ ] byte ( result . Message ) ) > 4096 || ! utf8 . ValidString ( result . Message ) || ! strings . Contains ( result . Message , "occurrences[0]" ) || ! strings . Contains ( result . Message , "additional issue(s) omitted" ) {
2026-07-25 21:52:59 +00:00
t . Fatalf ( "Validate() = %#v, %v" , result , err )
}
2026-08-28 14:37:22 +00:00
if len ( [ ] byte ( result . CorrectionGuidance ) ) > 4096 || ! utf8 . ValidString ( result . CorrectionGuidance ) || ! strings . Contains ( result . CorrectionGuidance , "additional issue(s) omitted" ) {
t . Fatalf ( "CorrectionGuidance = %q, want bounded aggregate" , result . CorrectionGuidance )
}
if value . Occurrences [ 0 ] . Name != "Item 0" {
2026-07-25 21:52:59 +00:00
t . Fatal ( "Validate() mutated input" )
}
if got := New ( Options { } ) . CheckpointFingerprints ( ) ; ! reflect . DeepEqual ( got , [ ] pipeline . CheckpointFingerprint { { Name : "policy" , Value : policy } } ) {
t . Fatalf ( "CheckpointFingerprints() = %#v" , got )
}
registry := pipeline . NewValidatorRegistry ( )
if err := Register ( registry ) ; err != nil {
t . Fatal ( err )
}
if got , ok := registry . Spec ( Key ) ; ! ok || ! reflect . DeepEqual ( got , Spec ( ) ) {
t . Fatalf ( "registry spec = %#v, %t" , got , ok )
}
if _ , err := DecodeOptions ( map [ string ] any { "unexpected" : true } ) ; err == nil {
t . Fatal ( "DecodeOptions() accepted unknown option" )
}
}
2026-08-05 19:55:34 +00:00
func validList ( ) dnd . ItemOccurrenceList {
return dnd . ItemOccurrenceList { Occurrences : [ ] dnd . ItemOccurrence { { ItemID : "item" , Name : "Ring" , Kind : dnd . ItemOccurrenceKindAcquired , To : "party" , SourceRefs : refs ( 1 , 1 ) } } }
2026-07-25 21:52:59 +00:00
}
2026-08-05 20:00:20 +00:00
func listWith ( occurrence dnd . ItemOccurrence ) dnd . ItemOccurrenceList {
return dnd . ItemOccurrenceList { Occurrences : [ ] dnd . ItemOccurrence { occurrence } }
2026-07-25 21:52:59 +00:00
}
func refs ( start , end int ) [ ] source . SourceRef {
return [ ] source . SourceRef { { SourceID : "session" , StartUnitID : start , EndUnitID : end } }
}