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)
});
}
}
}