using System.Net; using System.Text; using ZB.MOM.WW.ScadaBridge.DelmiaNotifier; namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier.Tests; public class HttpRecipeSenderTests { /// Verifies the case: DC7, explicit Standby Refusal, tries Second Url, but Generic503 Does Not. /// A task that represents the asynchronous operation. [Fact] public async Task DC7_ExplicitStandbyRefusal_TriesSecondUrl_ButGeneric503DoesNot() { foreach (var standby in new[] { true, false }) { var calls = 0; var handler = new StubHandler(_ => { calls++; if (calls > 1) return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"Result\":true}") }; var result = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable) { Content = new StringContent("{\"code\":\"STANDBY_NODE\"}") }; if (standby) result.Headers.Add("X-ScadaBridge-Standby", "1"); return result; }); using var http = new HttpClient(handler); var outcome = await Notifier.RunAsync(["http://a", "http://b"], Payload, new HttpRecipeSender(http, "key"), CancellationToken.None); Assert.Equal(standby, outcome.Ok); Assert.Equal(standby ? 2 : 1, calls); } } /// Verifies the case: DC7, applied Then Response Lost, does Not Replay. /// A task that represents the asynchronous operation. [Fact] public async Task DC7_AppliedThenResponseLost_DoesNotReplay() { var effects = 0; using var http = new HttpClient(new StubHandler(_ => { effects++; throw new HttpRequestException("lost response"); })); var outcome = await Notifier.RunAsync(["http://a", "http://b"], Payload, new HttpRecipeSender(http, "key"), CancellationToken.None); Assert.Equal(1, effects); Assert.Contains("OUTCOME_UNKNOWN", outcome.Reason); } private sealed class StubHandler(Func responder) : HttpMessageHandler { /// Gets the last HTTP request seen by this handler. public HttpRequestMessage? LastRequest { get; private set; } /// protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { LastRequest = request; return Task.FromResult(responder(request)); } } private static readonly RecipeDownload Payload = new() { MachineCode = "Z", DownloadPath = "x", WorkOrderNumber = "W", PartNumber = "P" }; /// Verifies the case: connect Failure, maps, to, connect Failed. /// A task that represents the asynchronous operation. [Fact] public async Task ConnectFailure_maps_to_ConnectFailed() { var handler = new StubHandler(_ => throw new HttpRequestException("refused", new System.Net.Sockets.SocketException((int)System.Net.Sockets.SocketError.ConnectionRefused))); using var http = new HttpClient(handler); var sender = new HttpRecipeSender(http, "sbk_x"); var outcome = await sender.SendAsync("http://a", Payload, CancellationToken.None); Assert.Equal(AttemptKind.ConnectFailed, outcome.Kind); Assert.Equal(0, outcome.StatusCode); } /// Verifies the case: success, 200, parses, body, and, sends, header, and, path. /// A task that represents the asynchronous operation. [Fact] public async Task Success_200_parses_body_and_sends_header_and_path() { var handler = new StubHandler(_ => new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"Result\":true,\"ResultText\":\"\"}", Encoding.UTF8, "application/json"), }); using var http = new HttpClient(handler); var sender = new HttpRecipeSender(http, "sbk_x"); var outcome = await sender.SendAsync("http://a/", Payload, CancellationToken.None); Assert.Equal(AttemptKind.Connected, outcome.Kind); Assert.Equal(200, outcome.StatusCode); Assert.NotNull(outcome.Body); Assert.True(outcome.Body!.Result); Assert.Equal("sbk_x", handler.LastRequest!.Headers.GetValues("X-API-Key").Single()); Assert.Equal("http://a/api/DelmiaRecipeDownload", handler.LastRequest.RequestUri!.ToString()); } /// Verifies the case: non2xx, is, connected, with, status. /// A task that represents the asynchronous operation. [Fact] public async Task Non2xx_is_Connected_with_status() { var handler = new StubHandler(_ => new HttpResponseMessage(HttpStatusCode.InternalServerError)); using var http = new HttpClient(handler); var sender = new HttpRecipeSender(http, "sbk_x"); var outcome = await sender.SendAsync("http://a", Payload, CancellationToken.None); Assert.Equal(AttemptKind.Connected, outcome.Kind); Assert.Equal(500, outcome.StatusCode); } }