package cli import ( "context" "errors" "strings" "testing" "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" ) const invalidEnemyEventExtractorKey = "test/dnd/invalid-enemy-events" func TestAssembledEnemyEventLaneRejectsInvalidFinalArtifactDespiteValidatorOverrides(t *testing.T) { components := productionTestComponents(t) if err := pipeline.RegisterExtractor[dnd.EnemyEventList](components.registries.Extractors, pipeline.ModuleSpec{ Key: invalidEnemyEventExtractorKey, Stage: pipeline.StageExtract, ExecutionClass: contracts.ExecutionClassDeterministic, Requires: []string{"chunks", "source.transcript"}, Provides: []string{"dnd.enemy_events"}, ArtifactKind: dnd.EnemyEventListKind, }, func() (contracts.Extractor[dnd.EnemyEventList], error) { return invalidEnemyEventExtractor{}, nil }); err != nil { t.Fatalf("register extractor: %v", err) } accept := pipeline.ValidatorOverride{Set: true, Validators: []pipeline.ModuleBinding{pipeline.Binding("generic/always_accept")}} resolved, err := pipeline.ResolvePipeline(pipeline.PipelineProfile{ ID: "assembled-invalid-enemy-events", Input: pipeline.Binding("seriatim"), Chunk: pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}}, Artifacts: map[string]pipeline.ArtifactLaneProfile{ "enemy-events": { Extract: pipeline.ModuleBinding{Module: invalidEnemyEventExtractorKey, Validators: accept}, Normalize: pipeline.ModuleBinding{Module: pipeline.DefaultNormalizeModule, Validators: accept}, }, }, Output: pipeline.Binding("json"), }, pipeline.ResolveOptions{}, catalogFromRegistries(components.registries)) if err != nil { t.Fatalf("ResolvePipeline() error = %v", err) } prepared, err := pipeline.Prepare(resolved, components.registries, pipeline.ModuleDependencies{}) if err != nil { t.Fatalf("Prepare() error = %v", err) } _, err = pipeline.New().Run(context.Background(), pipeline.RunInput{ Prepared: prepared, RawInput: readRepositoryFile(t, "examples", "seriatim-minimal-transcript.json"), ChunkCacheMode: pipeline.ChunkCacheBypass, }) if err == nil || !strings.Contains(err.Error(), "serialize accepted extract output") || !strings.Contains(err.Error(), "must not exceed") { t.Fatalf("Run() error = %v, want final durable range rejection", err) } } type invalidEnemyEventExtractor struct{} func (invalidEnemyEventExtractor) Key() string { return invalidEnemyEventExtractorKey } func (invalidEnemyEventExtractor) ReferenceSlots() []contracts.ReferenceSlot { return nil } func (invalidEnemyEventExtractor) Extract(ctx context.Context, req contracts.TypedExtractionRequest) (contracts.TypedExtractionResult[dnd.EnemyEventList], error) { if err := ctx.Err(); err != nil { return contracts.TypedExtractionResult[dnd.EnemyEventList]{}, err } if req.Source == nil { return contracts.TypedExtractionResult[dnd.EnemyEventList]{}, errors.New("assembled extractor requires source") } return contracts.TypedExtractionResult[dnd.EnemyEventList]{Value: dnd.EnemyEventList{Events: []dnd.EnemyEvent{{ Name: "Ashfang", Kind: dnd.EnemyEventKindEngaged, SourceRefs: []source.SourceRef{{SourceID: req.Source.ID, StartUnitID: 2, EndUnitID: 1}}, }}}}, nil }