2026-07-07 17:12:38 +05:30
|
|
|
using FluentAssertions;
|
|
|
|
|
using PiiRedaction.Core.Detection;
|
|
|
|
|
using PiiRedaction.Core.Models;
|
|
|
|
|
using PiiRedaction.Infrastructure.Onnx;
|
|
|
|
|
|
|
|
|
|
namespace PiiRedaction.Infrastructure.Tests.Onnx;
|
|
|
|
|
|
|
|
|
|
[TestFixture]
|
|
|
|
|
public sealed class RoutingOnnxNerModelRunnerTests
|
|
|
|
|
{
|
|
|
|
|
[Test]
|
|
|
|
|
public void PredictEntities_LatinOnly_UsesEnglishRunnerOnly()
|
|
|
|
|
{
|
2026-07-08 12:18:27 +05:30
|
|
|
var english = new FakeLanguageNerRunner("Ravi Kumar", NerModelOrigin.English);
|
|
|
|
|
var tamil = new FakeLanguageNerRunner("தமிழ் பெயர்", NerModelOrigin.Tamil);
|
2026-07-07 17:12:38 +05:30
|
|
|
var router = new RoutingOnnxNerModelRunner(english, tamil, enableTamilNer: true);
|
|
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
var result = router.PredictEntities("Customer Ravi Kumar called.");
|
2026-07-07 17:12:38 +05:30
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
result.Entities.Should().ContainSingle(entity =>
|
|
|
|
|
entity.Value == "Ravi Kumar" && entity.ModelOrigin == NerModelOrigin.English);
|
|
|
|
|
result.InvokedModels.Should().Equal(NerModelOrigin.English);
|
2026-07-07 17:12:38 +05:30
|
|
|
english.CallCount.Should().Be(1);
|
|
|
|
|
tamil.CallCount.Should().Be(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void PredictEntities_TamilOnly_UsesTamilRunnerOnly()
|
|
|
|
|
{
|
2026-07-08 12:18:27 +05:30
|
|
|
var english = new FakeLanguageNerRunner("Ravi Kumar", NerModelOrigin.English);
|
|
|
|
|
var tamil = new FakeLanguageNerRunner("ராஜேஷ்", NerModelOrigin.Tamil);
|
2026-07-07 17:12:38 +05:30
|
|
|
var router = new RoutingOnnxNerModelRunner(english, tamil, enableTamilNer: true);
|
|
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
var result = router.PredictEntities("வாடிக்கையாளர் ராஜேஷ்");
|
2026-07-07 17:12:38 +05:30
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
result.Entities.Should().ContainSingle(entity =>
|
|
|
|
|
entity.Value == "ராஜேஷ்" && entity.ModelOrigin == NerModelOrigin.Tamil);
|
|
|
|
|
result.InvokedModels.Should().Equal(NerModelOrigin.Tamil);
|
2026-07-07 17:12:38 +05:30
|
|
|
english.CallCount.Should().Be(0);
|
|
|
|
|
tamil.CallCount.Should().Be(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
2026-07-08 12:18:27 +05:30
|
|
|
public void PredictEntities_Mixed_InvokesBothRunnersAndTagsOrigins()
|
2026-07-07 17:12:38 +05:30
|
|
|
{
|
2026-07-08 12:18:27 +05:30
|
|
|
var english = new FakeLanguageNerRunner("Priya", NerModelOrigin.English);
|
|
|
|
|
var tamil = new FakeLanguageNerRunner("மற்றும்", NerModelOrigin.Tamil);
|
2026-07-07 17:12:38 +05:30
|
|
|
var router = new RoutingOnnxNerModelRunner(english, tamil, enableTamilNer: true);
|
|
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
var result = router.PredictEntities("Rajesh மற்றும் Priya");
|
2026-07-07 17:12:38 +05:30
|
|
|
|
|
|
|
|
english.CallCount.Should().Be(1);
|
|
|
|
|
tamil.CallCount.Should().Be(1);
|
2026-07-08 12:18:27 +05:30
|
|
|
result.InvokedModels.Should().Equal(NerModelOrigin.English, NerModelOrigin.Tamil);
|
|
|
|
|
result.Entities.Should().Contain(entity => entity.ModelOrigin == NerModelOrigin.English);
|
|
|
|
|
result.Entities.Should().Contain(entity => entity.ModelOrigin == NerModelOrigin.Tamil);
|
2026-07-07 17:12:38 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void PredictEntities_NoLetters_InvokesNeither()
|
|
|
|
|
{
|
2026-07-08 12:18:27 +05:30
|
|
|
var english = new FakeLanguageNerRunner("ignored", NerModelOrigin.English);
|
|
|
|
|
var tamil = new FakeLanguageNerRunner("ignored", NerModelOrigin.Tamil);
|
2026-07-07 17:12:38 +05:30
|
|
|
var router = new RoutingOnnxNerModelRunner(english, tamil, enableTamilNer: true);
|
|
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
var result = router.PredictEntities("9876543210");
|
|
|
|
|
|
|
|
|
|
result.Entities.Should().BeEmpty();
|
|
|
|
|
result.InvokedModels.Should().BeEmpty();
|
2026-07-07 17:12:38 +05:30
|
|
|
english.CallCount.Should().Be(0);
|
|
|
|
|
tamil.CallCount.Should().Be(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void PredictEntities_TamilDisabled_SkipsTamilRunnerForMixedText()
|
|
|
|
|
{
|
2026-07-08 12:18:27 +05:30
|
|
|
var english = new FakeLanguageNerRunner("EnglishName", NerModelOrigin.English);
|
|
|
|
|
var tamil = new FakeLanguageNerRunner("தமிழ்", NerModelOrigin.Tamil);
|
2026-07-07 17:12:38 +05:30
|
|
|
var router = new RoutingOnnxNerModelRunner(english, tamil, enableTamilNer: false);
|
|
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
var result = router.PredictEntities("Rajesh மற்றும் Priya");
|
2026-07-07 17:12:38 +05:30
|
|
|
|
|
|
|
|
english.CallCount.Should().Be(1);
|
|
|
|
|
tamil.CallCount.Should().Be(0);
|
2026-07-08 12:18:27 +05:30
|
|
|
result.InvokedModels.Should().Equal(NerModelOrigin.English);
|
2026-07-07 17:12:38 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void MergePersonSpans_PrefersLongerOverlappingSpan()
|
|
|
|
|
{
|
|
|
|
|
var entities = new[]
|
|
|
|
|
{
|
2026-07-08 12:18:27 +05:30
|
|
|
CreatePerson("Raj", 0, 3, NerModelOrigin.English),
|
|
|
|
|
CreatePerson("Rajesh", 0, 6, NerModelOrigin.Tamil)
|
2026-07-07 17:12:38 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var merged = RoutingOnnxNerModelRunner.MergePersonSpans(entities);
|
|
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
merged.Should().ContainSingle(entity =>
|
|
|
|
|
entity.Value == "Rajesh" && entity.ModelOrigin == NerModelOrigin.Tamil);
|
2026-07-07 17:12:38 +05:30
|
|
|
}
|
|
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
private static PiiEntity CreatePerson(string value, int start, int length, NerModelOrigin origin) =>
|
|
|
|
|
new(PiiEntityType.Person, value, start, length, PiiDetectionSource.Ner, ModelOrigin: origin);
|
2026-07-07 17:12:38 +05:30
|
|
|
|
|
|
|
|
private sealed class FakeLanguageNerRunner : IOnnxNerModelRunner
|
|
|
|
|
{
|
|
|
|
|
private readonly string _personValue;
|
2026-07-08 12:18:27 +05:30
|
|
|
private readonly NerModelOrigin _origin;
|
2026-07-07 17:12:38 +05:30
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
public FakeLanguageNerRunner(string personValue, NerModelOrigin origin)
|
|
|
|
|
{
|
|
|
|
|
_personValue = personValue;
|
|
|
|
|
_origin = origin;
|
|
|
|
|
}
|
2026-07-07 17:12:38 +05:30
|
|
|
|
|
|
|
|
public int CallCount { get; private set; }
|
|
|
|
|
|
|
|
|
|
public bool IsModelAvailable => true;
|
|
|
|
|
|
2026-07-08 12:18:27 +05:30
|
|
|
public NerPredictionResult PredictEntities(string text)
|
2026-07-07 17:12:38 +05:30
|
|
|
{
|
|
|
|
|
CallCount++;
|
2026-07-08 12:18:27 +05:30
|
|
|
var start = text.IndexOf(_personValue, StringComparison.Ordinal);
|
|
|
|
|
if (start < 0)
|
|
|
|
|
{
|
|
|
|
|
start = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new NerPredictionResult(
|
2026-07-07 17:12:38 +05:30
|
|
|
[
|
|
|
|
|
new PiiEntity(
|
|
|
|
|
PiiEntityType.Person,
|
|
|
|
|
_personValue,
|
2026-07-08 12:18:27 +05:30
|
|
|
start,
|
2026-07-07 17:12:38 +05:30
|
|
|
_personValue.Length,
|
2026-07-08 12:18:27 +05:30
|
|
|
PiiDetectionSource.Ner,
|
|
|
|
|
ModelOrigin: _origin)
|
|
|
|
|
],
|
|
|
|
|
[_origin]);
|
2026-07-07 17:12:38 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|