using System.Net; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using ScadaLink.Commons.Entities.ExternalSystems; using ScadaLink.Commons.Interfaces.Repositories; namespace ScadaLink.ExternalSystemGateway.Tests; /// /// WP-6/7: Tests for ExternalSystemClient — HTTP client, call modes, error handling. /// public class ExternalSystemClientTests { private readonly IExternalSystemRepository _repository = Substitute.For(); private readonly IHttpClientFactory _httpClientFactory = Substitute.For(); [Fact] public async Task Call_SystemNotFound_ReturnsError() { _repository.GetAllExternalSystemsAsync().Returns(new List()); var client = new ExternalSystemClient( _httpClientFactory, _repository, NullLogger.Instance); var result = await client.CallAsync("nonexistent", "method"); Assert.False(result.Success); Assert.Contains("not found", result.ErrorMessage); } [Fact] public async Task Call_MethodNotFound_ReturnsError() { var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "none") { Id = 1 }; _repository.GetAllExternalSystemsAsync().Returns(new List { system }); _repository.GetMethodsByExternalSystemIdAsync(1).Returns(new List()); var client = new ExternalSystemClient( _httpClientFactory, _repository, NullLogger.Instance); var result = await client.CallAsync("TestAPI", "missingMethod"); Assert.False(result.Success); Assert.Contains("not found", result.ErrorMessage); } [Fact] public async Task Call_SuccessfulHttp_ReturnsResponse() { var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "none") { Id = 1 }; var method = new ExternalSystemMethod("getData", "GET", "/data") { Id = 1, ExternalSystemDefinitionId = 1 }; _repository.GetAllExternalSystemsAsync().Returns(new List { system }); _repository.GetMethodsByExternalSystemIdAsync(1).Returns(new List { method }); var handler = new MockHttpMessageHandler(HttpStatusCode.OK, "{\"result\": 42}"); var httpClient = new HttpClient(handler); _httpClientFactory.CreateClient(Arg.Any()).Returns(httpClient); var client = new ExternalSystemClient( _httpClientFactory, _repository, NullLogger.Instance); var result = await client.CallAsync("TestAPI", "getData"); Assert.True(result.Success); Assert.Contains("42", result.ResponseJson!); } [Fact] public async Task Call_Transient500_ReturnsTransientError() { var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "none") { Id = 1 }; var method = new ExternalSystemMethod("failMethod", "POST", "/fail") { Id = 1, ExternalSystemDefinitionId = 1 }; _repository.GetAllExternalSystemsAsync().Returns(new List { system }); _repository.GetMethodsByExternalSystemIdAsync(1).Returns(new List { method }); var handler = new MockHttpMessageHandler(HttpStatusCode.InternalServerError, "server error"); var httpClient = new HttpClient(handler); _httpClientFactory.CreateClient(Arg.Any()).Returns(httpClient); var client = new ExternalSystemClient( _httpClientFactory, _repository, NullLogger.Instance); var result = await client.CallAsync("TestAPI", "failMethod"); Assert.False(result.Success); Assert.Contains("Transient error", result.ErrorMessage); } [Fact] public async Task Call_Permanent400_ReturnsPermanentError() { var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "none") { Id = 1 }; var method = new ExternalSystemMethod("badMethod", "POST", "/bad") { Id = 1, ExternalSystemDefinitionId = 1 }; _repository.GetAllExternalSystemsAsync().Returns(new List { system }); _repository.GetMethodsByExternalSystemIdAsync(1).Returns(new List { method }); var handler = new MockHttpMessageHandler(HttpStatusCode.BadRequest, "bad request"); var httpClient = new HttpClient(handler); _httpClientFactory.CreateClient(Arg.Any()).Returns(httpClient); var client = new ExternalSystemClient( _httpClientFactory, _repository, NullLogger.Instance); var result = await client.CallAsync("TestAPI", "badMethod"); Assert.False(result.Success); Assert.Contains("Permanent error", result.ErrorMessage); } [Fact] public async Task CachedCall_SystemNotFound_ReturnsError() { _repository.GetAllExternalSystemsAsync().Returns(new List()); var client = new ExternalSystemClient( _httpClientFactory, _repository, NullLogger.Instance); var result = await client.CachedCallAsync("nonexistent", "method"); Assert.False(result.Success); Assert.Contains("not found", result.ErrorMessage); } [Fact] public async Task CachedCall_Success_ReturnsDirectly() { var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "none") { Id = 1 }; var method = new ExternalSystemMethod("getData", "GET", "/data") { Id = 1, ExternalSystemDefinitionId = 1 }; _repository.GetAllExternalSystemsAsync().Returns(new List { system }); _repository.GetMethodsByExternalSystemIdAsync(1).Returns(new List { method }); var handler = new MockHttpMessageHandler(HttpStatusCode.OK, "{\"ok\": true}"); var httpClient = new HttpClient(handler); _httpClientFactory.CreateClient(Arg.Any()).Returns(httpClient); var client = new ExternalSystemClient( _httpClientFactory, _repository, NullLogger.Instance); var result = await client.CachedCallAsync("TestAPI", "getData"); Assert.True(result.Success); Assert.False(result.WasBuffered); } /// /// Test helper: mock HTTP message handler. /// private class MockHttpMessageHandler : HttpMessageHandler { private readonly HttpStatusCode _statusCode; private readonly string _body; public MockHttpMessageHandler(HttpStatusCode statusCode, string body) { _statusCode = statusCode; _body = body; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return Task.FromResult(new HttpResponseMessage(_statusCode) { Content = new StringContent(_body) }); } } }