Finish the D&D module cleanup
This commit is contained in:
@@ -334,6 +334,9 @@ func TestDocumentIndexSnapshotsIdentityAndUnitPositions(t *testing.T) {
|
||||
doc.ID = "changed"
|
||||
doc.Units[0].ID = 99
|
||||
|
||||
if documentID, ok := index.DocumentID(); !ok || documentID != "source-1" {
|
||||
t.Fatalf("DocumentID() = %q, %t, want source-1, true", documentID, ok)
|
||||
}
|
||||
if position, ok := index.Position(30); !ok || position != 0 {
|
||||
t.Fatalf("Position(30) = %d, %t, want 0, true", position, ok)
|
||||
}
|
||||
@@ -348,6 +351,9 @@ func TestDocumentIndexSnapshotsIdentityAndUnitPositions(t *testing.T) {
|
||||
|
||||
func TestZeroDocumentIndexIsSafe(t *testing.T) {
|
||||
var index DocumentIndex
|
||||
if documentID, ok := index.DocumentID(); ok || documentID != "" {
|
||||
t.Fatalf("DocumentID() = %q, %t, want empty, false", documentID, ok)
|
||||
}
|
||||
if position, ok := index.Position(1); ok || position != 0 {
|
||||
t.Fatalf("Position(1) = %d, %t, want 0, false", position, ok)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ func NewDocumentIndex(doc *SourceDocument) DocumentIndex {
|
||||
}
|
||||
}
|
||||
|
||||
// DocumentID returns the indexed document identity.
|
||||
func (i DocumentIndex) DocumentID() (string, bool) {
|
||||
if !i.hasDocument {
|
||||
return "", false
|
||||
}
|
||||
return i.documentID, true
|
||||
}
|
||||
|
||||
// Position returns the indexed document position for unitID.
|
||||
func (i DocumentIndex) Position(unitID int) (int, bool) {
|
||||
position, ok := i.positions[unitID]
|
||||
|
||||
@@ -111,7 +111,7 @@ func (n *Normalizer) Normalize(ctx context.Context, req contracts.TypedNormalize
|
||||
return contracts.TypedNormalizeResult[dnd.CombatTurnList]{}, normalizerErrorf("resolve NPC registry: %w", err)
|
||||
}
|
||||
index := source.NewDocumentIndex(req.Source)
|
||||
order := shared.NewSourceRefOrderWithIndex(req.Source, index)
|
||||
order := shared.NewSourceRefOrderFromIndex(index)
|
||||
value, warnings := normalizeList(req.MergeOutput.Value, index, order, npcRegistry)
|
||||
return contracts.TypedNormalizeResult[dnd.CombatTurnList]{Value: value, Warnings: warnings}, nil
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func (n *Normalizer) Normalize(ctx context.Context, req contracts.TypedNormalize
|
||||
return contracts.TypedNormalizeResult[dnd.NPCInteractionList]{}, normalizerErrorf("NPC registry reference is required")
|
||||
}
|
||||
index := source.NewDocumentIndex(req.Source)
|
||||
order := shared.NewSourceRefOrderWithIndex(req.Source, index)
|
||||
order := shared.NewSourceRefOrderFromIndex(index)
|
||||
value, warnings := normalizeList(req.MergeOutput.Value, index, order, registry)
|
||||
return contracts.TypedNormalizeResult[dnd.NPCInteractionList]{Value: value, Warnings: warnings}, nil
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func (n *Normalizer) Normalize(ctx context.Context, req contracts.TypedNormalize
|
||||
}
|
||||
|
||||
index := source.NewDocumentIndex(req.Source)
|
||||
order := shared.NewSourceRefOrderWithIndex(req.Source, index)
|
||||
order := shared.NewSourceRefOrderFromIndex(index)
|
||||
value, warnings := normalizeSpellList(req.MergeOutput.Value, n.effectiveCatalog, order)
|
||||
value, duplicateWarnings := collapseDuplicateSpellCasts(value, index, n.effectiveCatalog)
|
||||
warnings = append(warnings, duplicateWarnings...)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package register
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
)
|
||||
@@ -110,32 +112,28 @@ func appendSceneDescriptionLists(values []dnd.SceneDescriptionList) (dnd.SceneDe
|
||||
|
||||
func cloneCombatTurn(value dnd.CombatTurn) dnd.CombatTurn {
|
||||
clone := value
|
||||
if value.SourceRefs != nil {
|
||||
clone.SourceRefs = append([]source.SourceRef(nil), value.SourceRefs...)
|
||||
}
|
||||
clone.SourceRefs = cloneSourceRefs(value.SourceRefs)
|
||||
return clone
|
||||
}
|
||||
|
||||
func cloneSpellCast(value dnd.SpellCast) dnd.SpellCast {
|
||||
clone := value
|
||||
if value.SourceRefs != nil {
|
||||
clone.SourceRefs = append([]source.SourceRef(nil), value.SourceRefs...)
|
||||
}
|
||||
clone.SourceRefs = cloneSourceRefs(value.SourceRefs)
|
||||
return clone
|
||||
}
|
||||
|
||||
func cloneNPC(value dnd.NPC) dnd.NPC {
|
||||
clone := value
|
||||
if value.SourceRefs != nil {
|
||||
clone.SourceRefs = append([]source.SourceRef(nil), value.SourceRefs...)
|
||||
}
|
||||
clone.SourceRefs = cloneSourceRefs(value.SourceRefs)
|
||||
return clone
|
||||
}
|
||||
|
||||
func cloneNPCInteraction(value dnd.NPCInteraction) dnd.NPCInteraction {
|
||||
clone := value
|
||||
if value.SourceRefs != nil {
|
||||
clone.SourceRefs = append([]source.SourceRef(nil), value.SourceRefs...)
|
||||
}
|
||||
clone.SourceRefs = cloneSourceRefs(value.SourceRefs)
|
||||
return clone
|
||||
}
|
||||
|
||||
func cloneSourceRefs(refs []source.SourceRef) []source.SourceRef {
|
||||
return slices.Clone(refs)
|
||||
}
|
||||
|
||||
@@ -409,6 +409,28 @@ func TestAppendCombatTurnListsPreservesOrderPresenceAndOwnership(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendListsPreserveNestedSourceReferencePresence(t *testing.T) {
|
||||
spells, err := appendSpellLists([]dnd.SpellList{{SpellCasts: []dnd.SpellCast{{SourceRefs: []source.SourceRef{}}}}})
|
||||
if err != nil || spells.SpellCasts[0].SourceRefs == nil {
|
||||
t.Fatalf("appendSpellLists() = %#v, %v; want present-empty source refs", spells, err)
|
||||
}
|
||||
|
||||
npcs, err := appendNPCLists([]dnd.NPCList{{NPCs: []dnd.NPC{{SourceRefs: []source.SourceRef{}}}}})
|
||||
if err != nil || npcs.NPCs[0].SourceRefs == nil {
|
||||
t.Fatalf("appendNPCLists() = %#v, %v; want present-empty source refs", npcs, err)
|
||||
}
|
||||
|
||||
turns, err := appendCombatTurnLists([]dnd.CombatTurnList{{CombatTurns: []dnd.CombatTurn{{SourceRefs: []source.SourceRef{}}}}})
|
||||
if err != nil || turns.CombatTurns[0].SourceRefs == nil {
|
||||
t.Fatalf("appendCombatTurnLists() = %#v, %v; want present-empty source refs", turns, err)
|
||||
}
|
||||
|
||||
interactions, err := appendNPCInteractionLists([]dnd.NPCInteractionList{{Interactions: []dnd.NPCInteraction{{SourceRefs: []source.SourceRef{}}}}})
|
||||
if err != nil || interactions.Interactions[0].SourceRefs == nil {
|
||||
t.Fatalf("appendNPCInteractionLists() = %#v, %v; want present-empty source refs", interactions, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterRejectsMissingDNDDependenciesBeforeMutation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -15,16 +15,17 @@ type SourceRefOrder struct {
|
||||
|
||||
// NewSourceRefOrder captures the source identity and unit positions from doc.
|
||||
func NewSourceRefOrder(doc *source.SourceDocument) SourceRefOrder {
|
||||
return NewSourceRefOrderWithIndex(doc, source.NewDocumentIndex(doc))
|
||||
return NewSourceRefOrderFromIndex(source.NewDocumentIndex(doc))
|
||||
}
|
||||
|
||||
// NewSourceRefOrderWithIndex captures source identity with a supplied
|
||||
// document index for source-reference comparison and canonicalization.
|
||||
func NewSourceRefOrderWithIndex(doc *source.SourceDocument, index source.DocumentIndex) SourceRefOrder {
|
||||
if doc == nil {
|
||||
// NewSourceRefOrderFromIndex captures source identity and unit positions from
|
||||
// one document index for source-reference comparison and canonicalization.
|
||||
func NewSourceRefOrderFromIndex(index source.DocumentIndex) SourceRefOrder {
|
||||
sourceID, ok := index.DocumentID()
|
||||
if !ok {
|
||||
return SourceRefOrder{}
|
||||
}
|
||||
return SourceRefOrder{sourceID: doc.ID, index: index}
|
||||
return SourceRefOrder{sourceID: sourceID, index: index}
|
||||
}
|
||||
|
||||
// Less orders references by source identity, then document positions when
|
||||
|
||||
@@ -60,19 +60,22 @@ func TestSourceRefOrderCanonicalizePreservesRepresentationAndOwnership(t *testin
|
||||
|
||||
func TestSourceRefOrderSnapshotAndEarliestValid(t *testing.T) {
|
||||
doc := unitRefSourceDocument(30, 10, 20)
|
||||
order := NewSourceRefOrder(doc)
|
||||
sourceID := doc.ID
|
||||
index := source.NewDocumentIndex(doc)
|
||||
order := NewSourceRefOrderFromIndex(index)
|
||||
doc.ID = "changed"
|
||||
doc.Units[0].ID, doc.Units[1].ID = 10, 30
|
||||
refs := []source.SourceRef{
|
||||
{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 20},
|
||||
{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10},
|
||||
{SourceID: sourceID, StartUnitID: 20, EndUnitID: 20},
|
||||
{SourceID: sourceID, StartUnitID: 10, EndUnitID: 10},
|
||||
{SourceID: "other", StartUnitID: 1, EndUnitID: 1},
|
||||
{SourceID: doc.ID, StartUnitID: 999, EndUnitID: 999},
|
||||
{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 30},
|
||||
{SourceID: sourceID, StartUnitID: 999, EndUnitID: 999},
|
||||
{SourceID: sourceID, StartUnitID: 20, EndUnitID: 30},
|
||||
}
|
||||
if position, ok := order.EarliestValid(refs); !ok || position != 1 {
|
||||
t.Fatalf("EarliestValid() = %d, %t, want 1, true", position, ok)
|
||||
}
|
||||
if position, ok := order.EarliestValid([]source.SourceRef{{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 30}}); ok || position != 0 {
|
||||
if position, ok := order.EarliestValid([]source.SourceRef{{SourceID: sourceID, StartUnitID: 20, EndUnitID: 30}}); ok || position != 0 {
|
||||
t.Fatalf("EarliestValid(invalid) = %d, %t, want 0, false", position, ok)
|
||||
}
|
||||
if position, ok := (SourceRefOrder{}).EarliestValid(refs); ok || position != 0 {
|
||||
|
||||
@@ -55,7 +55,7 @@ func Validate(doc *source.SourceDocument, value dnd.CombatTurnList) error {
|
||||
if !sourceRefsValid(index, value) {
|
||||
return nil
|
||||
}
|
||||
issues := issuesFor(shared.NewSourceRefOrderWithIndex(doc, index), value)
|
||||
issues := issuesFor(shared.NewSourceRefOrderFromIndex(index), value)
|
||||
if len(issues) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
if !npcRegistry.Bound() {
|
||||
return contracts.ValidationResult{Approved: false, ReasonCode: ReasonCode, Message: "invalid NPC interaction normalization: NPC registry reference is required"}, nil
|
||||
}
|
||||
issues := issuesFor(shared.NewSourceRefOrderWithIndex(req.Source, index), req.Value, npcRegistry)
|
||||
issues := issuesFor(shared.NewSourceRefOrderFromIndex(index), req.Value, npcRegistry)
|
||||
if len(issues) == 0 {
|
||||
return contracts.ValidationResult{Approved: true}, nil
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.SceneDescriptionList]) (contracts.ValidationResult, error) {
|
||||
if shape.Validate(req.Value) != nil || !sourceRefsValid(req.Source, req.Value) {
|
||||
if shape.Validate(req.Value) != nil {
|
||||
return contracts.ValidationResult{Approved: true}, nil
|
||||
}
|
||||
resolver, err := shared.NewCitationResolver(req.Source)
|
||||
@@ -72,15 +72,6 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sourceRefsValid(doc *source.SourceDocument, value dnd.SceneDescriptionList) bool {
|
||||
for _, scene := range value.Scenes {
|
||||
if source.ValidateRef(doc, scene.SourceRef) != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func tokenSet(value string) map[string]struct{} {
|
||||
tokens := make(map[string]struct{})
|
||||
for _, token := range shared.NormalizedTokens(value) {
|
||||
|
||||
@@ -54,6 +54,14 @@ func TestValidatorUsesTranscriptOnlyAndDefersInvalidInputs(t *testing.T) {
|
||||
if err != nil || !malformed.Approved || len(malformed.Warnings) != 0 {
|
||||
t.Fatalf("malformed Validate() = %#v, %v; want deferral", malformed, err)
|
||||
}
|
||||
|
||||
invalidScene := scene("invalid", "Greencloak", "Greencloak arrives.")
|
||||
invalidScene.SourceRef.StartUnitID = 99
|
||||
invalidSource := dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{invalidScene}}
|
||||
deferred, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.SceneDescriptionList]{Source: doc, Value: invalidSource})
|
||||
if err != nil || !deferred.Approved || len(deferred.Warnings) != 0 {
|
||||
t.Fatalf("invalid-source Validate() = %#v, %v; want deferral", deferred, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatorRegistrationAndPolicyFingerprint(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user