Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.NotificationService.Tests/Ews/EwsResponseParserTests.cs
T
Joseph Doherty 513fa1aa16 docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only:
- add summaries to ~10k test methods, fixtures, fakes and helpers
  (+ <param> on theories, <returns> on async Task methods,
  <inheritdoc/> on interface implementations the checker can resolve)
- strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N,
  #NN, Component-0NN) from comments, keeping the explanatory text;
  reword false positives in comments only
- real docs on fakes implementing BCL/library interfaces
  (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime)
- reword the last 5 src/ false positives (CWE-1236, failures-1,
  year-0001, M365)

Only non-comment edits: four one-line nested classes split across lines
so members can carry docs. Build 0 errors / 0 warnings.
2026-10-07 15:05:49 -04:00

176 lines
7.8 KiB
C#

using ZB.MOM.WW.ScadaBridge.NotificationService.Ews;
namespace ZB.MOM.WW.ScadaBridge.NotificationService.Tests.Ews;
/// <summary>
/// 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.
/// </summary>
public class EwsResponseParserTests
{
private const string SuccessBody = """
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<m:CreateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<m:ResponseMessages>
<m:CreateItemResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:Items><t:Message><t:ItemId Id="AAA=" ChangeKey="CQ=="/></t:Message></m:Items>
</m:CreateItemResponseMessage>
</m:ResponseMessages>
</m:CreateItemResponse>
</s:Body>
</s:Envelope>
""";
private const string ErrorBody = """
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<m:CreateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<m:ResponseMessages>
<m:CreateItemResponseMessage ResponseClass="Error">
<m:MessageText>server busy</m:MessageText>
<m:ResponseCode>ErrorServerBusy</m:ResponseCode>
<m:DescriptiveLinkKey>0</m:DescriptiveLinkKey>
</m:CreateItemResponseMessage>
</m:ResponseMessages>
</m:CreateItemResponse>
</s:Body>
</s:Envelope>
""";
private const string FaultWithResponseCodeBody = """
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<s:Fault>
<faultcode xmlns:a="http://schemas.microsoft.com/exchange/services/2006/types">a:ErrorSchemaValidation</faultcode>
<faultstring xml:lang="en-US">The request failed schema validation.</faultstring>
<detail>
<e:ResponseCode xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">ErrorSchemaValidation</e:ResponseCode>
<e:Message xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">The request failed schema validation.</e:Message>
</detail>
</s:Fault>
</s:Body>
</s:Envelope>
""";
private const string FaultWithoutResponseCodeBody = """
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<s:Fault>
<faultcode>s:Server</faultcode>
<faultstring xml:lang="en-US">An internal server error occurred.</faultstring>
<detail><e:Message xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">boom</e:Message></detail>
</s:Fault>
</s:Body>
</s:Envelope>
""";
/// <summary>Verifies that a success response parses to a success result with no error.</summary>
[Fact]
public void Parse_SuccessResponse_ReturnsSuccessWithNoError()
{
var result = EwsResponseParser.Parse(SuccessBody);
Assert.Equal(EwsResponseKind.Success, result.Kind);
Assert.Equal("NoError", result.ResponseCode);
}
/// <summary>Verifies that an error response parses to an error result with its code and message.</summary>
[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);
}
/// <summary>Verifies that a SOAP fault with a detail response code parses to a fault carrying that code.</summary>
[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);
}
/// <summary>Verifies that a SOAP fault without a response code parses to a fault with a null code.</summary>
[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);
}
/// <summary>Verifies that a payload containing a DTD entity declaration is rejected as unparseable.</summary>
[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 = """
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Envelope [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
<!ENTITY expanded "server busy">
]>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<m:CreateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:ResponseMessages>
<m:CreateItemResponseMessage ResponseClass="Error">
<m:MessageText>&expanded;</m:MessageText>
<m:ResponseCode>ErrorServerBusy</m:ResponseCode>
</m:CreateItemResponseMessage>
</m:ResponseMessages>
</m:CreateItemResponse>
</s:Body>
</s:Envelope>
""";
var result = EwsResponseParser.Parse(xxe);
Assert.Equal(EwsResponseKind.Unparseable, result.Kind);
Assert.Null(result.ResponseCode);
Assert.Null(result.MessageText);
}
/// <summary>Verifies that a garbage or unrelated payload is reported as unparseable.</summary>
/// <param name="body">The response body under test.</param>
[Theory]
[InlineData("not xml")]
[InlineData("")]
[InlineData(" ")]
[InlineData("<foo/>")]
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);
}
}