package validators import ( "testing" "gitea.maximumdirect.net/eric/audita/v3/internal/core/glossary" "gitea.maximumdirect.net/eric/audita/v3/internal/core/schema" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals" ) func TestIndependentProtectionForSelectedGlossary(t *testing.T) { active := []glossary.Entry{{Collection: "spells", ID: "shield", Name: "Shield"}} entries := append(append([]glossary.Entry(nil), active...), glossary.Entry{Collection: "items", ID: "shield", Name: "Shield", Aliases: []string{"Aegis"}}) cases := []struct { name string terms []glossary.Term before, after string approved bool }{ {"shared spelling exempt", glossary.Vocabulary(entries, active), "Shield", "Barrier", true}, {"other alias protected", glossary.Vocabulary(entries, active), "Aegis", "Armor", false}, {"full protection retained", glossary.Vocabulary(entries, nil), "Shield", "Barrier", false}, {"empty protection", nil, "Aegis", "Armor", true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { result, err := ValidateProtectedTerms(Request{ProtectedTerms: tc.terms, CandidateProposal: []proposals.EnrichedCorrectionProposal{{CorrectionProposal: proposals.CorrectionProposal{OriginalText: tc.before, CorrectedText: tc.after}}}}) if err != nil || len(result.Decisions) != 1 || result.Decisions[0].Approved != tc.approved { t.Fatalf("result=%+v err=%v", result, err) } }) } } func TestSharedAliasRetainsAllIdentities(t *testing.T) { entries := []glossary.Entry{ {Collection: "a", ID: "first", Name: "First", Aliases: []string{"Shared"}}, {Collection: "b", ID: "second", Name: "Second", Aliases: []string{"Shared"}}, } vocabulary := NewProtectedVocabulary(glossary.Vocabulary(entries, nil)) identities := vocabulary.occurrencesByIdentity("Shared") if len(identities) != 2 || len(identities[glossary.Identity{Collection: "a", ID: "first"}]) != 1 || len(identities[glossary.Identity{Collection: "b", ID: "second"}]) != 1 { t.Fatalf("identities=%v", identities) } } func TestApplicationChecksCurrentTextWithSelectedProtection(t *testing.T) { active := []glossary.Entry{{Collection: "spells", ID: "spell", Name: "Shield"}} protected := append(append([]glossary.Entry(nil), active...), glossary.Entry{Collection: "items", ID: "item", Name: "Shield", Aliases: []string{"Aegis"}}) vocabulary := NewProtectedVocabulary(glossary.Vocabulary(protected, active)) transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "Shield and Aegis"}}} candidates := []proposals.EnrichedCorrectionProposal{ {CorrectionProposal: proposals.CorrectionProposal{TargetSegmentID: 1, OriginalText: "Shield", CorrectedText: "Barrier", Confidence: 1}, ProposalMetadata: proposals.ProposalMetadata{ProposalIndex: 0}}, {CorrectionProposal: proposals.CorrectionProposal{TargetSegmentID: 1, OriginalText: "egis", CorrectedText: "rmor", Confidence: 1}, ProposalMetadata: proposals.ProposalMetadata{ProposalIndex: 1}}, } result := proposals.ApplyProposals(transcript, candidates, proposals.ReplacementPolicyRequireUnique, func(before, after string) bool { return vocabulary.AllowsChange(before, after, false) }) if result.Transcript.Segments[0].Text != "Barrier and Aegis" || len(result.Applied) != 1 || len(result.Skipped) != 1 || result.Skipped[0].SkipReason != proposals.SkipReasonProtectedTerm { t.Fatalf("unexpected application: %+v", result) } } func TestExemptGlossarySpellingKeepsLongestMatchBoundary(t *testing.T) { active := []glossary.Entry{ {ID: "greater-mage", Name: "Greater Mage Armor"}, {ID: "greater-magus", Name: "Greater Magus Armor"}, } protected := []glossary.Entry{ active[0], active[1], {ID: "short", Name: "Mage Armor"}, {ID: "long", Name: "Superior Greater Mage Armor"}, } for _, tc := range []struct { name, module, before, original, replacement string exempt, allowed bool }{ {"full active name", "glossary", "Cast Greater Mage Armor.", "Greater Mage Armor", "Greater Magus Armor", true, true}, {"span within active name", "glossary", "Cast Greater Mage Armor.", "Mage Armor", "Magus Armor", true, true}, {"standalone protected name", "glossary", "Cast Mage Armor.", "Mage Armor", "Magus Armor", true, false}, {"longer protected name", "glossary", "Cast Superior Greater Mage Armor.", "Greater Mage Armor", "Greater Magus Armor", true, false}, {"same name in a grammar pass", "grammar", "Cast Greater Mage Armor.", "Greater Mage Armor", "Greater Magus Armor", false, false}, } { t.Run(tc.name, func(t *testing.T) { var exclusions []glossary.Entry if tc.exempt { exclusions = active } terms := glossary.Vocabulary(protected, exclusions) transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: tc.before}}} candidate := proposals.EnrichedCorrectionProposal{CorrectionProposal: proposals.CorrectionProposal{ TargetSegmentID: 1, OriginalText: tc.original, CorrectedText: tc.replacement, Confidence: 1, }} policy := proposals.ReplacementPolicyRequireUnique decision, err := ValidateProtectedTerms(Request{ModuleKey: tc.module, WorkingTranscript: transcript, ReplacementPolicy: policy, ProtectedTerms: terms, CandidateProposal: []proposals.EnrichedCorrectionProposal{candidate}}) if err != nil || len(decision.Decisions) != 1 || decision.Decisions[0].Approved != tc.allowed { t.Fatalf("decision=%+v err=%v", decision, err) } vocabulary := NewProtectedVocabulary(terms) result := proposals.ApplyProposals(transcript, []proposals.EnrichedCorrectionProposal{candidate}, policy, func(before, after string) bool { return vocabulary.AllowsChange(before, after, false) }) if (len(result.Applied) == 1) != tc.allowed { t.Fatalf("apply-time protection disagrees with validation: %+v", result) } }) } } func TestValidationContextIncludesEachSharedDefinition(t *testing.T) { entries := []glossary.Entry{ {Collection: "items", ID: "sword", Name: "Sword", Category: "item"}, {Collection: "spells", ID: "shield", Name: "Shield", Category: "spell"}, {Collection: "items", ID: "shield", Name: "Shield", Category: "item"}, {Collection: "items", ID: "bow", Name: "Bow", Category: "item"}, } segments := []schema.Segment{{ID: 1, Text: "Shield Shield"}} _, selected := prepareGlossaryContext(entries).validationContext(segments, 0, "Sword") if len(selected) != 3 || selected[0].ID != "sword" || selected[1].Category != "spell" || selected[2].ID != "shield" || selected[2].Collection != "items" { t.Fatalf("selected=%+v", selected) } }