136 lines
5.5 KiB
Go
136 lines
5.5 KiB
Go
package validators
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
domain "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 TestRecognizedTermsDeduplicateAndIncludePlurals(t *testing.T) {
|
|
plural := "Hranks"
|
|
entries := []domain.Entry{{ID: "jesters", Name: "Jesters", Aliases: []string{"Jester", " "}}, {ID: "hrank", Name: "Hrank", Aliases: []string{"hrank", "Hrank"}, Plural: &plural}, {ID: "lyra", Name: "Lyra"}}
|
|
var got []string
|
|
for _, term := range domain.Vocabulary(entries, nil) {
|
|
got = append(got, term.Spelling)
|
|
}
|
|
sort.Strings(got)
|
|
want := []string{"Hrank", "Hranks", "Jester", "Jesters", "Jesterss", "Lyra", "Lyras"}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("got %v want %v", got, want)
|
|
}
|
|
if len(domain.Vocabulary(nil, nil)) != 0 {
|
|
t.Fatal("empty input produced terms")
|
|
}
|
|
}
|
|
|
|
func TestValidateProtectedTermsAppliesToNonGlossaryModule(t *testing.T) {
|
|
req := Request{
|
|
ModuleKey: "grammar",
|
|
Glossary: []domain.Entry{{ID: "jesters", Name: "Jesters", Category: "faction"}},
|
|
ProtectedTerms: domain.Vocabulary([]domain.Entry{{ID: "jesters", Name: "Jesters"}}, nil),
|
|
CandidateProposal: []proposals.EnrichedCorrectionProposal{
|
|
{
|
|
CorrectionProposal: proposals.CorrectionProposal{
|
|
TargetSegmentID: 1,
|
|
OriginalText: "Jesters",
|
|
CorrectedText: "Gestures",
|
|
Confidence: 0.9,
|
|
},
|
|
ProposalMetadata: proposals.ProposalMetadata{ProposalIndex: 0},
|
|
},
|
|
{
|
|
CorrectionProposal: proposals.CorrectionProposal{TargetSegmentID: 1, OriginalText: "Jesters", CorrectedText: "", Confidence: 0.9},
|
|
ProposalMetadata: proposals.ProposalMetadata{ProposalIndex: 1},
|
|
},
|
|
},
|
|
}
|
|
res, err := ValidateProtectedTerms(req)
|
|
if err != nil {
|
|
t.Fatalf("validator error: %v", err)
|
|
}
|
|
if len(res.Decisions) != 2 || res.Decisions[0].Approved || res.Decisions[1].Approved || res.Decisions[1].ReasonCode != ReasonProtectedGlossaryTerm {
|
|
t.Fatalf("expected protected-term rejection, got %+v", res.Decisions)
|
|
}
|
|
}
|
|
|
|
func TestValidateProtectedTermsGlossaryStageAllowsProtectedTermSwap(t *testing.T) {
|
|
req := Request{
|
|
ModuleKey: "glossary",
|
|
Glossary: []domain.Entry{{ID: "jesters", Name: "Jesters", Category: "faction"}},
|
|
CandidateProposal: []proposals.EnrichedCorrectionProposal{
|
|
{
|
|
CorrectionProposal: proposals.CorrectionProposal{
|
|
TargetSegmentID: 1,
|
|
OriginalText: "gestures",
|
|
CorrectedText: "Jesters",
|
|
Confidence: 0.9,
|
|
},
|
|
ProposalMetadata: proposals.ProposalMetadata{ProposalIndex: 0},
|
|
},
|
|
},
|
|
}
|
|
res, err := ValidateProtectedTerms(req)
|
|
if err != nil {
|
|
t.Fatalf("validator error: %v", err)
|
|
}
|
|
if len(res.Decisions) != 1 || !res.Decisions[0].Approved {
|
|
t.Fatalf("expected approval for glossary-stage protected term correction, got %+v", res.Decisions)
|
|
}
|
|
}
|
|
|
|
func TestProtectedNameRepetitionRequiresSurvivingIdentityAndSpokenWordScope(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name, module, before, original, replacement string
|
|
allowed bool
|
|
}{
|
|
{"whole repetition", "spoken_word", "Jesters Jesters arrived", "Jesters Jesters", "Jesters", true},
|
|
{"span deletion", "spoken_word", "Jesters, Jesters arrived", "Jesters, ", "", true},
|
|
{"last reference", "spoken_word", "Jesters arrived", "Jesters ", "", false},
|
|
{"different identity", "spoken_word", "Jesters Jesters arrived", "Jesters Jesters", "Lyra", false},
|
|
{"spelling degraded", "spoken_word", "Jesters Jesters arrived", "Jesters Jesters", "jestERS", false},
|
|
{"grammar unchanged", "grammar", "Jesters Jesters arrived", "Jesters Jesters", "Jesters", false},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := Request{ModuleKey: tc.module, ReplacementPolicy: proposals.ReplacementPolicyRequireUnique,
|
|
WorkingTranscript: &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: tc.before}}},
|
|
ProtectedTerms: domain.Vocabulary([]domain.Entry{{ID: "jesters", Name: "Jesters"}, {ID: "lyra", Name: "Lyra"}}, nil),
|
|
CandidateProposal: []proposals.EnrichedCorrectionProposal{{CorrectionProposal: proposals.CorrectionProposal{TargetSegmentID: 1, OriginalText: tc.original, CorrectedText: tc.replacement, Confidence: 1}}},
|
|
}
|
|
result, err := ValidateProtectedTerms(req)
|
|
if err != nil || len(result.Decisions) != 1 || result.Decisions[0].Approved != tc.allowed {
|
|
t.Fatalf("result=%+v err=%v", result, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProtectedNameFindingIdentifiesRemovedAlias(t *testing.T) {
|
|
req := Request{
|
|
ModuleKey: "glossary",
|
|
IndependentEvidence: true,
|
|
ReplacementPolicy: proposals.ReplacementPolicyReplaceAll,
|
|
WorkingTranscript: &schema.Transcript{Segments: []schema.Segment{
|
|
{ID: 1, Text: "Mira spoke to Captain Mira"},
|
|
}},
|
|
ProtectedTerms: domain.Vocabulary([]domain.Entry{{ID: "mira", Name: "Mira", Aliases: []string{"Captain Mira"}}}, nil),
|
|
CandidateProposal: []proposals.EnrichedCorrectionProposal{{
|
|
CorrectionProposal: proposals.CorrectionProposal{
|
|
TargetSegmentID: 1, OriginalText: "Captain Mira", CorrectedText: "the captain", Confidence: 0.9,
|
|
},
|
|
}},
|
|
}
|
|
result, err := ValidateProtectedTerms(req)
|
|
if err != nil || len(result.Decisions) != 1 {
|
|
t.Fatalf("result=%+v err=%v", result, err)
|
|
}
|
|
decision := result.Decisions[0]
|
|
if decision.Approved || decision.ReasonCode != ReasonProtectedGlossaryTerm || !strings.Contains(decision.Message, `"Captain Mira"`) {
|
|
t.Fatalf("finding did not identify the removed alias: %+v", decision)
|
|
}
|
|
}
|