using FluentAssertions; using PiiRedaction.Core.Abstractions; using PiiRedaction.Core.Detection; using PiiRedaction.Core.Models; namespace PiiRedaction.Core.Tests.Detection; [TestFixture] public sealed class CompositePiiDetectorTests { [Test] public void Detect_AdjacentSpans_KeepsBothEntities() { var composite = new CompositePiiDetector([new FixedDetector( new PiiEntity(PiiEntityType.Email, "a@b.co", 0, 6, PiiDetectionSource.Regex), new PiiEntity(PiiEntityType.Phone, "9876543210", 6, 10, PiiDetectionSource.Regex))]); var entities = composite.Detect("abcdef9876543210padding"); entities.Should().HaveCount(2); } [Test] public void Detect_NestedSpan_KeepsLongerSpan() { var composite = new CompositePiiDetector([new FixedDetector( new PiiEntity(PiiEntityType.Aadhaar, "123456789012", 0, 12, PiiDetectionSource.Regex), new PiiEntity(PiiEntityType.Phone, "4567890123", 2, 10, PiiDetectionSource.Regex))]); var entities = composite.Detect("123456789012"); entities.Should().ContainSingle(entity => entity.Type == PiiEntityType.Aadhaar); } [Test] public void Detect_OverlappingSameStart_LongerSpanWins() { var composite = new CompositePiiDetector([new FixedDetector( new PiiEntity(PiiEntityType.LoanNumber, "LN-456789", 0, 9, PiiDetectionSource.Domain), new PiiEntity(PiiEntityType.Phone, "456789", 3, 6, PiiDetectionSource.Regex))]); var entities = composite.Detect("LN-456789"); entities.Should().ContainSingle(entity => entity.Type == PiiEntityType.LoanNumber); } [Test] public void Detect_OverlappingDifferentPriority_DomainBeatsRegex() { var composite = new CompositePiiDetector([new FixedDetector( new PiiEntity(PiiEntityType.LoanNumber, "LN-456789", 0, 9, PiiDetectionSource.Domain), new PiiEntity(PiiEntityType.Phone, "456789", 0, 6, PiiDetectionSource.Regex))]); var entities = composite.Detect("LN-456789"); entities.Should().ContainSingle(entity => entity.Type == PiiEntityType.LoanNumber && entity.Source == PiiDetectionSource.Domain); } [Test] public void Detect_OverlappingPriority_RegexBeatsNer() { var composite = new CompositePiiDetector([new FixedDetector( new PiiEntity(PiiEntityType.Email, "a@b.co", 0, 6, PiiDetectionSource.Regex), new PiiEntity(PiiEntityType.Person, "a@b", 0, 3, PiiDetectionSource.Ner))]); var entities = composite.Detect("a@b.co"); entities.Should().ContainSingle(entity => entity.Source == PiiDetectionSource.Regex); } [Test] public void Detect_OverlappingPriority_DomainBeatsNer() { var composite = new CompositePiiDetector([new FixedDetector( new PiiEntity(PiiEntityType.LoanNumber, "LN-456789", 0, 9, PiiDetectionSource.Domain), new PiiEntity(PiiEntityType.Person, "LN-456", 0, 6, PiiDetectionSource.Ner))]); var entities = composite.Detect("LN-456789"); entities.Should().ContainSingle(entity => entity.Source == PiiDetectionSource.Domain); } [Test] public void Detect_DuplicateOverlappingSpan_KeepsFirstAccepted() { var composite = new CompositePiiDetector([new FixedDetector( new PiiEntity(PiiEntityType.Phone, "9876543210", 0, 10, PiiDetectionSource.Regex), new PiiEntity(PiiEntityType.Phone, "9876543210", 0, 10, PiiDetectionSource.Regex))]); var entities = composite.Detect("9876543210"); entities.Should().HaveCount(1); } [Test] public void Detect_AadhaarWithEmbeddedPhone_PrefersAadhaarSpan() { var detector = new CompositePiiDetector( [ new DomainRulePiiDetector(), new RegexPiiDetector() ]); var entities = detector.Detect("Aadhaar 987654321012 phone 9876543210."); entities.Should().Contain(entity => entity.Type == PiiEntityType.Aadhaar && entity.Value == "987654321012"); entities.Should().Contain(entity => entity.Type == PiiEntityType.Phone && entity.Value == "9876543210"); entities.Count(entity => entity.Type == PiiEntityType.Phone).Should().Be(1); } [TestCase(null)] [TestCase("")] [TestCase(" ")] public void Detect_InvalidInput_ThrowsArgumentException(string? text) { var composite = new CompositePiiDetector([new RegexPiiDetector()]); var action = () => composite.Detect(text!); action.Should().Throw(); } private sealed class FixedDetector(params PiiEntity[] entities) : IPiiDetector { public IReadOnlyList Detect(string text) => entities; } }