diff --git a/BleBoxNetSdk.Tests/ResponseRequestTestCases.cs b/BleBoxNetSdk.Tests/ResponseRequestTestCases.cs new file mode 100644 index 0000000..df5a03d --- /dev/null +++ b/BleBoxNetSdk.Tests/ResponseRequestTestCases.cs @@ -0,0 +1,47 @@ +using BleBoxNetSdk.AirSensor.Endpoints; +using BleBoxNetSdk.AirSensor.Enums; +using BleBoxNetSdk.Common.Endpoints; +using BleBoxNetSdk.Common.Enums; + +namespace BleBoxNetSdk.Tests; + +public class ResponseRequestTestCases +{ + internal static IEnumerable GetResponses() + { + //Common + yield return new TestCaseData(new Info.ResponseResult(), Samples.InfoResponse); + yield return new TestCaseData(new DeviceUptime.ResponseResult(), Samples.UptimeResponse); + yield return new TestCaseData(new PerformUpdate.ResponseResult(), Samples.PerformUpdateResponse); + yield return new TestCaseData(new NetworkInformation.ResponseResult(), Samples.NetworkInformationResponse); + yield return new TestCaseData(new SetNetwork.ResponseResult(), Samples.SetAPResponse); + yield return new TestCaseData(new WiFiScan.ResponseResult(), Samples.PerformWiFiScanResponse); + yield return new TestCaseData(new ConnectWiFi.ResponseResult(), Samples.PerformWiFiConnectResponse); + yield return new TestCaseData(new DisconnectWiFi.ResponseResult(), Samples.PerformWiFiDisconnectResponse); + + //AirSensor + yield return new TestCaseData(new DeviceState.ResponseResult(), Samples.AirSensorDeviceStateResponse); + yield return new TestCaseData(new ExtendedDeviceState.ResponseResult(), Samples.AirSensorExtendedResponse); + yield return new TestCaseData(new SensorRuntime.ResponseResult(), Samples.AirSensorRuntimeResponse); + yield return new TestCaseData(new ForceMeasurement.ResponseResult(), Samples.AirSensorMeasurementResponse); + yield return new TestCaseData(new SettingsState.ResponseResult(), Samples.AirSensorSettingsResponse); + yield return new TestCaseData(new SettingsSet.ResponseResult(), Samples.AirSensorSetSettingsResponse); + } + + internal static IEnumerable GetRequests() + { + //Common + yield return new TestCaseData(new SetNetwork.Request(true, "shutterBox-g650e32d2217", "my_secret_password"), Samples.SetAPRequest); + yield return new TestCaseData(new ConnectWiFi.Request("WiFi_Name", "my_secret_password"), Samples.PerformWiFiConnectRequest); + + //AirSensor + yield return new TestCaseData(new SettingsSet.Request( + "My BleBox device name", + Toggle.Enabled, + Toggle.Enabled, + Toggle.Enabled, + Geolocation.Accurate, + Mounting.Outside, + Toggle.Enabled), Samples.AirSensorSetSettingsRequest); + } +} diff --git a/BleBoxNetSdk.Tests/Services/ApiHttpClientTests.cs b/BleBoxNetSdk.Tests/Services/ApiHttpClientTests.cs new file mode 100644 index 0000000..1bf2d6b --- /dev/null +++ b/BleBoxNetSdk.Tests/Services/ApiHttpClientTests.cs @@ -0,0 +1,52 @@ +using BleBoxNetSdk.Common.Endpoints; +using BleBoxNetSdk.Services; +using BleBoxNetSdk.Wrappers; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; + +namespace BleBoxNetSdk.Tests.Services; + +internal class ApiHttpClientTests : ResponseRequestTestCases +{ + [TestCaseSource(nameof(GetResponses))] + public async Task should_send_request(TResponse _, string json) + { + var uri = new Uri("http://127.0.0.1/"); + var request = new PerformUpdate.Request(); + var httpClientMock = new Mock(); + var apiHttpClient = PrepareApiHttpClient(httpClientMock.Object); + var responseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent(json) }; + httpClientMock.Setup(h => h.SendAsync(It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(responseMessage)); + + var result = await apiHttpClient.Send(uri, request, CancellationToken.None); + + result.Should().NotBeNull(); + } + + [Test] + public void should_throw_after_failed_trysends() + { + var uri = new Uri("http://127.0.0.1/"); + var request = new PerformUpdate.Request(); + var httpClientMock = new Mock(); + var apiHttpClient = PrepareApiHttpClient(httpClientMock.Object); + var responseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); + var totalCalls = 3; + httpClientMock.Setup(h => h.SendAsync(It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(responseMessage)); + + Assert.ThrowsAsync( async () => await apiHttpClient.Send(uri, request, CancellationToken.None)); + + httpClientMock.Verify(m => m.SendAsync(It.IsAny(), CancellationToken.None), Times.Exactly(totalCalls)); + } + + private ApiHttpClient PrepareApiHttpClient(IHttpClient? httpClient = null) + { + var apiHttpClient = new ApiHttpClient( + httpClient ?? Mock.Of(), + Mock.Of>(), + new Serializer()); + + return apiHttpClient; + } +} diff --git a/BleBoxNetSdk.Tests/Services/SerializerTests.cs b/BleBoxNetSdk.Tests/Services/SerializerTests.cs index d53154e..4d283f1 100644 --- a/BleBoxNetSdk.Tests/Services/SerializerTests.cs +++ b/BleBoxNetSdk.Tests/Services/SerializerTests.cs @@ -1,13 +1,9 @@ -using BleBoxNetSdk.AirSensor.Endpoints; -using BleBoxNetSdk.AirSensor.Enums; -using BleBoxNetSdk.Common.Endpoints; -using BleBoxNetSdk.Common.Enums; -using BleBoxNetSdk.Services; +using BleBoxNetSdk.Services; using FluentAssertions; namespace BleBoxNetSdk.Tests.Services; -internal class SerializerTests +internal class SerializerTests : ResponseRequestTestCases { [TestCaseSource(nameof(GetResponses))] public void should_deserialize_and_serialize_info(TResponse _, string json) @@ -30,42 +26,4 @@ public void should_serialize_requests(object request, string json) serializedString.Should().Be(json); } - - private static IEnumerable GetResponses() - { - //Common - yield return new TestCaseData(new Info.ResponseResult(), Samples.InfoResponse); - yield return new TestCaseData(new DeviceUptime.ResponseResult(), Samples.UptimeResponse); - yield return new TestCaseData(new PerformUpdate.ResponseResult(), Samples.PerformUpdateResponse); - yield return new TestCaseData(new NetworkInformation.ResponseResult(), Samples.NetworkInformationResponse); - yield return new TestCaseData(new SetNetwork.ResponseResult(), Samples.SetAPResponse); - yield return new TestCaseData(new WiFiScan.ResponseResult(), Samples.PerformWiFiScanResponse); - yield return new TestCaseData(new ConnectWiFi.ResponseResult(), Samples.PerformWiFiConnectResponse); - yield return new TestCaseData(new DisconnectWiFi.ResponseResult(), Samples.PerformWiFiDisconnectResponse); - - //AirSensor - yield return new TestCaseData(new DeviceState.ResponseResult(), Samples.AirSensorDeviceStateResponse); - yield return new TestCaseData(new ExtendedDeviceState.ResponseResult(), Samples.AirSensorExtendedResponse); - yield return new TestCaseData(new SensorRuntime.ResponseResult(), Samples.AirSensorRuntimeResponse); - yield return new TestCaseData(new ForceMeasurement.ResponseResult(), Samples.AirSensorMeasurementResponse); - yield return new TestCaseData(new SettingsState.ResponseResult(), Samples.AirSensorSettingsResponse); - yield return new TestCaseData(new SettingsSet.ResponseResult(), Samples.AirSensorSetSettingsResponse); - } - - private static IEnumerable GetRequests() - { - //Common - yield return new TestCaseData(new SetNetwork.Request(true, "shutterBox-g650e32d2217", "my_secret_password"), Samples.SetAPRequest); - yield return new TestCaseData(new ConnectWiFi.Request("WiFi_Name", "my_secret_password"), Samples.PerformWiFiConnectRequest); - - //AirSensor - yield return new TestCaseData(new SettingsSet.Request( - "My BleBox device name", - Toggle.Enabled, - Toggle.Enabled, - Toggle.Enabled, - Geolocation.Accurate, - Mounting.Outside, - Toggle.Enabled), Samples.AirSensorSetSettingsRequest); - } } diff --git a/BleBoxNetSdk/ServiceExtensions.cs b/BleBoxNetSdk/ServiceExtensions.cs index 19f669f..8cc8f4b 100644 --- a/BleBoxNetSdk/ServiceExtensions.cs +++ b/BleBoxNetSdk/ServiceExtensions.cs @@ -1,6 +1,7 @@ using BleBoxNetSdk.AirSensor; using BleBoxNetSdk.Common; using BleBoxNetSdk.Services; +using BleBoxNetSdk.Wrappers; using Microsoft.Extensions.DependencyInjection; namespace BleBoxNetSdk; @@ -11,6 +12,7 @@ public static IServiceCollection RegisterSdkServices(this IServiceCollection ser { services .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/BleBoxNetSdk/Services/ApiHttpClient.cs b/BleBoxNetSdk/Services/ApiHttpClient.cs index e207a31..2ad10e8 100644 --- a/BleBoxNetSdk/Services/ApiHttpClient.cs +++ b/BleBoxNetSdk/Services/ApiHttpClient.cs @@ -1,4 +1,5 @@ using BleBoxNetSdk.Common; +using BleBoxNetSdk.Wrappers; using Microsoft.Extensions.Logging; using System.Net; using System.Text; @@ -11,25 +12,13 @@ public interface IApiHttpClient Task Send(Uri baseUri, IRequest requestObject, CancellationToken cancellationToken); } -public class ApiHttpClient : IApiHttpClient +public class ApiHttpClient(IHttpClient httpClient, ILogger logger, ISerializer serializer) : IApiHttpClient { private const int MaxResendCount = 3; private const string JsonContentType = "application/json"; - private readonly HttpClient _httpClient; - private readonly ILogger _logger; - private readonly ISerializer _serializer; private readonly Encoding _encoding = Encoding.UTF8; - public ApiHttpClient( - ILogger logger, - ISerializer serializer) - { - _httpClient = new HttpClient(); - _logger = logger; - _serializer = serializer; - } - public async Task Send( Uri baseUri, IRequest requestObject, @@ -38,9 +27,9 @@ public async Task Send( var response = await TrySend(baseUri, requestObject, cancellationToken); var serializedResult = await response.Content.ReadAsStringAsync(cancellationToken); - _logger.LogDebug("<- Received response: {response}", serializedResult); + logger.LogDebug("<- Received response: {response}", serializedResult); - var deserializedResult = _serializer.DeserializeJson(serializedResult) + var deserializedResult = serializer.DeserializeJson(serializedResult) ?? throw new JsonException($"Could not deserialize {serializedResult} to {typeof(TResult)}"); return deserializedResult; @@ -57,20 +46,20 @@ private async Task TrySend( { var request = PrepareRequest(baseUri, requestObject); - var response = await _httpClient.SendAsync(request, cancellationToken); + var response = await httpClient.SendAsync(request, cancellationToken); if (response.StatusCode == HttpStatusCode.Conflict) - _logger.LogWarning("Requested action is already in progress"); + logger.LogWarning("Requested action is already in progress"); response.EnsureSuccessStatusCode(); return response; } - catch (Exception ex) when (count < MaxResendCount) + catch (Exception ex) when (count <= MaxResendCount) { await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken); - _logger.LogWarning("Request to API failed with exception: {ex}. Resending request {count}", ex, count); + logger.LogWarning("Request to API failed with exception: {ex}. Resending request {count}", ex, count); } } @@ -82,13 +71,13 @@ private HttpRequestMessage PrepareRequest( IRequest requestObject) { var request = new HttpRequestMessage(requestObject.HttpMethod, BuildUri(baseUri, requestObject.Uri)); - _logger.LogDebug("-> Sending http request to: {uri}", request.RequestUri?.ToString()); + logger.LogDebug("-> Sending http request to: {uri}", request.RequestUri?.ToString()); if (requestObject.HaveContent) { - var serializedContent = _serializer.SerializeJson(requestObject); + var serializedContent = serializer.SerializeJson(requestObject); - _logger.LogDebug("-> With body: {body}", serializedContent); + logger.LogDebug("-> With body: {body}", serializedContent); request.Content = new StringContent(serializedContent, _encoding, JsonContentType); } diff --git a/BleBoxNetSdk/Wrappers/BleHttpClient.cs b/BleBoxNetSdk/Wrappers/BleHttpClient.cs new file mode 100644 index 0000000..77ece5e --- /dev/null +++ b/BleBoxNetSdk/Wrappers/BleHttpClient.cs @@ -0,0 +1,19 @@ +namespace BleBoxNetSdk.Wrappers; + +public interface IHttpClient +{ + Task SendAsync(HttpRequestMessage request, CancellationToken token = default); +} + +public class BleHttpClient : IHttpClient +{ + private readonly HttpClient _httpClient; + + public BleHttpClient() + { + _httpClient = new HttpClient(); + } + + public Task SendAsync(HttpRequestMessage request, CancellationToken token = default) + => _httpClient.SendAsync(request, token); +}