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. XDocument.Parse prohibits DTDs by default; a
// refactor that supplies XmlReaderSettings with a looser DtdProcessing 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);
}
}