package validators import ( "context" "testing" "gitea.maximumdirect.net/eric/audita/internal/core/schema" "gitea.maximumdirect.net/eric/audita/internal/framework/contracts" "gitea.maximumdirect.net/eric/audita/internal/framework/proposals" "gitea.maximumdirect.net/eric/audita/internal/validators/confidence_threshold" "gitea.maximumdirect.net/eric/audita/internal/validators/editorial_review" "gitea.maximumdirect.net/eric/audita/internal/validators/meaning_reversal_review" validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata" "gitea.maximumdirect.net/eric/audita/internal/validators/no_effect" "gitea.maximumdirect.net/eric/audita/internal/validators/non_empty_corrected_text" "gitea.maximumdirect.net/eric/audita/internal/validators/original_text_presence" "gitea.maximumdirect.net/eric/audita/internal/validators/proposal_shape" "gitea.maximumdirect.net/eric/audita/internal/validators/protected_terms" "gitea.maximumdirect.net/eric/audita/internal/validators/spoken_form_plausibility" ) func TestBuiltInRegistryRegistersAllKeys(t *testing.T) { r := NewBuiltInRegistry() for _, key := range []string{ KeyProposalShape, KeyConfidenceThreshold, KeyOriginalTextPresence, KeyNonEmptyCorrectedText, KeyNoEffect, KeyProtectedTerms, KeySpokenFormPlausibility, KeyMeaningReversalReview, KeyEditorialReview, } { def, ok := r.Lookup(key) if !ok { t.Fatalf("expected validator key %q to be registered", key) } v, err := def.Build() if err != nil { t.Fatalf("build validator %q: %v", key, err) } if v.Name() != key { t.Fatalf("expected validator name %q, got %q", key, v.Name()) } } } func TestBuiltInValidatorPackagesConstruct(t *testing.T) { type validatorCtor struct { name string key string build func() (contracts.Validator, error) wantClass validatormetadata.ExecutionClass } cases := []validatorCtor{ {name: "proposal_shape", key: KeyProposalShape, build: proposal_shape.New, wantClass: validatormetadata.ExecutionClassDeterministic}, {name: "confidence_threshold", key: KeyConfidenceThreshold, build: confidence_threshold.New, wantClass: validatormetadata.ExecutionClassDeterministic}, {name: "original_text_presence", key: KeyOriginalTextPresence, build: original_text_presence.New, wantClass: validatormetadata.ExecutionClassDeterministic}, {name: "non_empty_corrected_text", key: KeyNonEmptyCorrectedText, build: non_empty_corrected_text.New, wantClass: validatormetadata.ExecutionClassDeterministic}, {name: "no_effect", key: KeyNoEffect, build: no_effect.New, wantClass: validatormetadata.ExecutionClassDeterministic}, {name: "protected_terms", key: KeyProtectedTerms, build: protected_terms.New, wantClass: validatormetadata.ExecutionClassDeterministic}, {name: "spoken_form_plausibility", key: KeySpokenFormPlausibility, build: spoken_form_plausibility.New, wantClass: validatormetadata.ExecutionClassLLMBacked}, {name: "meaning_reversal_review", key: KeyMeaningReversalReview, build: meaning_reversal_review.New, wantClass: validatormetadata.ExecutionClassLLMBacked}, {name: "editorial_review", key: KeyEditorialReview, build: editorial_review.New, wantClass: validatormetadata.ExecutionClassLLMBacked}, } for _, tc := range cases { v, err := tc.build() if err != nil { t.Fatalf("%s: build: %v", tc.name, err) } if v.Name() != tc.key { t.Fatalf("%s: expected key %q, got %q", tc.name, tc.key, v.Name()) } if got := validatormetadata.ClassOf(v); got != tc.wantClass { t.Fatalf("%s: expected class %q, got %q", tc.name, tc.wantClass, got) } } } func TestRegistryBuildsClassifiedValidators(t *testing.T) { r := NewBuiltInRegistry() llmKeys := map[string]bool{ KeySpokenFormPlausibility: true, KeyMeaningReversalReview: true, KeyEditorialReview: true, } for _, key := range r.RegisteredKeys() { v, err := r.MustBuild(key) if err != nil { t.Fatalf("must build %q: %v", key, err) } if _, ok := v.(validatormetadata.ClassifiedValidator); !ok { t.Fatalf("expected built validator %q to expose execution classification metadata", key) } want := validatormetadata.ExecutionClassDeterministic if llmKeys[key] { want = validatormetadata.ExecutionClassLLMBacked } if got := validatormetadata.ClassOf(v); got != want { t.Fatalf("expected class %q for %q, got %q", want, key, got) } } } func TestRegistryProtectedTermsUsesNonGlossaryStageBehavior(t *testing.T) { r := NewBuiltInRegistry() v, err := r.MustBuild(KeyProtectedTerms) if err != nil { t.Fatalf("must build protected_terms: %v", err) } req := contracts.ValidationRequest{ ModuleKey: "glossary", Glossary: &schema.Glossary{ Entries: []schema.GlossaryEntry{{Name: "Jesters", Category: "faction", Summary: "Protected"}}, }, CandidateProposal: []proposals.EnrichedCorrectionProposal{ { CorrectionProposal: proposals.CorrectionProposal{ TargetSegmentID: 1, OriginalText: "Jesters", CorrectedText: "Gestures", Confidence: 1.0, }, ProposalMetadata: proposals.ProposalMetadata{ ProposalIndex: 0, ModuleKey: "glossary", }, }, }, } result, err := v.Validate(context.Background(), req) if err != nil { t.Fatalf("validate protected_terms: %v", err) } if len(result.Decisions) != 1 { t.Fatalf("expected one decision, got %+v", result.Decisions) } if !result.Decisions[0].Approved { t.Fatalf("expected glossary module proposal to be approved by non-glossary-stage protected_terms validator, got %+v", result.Decisions[0]) } } func TestResolveBuiltInChainUsesGlossaryStageProtectedTermsBehavior(t *testing.T) { chain, err := ResolveBuiltInChain("glossary", NewBuiltInRegistry()) if err != nil { t.Fatalf("resolve glossary chain: %v", err) } var v contracts.Validator for _, candidate := range chain { if candidate.Name() == KeyProtectedTerms { v = candidate break } } if v == nil { t.Fatalf("expected protected_terms validator in glossary chain") } req := contracts.ValidationRequest{ ModuleKey: "glossary", Glossary: &schema.Glossary{ Entries: []schema.GlossaryEntry{{Name: "Jesters", Category: "faction", Summary: "Protected"}}, }, CandidateProposal: []proposals.EnrichedCorrectionProposal{ { CorrectionProposal: proposals.CorrectionProposal{ TargetSegmentID: 1, OriginalText: "Jesters", CorrectedText: "Gestures", Confidence: 1.0, }, ProposalMetadata: proposals.ProposalMetadata{ ProposalIndex: 0, ModuleKey: "glossary", }, }, }, } result, err := v.Validate(context.Background(), req) if err != nil { t.Fatalf("validate protected_terms: %v", err) } if len(result.Decisions) != 1 { t.Fatalf("expected one decision, got %+v", result.Decisions) } if result.Decisions[0].Approved { t.Fatalf("expected glossary-stage protected_terms rejection, got %+v", result.Decisions[0]) } } func TestBuiltInRegistryUnknownKeyFails(t *testing.T) { r := NewBuiltInRegistry() if _, err := r.MustBuild("missing"); err == nil { t.Fatalf("expected unknown validator key error") } } func TestBuiltInChainKeysResolveForProductionModules(t *testing.T) { for _, moduleKey := range []string{"glossary", "homophones", "spoken_word", "grammar"} { keys, err := BuiltInChainKeys(moduleKey) if err != nil { t.Fatalf("resolve keys for %q: %v", moduleKey, err) } if len(keys) == 0 { t.Fatalf("expected non-empty chain for %q", moduleKey) } } } func TestResolveBuiltInChainUsesRegisteredKeys(t *testing.T) { r := NewBuiltInRegistry() for _, moduleKey := range []string{"glossary", "homophones", "spoken_word", "grammar"} { chain, err := ResolveBuiltInChain(moduleKey, r) if err != nil { t.Fatalf("resolve chain for %q: %v", moduleKey, err) } if len(chain) == 0 { t.Fatalf("expected non-empty chain for %q", moduleKey) } for _, v := range chain { if v == nil { t.Fatalf("nil validator in %q chain", moduleKey) } } } } func TestBuiltInChainUnknownModuleFails(t *testing.T) { if _, err := BuiltInChainKeys("unknown"); err == nil { t.Fatalf("expected unknown module chain failure") } if _, err := ResolveBuiltInChain("unknown", NewBuiltInRegistry()); err == nil { t.Fatalf("expected unknown module chain failure") } }