using OCSentinelCli; using Xunit; namespace OCSentinelCli.Tests; public sealed class ExchangeIisLogParserTests { [Fact] public void ParsesFailedOwaLoginWithActualIisPort() { string[] lines = [ "#Fields: date time s-ip cs-method cs-uri-stem cs-username c-ip s-port sc-status", "2026-08-02 04:15:00 10.0.0.10 POST /owa/auth.owa - 203.0.113.20 443 401" ]; AttackEvent attack = Assert.Single(ExchangeIisLogParser.ParseLines(lines, new DateTimeOffset(2026, 8, 2, 4, 0, 0, TimeSpan.Zero))); Assert.Equal("Exchange OWA login", attack.Target); Assert.Equal("OWA", attack.Service); Assert.Equal(443, attack.DestinationPort); Assert.Equal("/owa/auth.owa", attack.Endpoint); Assert.Equal("203.0.113.20", attack.SourceIp); } [Fact] public void ParsesMapiAndIgnoresSuccessfulRequests() { string[] lines = [ "#Fields: date time cs-uri-stem cs-username c-ip s-port sc-status", "2026-08-02 04:15:00 /mapi/emsmdb/ user@example.test 198.51.100.8 444 403", "2026-08-02 04:16:00 /ecp/ user@example.test 198.51.100.9 443 200" ]; AttackEvent attack = Assert.Single(ExchangeIisLogParser.ParseLines(lines, new DateTimeOffset(2026, 8, 2, 4, 0, 0, TimeSpan.Zero))); Assert.Equal("MAPI/HTTP", attack.Service); Assert.Equal(444, attack.DestinationPort); Assert.Equal("user@example.test", attack.Username); } [Fact] public void IgnoresNormalIisAuthenticationHandshake() { string[] lines = [ "#Fields: date time cs-uri-stem cs-username c-ip s-port cs(User-Agent) sc-status sc-substatus", "2026-08-02 04:15:00 /mapi/emsmdb/ - 198.51.100.8 443 Outlook 401 1", "2026-08-02 04:15:01 /mapi/emsmdb/ user@example.test 198.51.100.8 443 Outlook 200 0" ]; Assert.Empty(ExchangeIisLogParser.ParseLines(lines, new DateTimeOffset(2026, 8, 2, 4, 0, 0, TimeSpan.Zero))); } [Fact] public void IgnoresNonCredentialIis401Substatus() { string[] lines = [ "#Fields: date time cs-uri-stem cs-username c-ip s-port sc-status sc-substatus", "2026-08-02 04:15:00 /ews/Exchange.asmx - 198.51.100.8 443 401 0" ]; Assert.Empty(ExchangeIisLogParser.ParseLines(lines, new DateTimeOffset(2026, 8, 2, 4, 0, 0, TimeSpan.Zero))); } }