using ZB.MOM.WW.ScadaBridge.NotificationService.Ews; namespace ZB.MOM.WW.ScadaBridge.NotificationService.Tests.Ews; /// /// Tests for the pure EWS response parser. The parser reports shape only /// (success / error / SOAP fault / unparseable) plus the response code — the /// transient-vs-permanent judgement belongs to the sender's classifier. /// public class EwsResponseParserTests { private const string SuccessBody = """ NoError """; private const string ErrorBody = """ server busy ErrorServerBusy 0 """; private const string FaultWithResponseCodeBody = """ a:ErrorSchemaValidation The request failed schema validation. ErrorSchemaValidation The request failed schema validation. """; private const string FaultWithoutResponseCodeBody = """ s:Server An internal server error occurred. boom """; [Fact] public void Parse_SuccessResponse_ReturnsSuccessWithNoError() { var result = EwsResponseParser.Parse(SuccessBody); Assert.Equal(EwsResponseKind.Success, result.Kind); Assert.Equal("NoError", result.ResponseCode); } [Fact] public void Parse_ErrorResponse_ReturnsErrorWithCodeAndMessage() { var result = EwsResponseParser.Parse(ErrorBody); Assert.Equal(EwsResponseKind.Error, result.Kind); Assert.Equal("ErrorServerBusy", result.ResponseCode); Assert.Equal("server busy", result.MessageText); } [Fact] public void Parse_SoapFaultWithDetailResponseCode_ReturnsFaultWithCode() { var result = EwsResponseParser.Parse(FaultWithResponseCodeBody); Assert.Equal(EwsResponseKind.Fault, result.Kind); Assert.Equal("ErrorSchemaValidation", result.ResponseCode); Assert.Equal("The request failed schema validation.", result.MessageText); } [Fact] public void Parse_SoapFaultWithoutResponseCode_ReturnsFaultWithNullCode() { var result = EwsResponseParser.Parse(FaultWithoutResponseCodeBody); Assert.Equal(EwsResponseKind.Fault, result.Kind); Assert.Null(result.ResponseCode); Assert.Equal("An internal server error occurred.", result.MessageText); } [Fact] public void Parse_PayloadWithDtdEntityDeclaration_IsRejectedAsUnparseable() { // Pins DTD-prohibited parsing (XXE guard). The response body is external input, so the // parser must never process a DOCTYPE. LINQ-to-XML does NOT prohibit DTDs on its own, so // the parser reads through an XmlReader pinned to DtdProcessing.Prohibit with a null // resolver; a refactor that loosens either setting fails here. // // The payload is deliberately a well-formed EWS response so the assertion has teeth: with // DTDs prohibited the DOCTYPE itself throws and the body is Unparseable, whereas any // DtdProcessing.Parse configuration expands the internal entity and yields Kind=Error. // The external file:/// entity is declared alongside it as the XXE payload that must // likewise never be reached. const string xxe = """ ]> &expanded; ErrorServerBusy """; var result = EwsResponseParser.Parse(xxe); Assert.Equal(EwsResponseKind.Unparseable, result.Kind); Assert.Null(result.ResponseCode); Assert.Null(result.MessageText); } [Theory] [InlineData("not xml")] [InlineData("")] [InlineData(" ")] [InlineData("")] public void Parse_GarbageOrUnrelatedPayload_ReturnsUnparseable(string body) { var result = EwsResponseParser.Parse(body); Assert.Equal(EwsResponseKind.Unparseable, result.Kind); Assert.Null(result.ResponseCode); Assert.Null(result.MessageText); } }