Apply spell normalization follow-up fixes
This commit is contained in:
@@ -22,7 +22,7 @@ import (
|
||||
const assembledSpellExtractorKey = "test/dnd/spell-casts"
|
||||
|
||||
func TestAssembledSpellPipelineNormalizesMergedCasts(t *testing.T) {
|
||||
registries, resolved, extractor := assembledSpellPipeline(t, false)
|
||||
registries, resolved, extractor := assembledSpellPipeline(t, assembledSpellPipelineOptions{})
|
||||
prepared, err := pipeline.Prepare(resolved, registries, pipeline.ModuleDependencies{})
|
||||
if err != nil {
|
||||
t.Fatalf("Prepare() error = %v, want nil", err)
|
||||
@@ -102,7 +102,7 @@ func TestAssembledSpellPipelineNormalizesMergedCasts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAssembledSpellPipelineHonorsNormalizeValidatorOverride(t *testing.T) {
|
||||
registries, resolved, _ := assembledSpellPipeline(t, true)
|
||||
registries, resolved, _ := assembledSpellPipeline(t, assembledSpellPipelineOptions{normalizeValidatorOverride: true})
|
||||
var normalizeChain *pipeline.ResolvedValidatorChain
|
||||
for index := range resolved.ValidatorChains {
|
||||
chain := &resolved.ValidatorChains[index]
|
||||
@@ -134,10 +134,84 @@ func TestAssembledSpellPipelineHonorsNormalizeValidatorOverride(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func assembledSpellPipeline(t *testing.T, override bool) (pipeline.Registries, pipeline.ResolvedPipeline, *assembledSpellExtractor) {
|
||||
func TestAssembledSpellPipelineRejectsUnknownSpellWithoutPromotingAttemptWarning(t *testing.T) {
|
||||
registries, resolved, _ := assembledSpellPipeline(t, assembledSpellPipelineOptions{unknownSpell: true})
|
||||
prepared, err := pipeline.Prepare(resolved, registries, pipeline.ModuleDependencies{})
|
||||
if err != nil {
|
||||
t.Fatalf("Prepare() error = %v, want nil", err)
|
||||
}
|
||||
output, err := pipeline.New().Run(context.Background(), pipeline.RunInput{
|
||||
Prepared: prepared,
|
||||
RawInput: readRepositoryFile(t, "examples", "seriatim-minimal-transcript.json"),
|
||||
ChunkCacheMode: pipeline.ChunkCacheBypass,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v, want nil", err)
|
||||
}
|
||||
if output.Manifest.ValidationStatus != "rejected" || len(output.NormalizeOutputs) != 0 || len(output.Rejected) != 1 {
|
||||
t.Fatalf("run output = %#v, want one rejected normalize candidate and no normalized output", output)
|
||||
}
|
||||
rejection := output.Rejected[0]
|
||||
if rejection.Stage != string(pipeline.StageNormalize) || rejection.LaneID != "spells" || rejection.ModuleKey != spellnormalize.Key || rejection.ValidatorName != "extract/dnd/spells/catalog" || rejection.ReasonCode != "unknown_spell" {
|
||||
t.Fatalf("rejection = %#v, want durable normalize catalog rejection", rejection)
|
||||
}
|
||||
rejectedFile := decodeAssembledOutput[struct {
|
||||
Rejected []contracts.RejectedOutput `json:"rejected"`
|
||||
}](t, output.OutputFiles, "rejected.json")
|
||||
if !reflect.DeepEqual(rejectedFile.Rejected, output.Rejected) {
|
||||
t.Fatalf("rejected file = %#v, run rejections = %#v, want durable rejection diagnostic", rejectedFile.Rejected, output.Rejected)
|
||||
}
|
||||
for _, warning := range output.Warnings {
|
||||
if warning.ReasonCode == spellnormalize.ReasonCodeSpellNameUnresolved {
|
||||
t.Fatalf("warnings = %#v, want rejected-attempt warning to remain non-durable", output.Warnings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssembledSpellPipelinePromotesUnknownSpellWarningWhenOverrideAccepts(t *testing.T) {
|
||||
registries, resolved, _ := assembledSpellPipeline(t, assembledSpellPipelineOptions{normalizeValidatorOverride: true, unknownSpell: true})
|
||||
prepared, err := pipeline.Prepare(resolved, registries, pipeline.ModuleDependencies{})
|
||||
if err != nil {
|
||||
t.Fatalf("Prepare() error = %v, want nil", err)
|
||||
}
|
||||
output, err := pipeline.New().Run(context.Background(), pipeline.RunInput{
|
||||
Prepared: prepared,
|
||||
RawInput: readRepositoryFile(t, "examples", "seriatim-minimal-transcript.json"),
|
||||
ChunkCacheMode: pipeline.ChunkCacheBypass,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v, want nil", err)
|
||||
}
|
||||
if output.Manifest.ValidationStatus != "approved" || len(output.Rejected) != 0 || len(output.NormalizeOutputs) != 1 {
|
||||
t.Fatalf("run output = %#v, want accepted unknown spell with explicit validator override", output)
|
||||
}
|
||||
var normalized dnd.SpellList
|
||||
if err := json.Unmarshal(output.NormalizeOutputs[0].Artifact.Content, &normalized); err != nil {
|
||||
t.Fatalf("decode normalized output: %v", err)
|
||||
}
|
||||
if len(normalized.SpellCasts) != 1 || normalized.SpellCasts[0].Spell != "Mysterious Burst" {
|
||||
t.Fatalf("normalized casts = %#v, want unresolved name preserved", normalized.SpellCasts)
|
||||
}
|
||||
if len(output.Warnings) != 1 || output.Warnings[0].ReasonCode != spellnormalize.ReasonCodeSpellNameUnresolved || output.Warnings[0].Scope != "spell_casts[0]" {
|
||||
t.Fatalf("warnings = %#v, want promoted scoped unresolved-name warning", output.Warnings)
|
||||
}
|
||||
warningsFile := decodeAssembledOutput[struct {
|
||||
Warnings []contracts.Warning `json:"warnings"`
|
||||
}](t, output.OutputFiles, "warnings.json")
|
||||
if !reflect.DeepEqual(warningsFile.Warnings, output.Warnings) {
|
||||
t.Fatalf("warnings file = %#v, run warnings = %#v, want durable unresolved-name warning", warningsFile.Warnings, output.Warnings)
|
||||
}
|
||||
}
|
||||
|
||||
type assembledSpellPipelineOptions struct {
|
||||
normalizeValidatorOverride bool
|
||||
unknownSpell bool
|
||||
}
|
||||
|
||||
func assembledSpellPipeline(t *testing.T, options assembledSpellPipelineOptions) (pipeline.Registries, pipeline.ResolvedPipeline, *assembledSpellExtractor) {
|
||||
t.Helper()
|
||||
components := productionTestComponents(t)
|
||||
extractor := &assembledSpellExtractor{}
|
||||
extractor := &assembledSpellExtractor{unknownSpell: options.unknownSpell}
|
||||
if err := pipeline.RegisterExtractor[dnd.SpellList](components.registries.Extractors, pipeline.ModuleSpec{
|
||||
Key: assembledSpellExtractorKey,
|
||||
Stage: pipeline.StageExtract,
|
||||
@@ -151,7 +225,7 @@ func assembledSpellPipeline(t *testing.T, override bool) (pipeline.Registries, p
|
||||
}
|
||||
|
||||
normalize := pipeline.Binding(spellnormalize.Key)
|
||||
if override {
|
||||
if options.normalizeValidatorOverride {
|
||||
normalize.Validators = pipeline.ValidatorOverride{
|
||||
Set: true,
|
||||
Validators: []pipeline.ModuleBinding{pipeline.Binding("generic/always_accept")},
|
||||
@@ -175,6 +249,7 @@ func assembledSpellPipeline(t *testing.T, override bool) (pipeline.Registries, p
|
||||
type assembledSpellExtractor struct {
|
||||
mu sync.Mutex
|
||||
chunkIndexes []int
|
||||
unknownSpell bool
|
||||
}
|
||||
|
||||
func (e *assembledSpellExtractor) Key() string { return assembledSpellExtractorKey }
|
||||
@@ -193,6 +268,14 @@ func (e *assembledSpellExtractor) Extract(ctx context.Context, req contracts.Typ
|
||||
e.mu.Unlock()
|
||||
refOne := source.SourceRef{SourceID: req.Source.ID, StartUnitID: 1, EndUnitID: 1}
|
||||
refTwo := source.SourceRef{SourceID: req.Source.ID, StartUnitID: 2, EndUnitID: 2}
|
||||
if e.unknownSpell {
|
||||
if req.Chunk.Index == 0 {
|
||||
return contracts.TypedExtractionResult[dnd.SpellList]{Value: dnd.SpellList{SpellCasts: []dnd.SpellCast{{
|
||||
Caster: "Aria", Spell: "Mysterious Burst", Effect: "an unknown magical effect", NarrativeDescription: "Aria produces a mysterious burst.", SourceRefs: []source.SourceRef{refOne},
|
||||
}}}}, nil
|
||||
}
|
||||
return contracts.TypedExtractionResult[dnd.SpellList]{Value: dnd.SpellList{SpellCasts: []dnd.SpellCast{}}}, nil
|
||||
}
|
||||
switch req.Chunk.Index {
|
||||
case 0:
|
||||
return contracts.TypedExtractionResult[dnd.SpellList]{Value: dnd.SpellList{SpellCasts: []dnd.SpellCast{{
|
||||
|
||||
Reference in New Issue
Block a user