Files
notarius/internal/cli/dnd_npc_contract_test.go

133 lines
6.2 KiB
Go

package cli
import (
"reflect"
"strings"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcs"
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcs"
)
func TestProductionNPCConfigurationResolvesTypedLane(t *testing.T) {
components := productionTestComponents(t)
catalog := catalogFromRegistries(components.registries)
configPath := repositoryPath("examples", "dnd-npcs.config.yml")
cfg := loadMaintainedExample(t, configPath)
effective, err := cfg.Resolve(config.ResolveInput{PipelineID: "dnd-session", Catalog: catalog})
if err != nil {
t.Fatalf("Resolve() error = %v, want nil", err)
}
if effective.ResolvedPipeline.Chunk.Module != pipeline.DefaultChunkModule {
t.Fatalf("chunk module = %q, want %q", effective.ResolvedPipeline.Chunk.Module, pipeline.DefaultChunkModule)
}
if len(effective.ResolvedPipeline.ArtifactLanes) != 1 {
t.Fatalf("artifact lanes = %#v, want one NPC lane", effective.ResolvedPipeline.ArtifactLanes)
}
lane := effective.ResolvedPipeline.ArtifactLanes[0]
if lane.ID != "npcs" || lane.ArtifactKind != dnd.NPCListKind || lane.Extract.Module != npcextract.Key || lane.Extract.Retries != 2 || lane.Merge.Module != pipeline.DefaultMergeModule || lane.Normalize.Module != npcnormalize.Key {
t.Fatalf("resolved NPC lane = %#v, want typed production composition", lane)
}
if len(lane.ExtractReferences.Bindings) != 0 || len(lane.NormalizeReferences.Bindings) != 0 {
t.Fatalf("unbound NPC references = %#v / %#v, want none", lane.ExtractReferences, lane.NormalizeReferences)
}
extractSpec, ok := catalog.Extractors.Spec(npcextract.Key)
if !ok || !reflect.DeepEqual(extractSpec.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(extractSpec.Provides, []string{"dnd.npcs"}) {
t.Fatalf("NPC extractor spec = %#v, want source and artifact capabilities", extractSpec)
}
mergeSpec, ok := catalog.Mergers.SpecForArtifact(pipeline.DefaultMergeModule, dnd.NPCListKind)
if !ok || !reflect.DeepEqual(mergeSpec.Provides, []string{"merged"}) {
t.Fatalf("NPC merger spec = %#v, want merged capability", mergeSpec)
}
normalizeSpec, ok := catalog.Normalizers.SpecForArtifact(npcnormalize.Key, dnd.NPCListKind)
if !ok || !reflect.DeepEqual(normalizeSpec.Requires, []string{"merged"}) || !reflect.DeepEqual(normalizeSpec.Provides, []string{"normalized"}) {
t.Fatalf("NPC normalizer spec = %#v, want merged/normalized capabilities", normalizeSpec)
}
wantExtractChain := []pipeline.ModuleBinding{
pipeline.Binding("generic/valid_json"),
pipeline.Binding("generic/valid_json_schema"),
pipeline.Binding("extract/dnd/npcs/shape"),
pipeline.Binding("extract/dnd/npcs/source_refs"),
pipeline.Binding("extract/dnd/npcs/source_relatedness"),
}
wantNormalizeChain := []pipeline.ModuleBinding{
pipeline.Binding("generic/valid_json"),
pipeline.Binding("generic/valid_json_schema"),
pipeline.Binding("extract/dnd/npcs/shape"),
pipeline.Binding("normalize/dnd/npcs/identity"),
pipeline.Binding("extract/dnd/npcs/source_refs"),
pipeline.Binding("extract/dnd/npcs/source_relatedness"),
}
if got := validatorChain(effective.ResolvedPipeline, pipeline.StageExtract, npcextract.Key); !reflect.DeepEqual(got, wantExtractChain) {
t.Fatalf("NPC extract chain = %#v, want %#v", got, wantExtractChain)
}
if got := validatorChain(effective.ResolvedPipeline, pipeline.StageNormalize, npcnormalize.Key); !reflect.DeepEqual(got, wantNormalizeChain) {
t.Fatalf("NPC normalize chain = %#v, want %#v", got, wantNormalizeChain)
}
if got := validatorChain(effective.ResolvedPipeline, pipeline.StageMerge, pipeline.DefaultMergeModule); len(got) != 0 {
t.Fatalf("NPC merge chain = %#v, want empty", got)
}
}
func TestProductionNPCConfigurationValidatesOptionsReferencesAndPlacement(t *testing.T) {
components := productionTestComponents(t)
configPath := repositoryPath("examples", "dnd-npcs.config.yml")
resolve := func(mutate func(*pipeline.PipelineProfile)) error {
cfg := loadMaintainedExample(t, configPath)
profile := cfg.Pipelines["dnd-session"]
mutate(&profile)
cfg.Pipelines["dnd-session"] = profile
_, err := cfg.Resolve(config.ResolveInput{PipelineID: "dnd-session", Catalog: catalogFromRegistries(components.registries)})
return err
}
if err := resolve(func(profile *pipeline.PipelineProfile) {
lane := profile.Artifacts["npcs"]
lane.Extract.Options = map[string]any{"unexpected": true}
profile.Artifacts["npcs"] = lane
}); err == nil || !strings.Contains(err.Error(), "unknown option") {
t.Fatalf("unknown extractor option error = %v, want strict option rejection", err)
}
if err := resolve(func(profile *pipeline.PipelineProfile) {
lane := profile.Artifacts["npcs"]
lane.Normalize.Options = map[string]any{"unexpected": true}
profile.Artifacts["npcs"] = lane
}); err == nil || !strings.Contains(err.Error(), "unknown option") {
t.Fatalf("unknown normalizer option error = %v, want strict option rejection", err)
}
if err := resolve(func(profile *pipeline.PipelineProfile) {
profile.References = map[string]string{
"players": "players.txt",
"party": "party.txt",
"glossary": "glossary.txt",
}
}); err != nil {
t.Fatalf("optional NPC references error = %v, want resolution success", err)
}
if err := resolve(func(profile *pipeline.PipelineProfile) {
lane := profile.Artifacts["npcs"]
lane.Validators = []pipeline.ModuleBinding{pipeline.Binding("normalize/dnd/npcs/identity")}
profile.Artifacts["npcs"] = lane
}); err == nil || !strings.Contains(err.Error(), "artifact lane level") {
t.Fatalf("lane-level validator error = %v, want invalid placement rejection", err)
}
}
func validatorChain(resolved pipeline.ResolvedPipeline, stage pipeline.ModuleStage, module string) []pipeline.ModuleBinding {
for _, chain := range resolved.ValidatorChains {
if chain.Stage == stage && chain.ModuleKey == module {
bindings := make([]pipeline.ModuleBinding, len(chain.Validators))
for index, validator := range chain.Validators {
bindings[index] = validator.Binding
}
return bindings
}
}
return nil
}