using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using StellaOps.Microservice;
using StellaOps.Router.Common.Models;
using Xunit;
namespace StellaOps.Microservice.Tests;
///
/// Tests for EndpointDiscoveryService - verifies integration of discovery + YAML loading + merging.
///
public class EndpointDiscoveryServiceTests
{
private readonly Mock _discoveryProviderMock;
private readonly Mock _yamlLoaderMock;
private readonly Mock _mergerMock;
private readonly ILogger _logger;
private readonly EndpointDiscoveryService _service;
public EndpointDiscoveryServiceTests()
{
_discoveryProviderMock = new Mock();
_yamlLoaderMock = new Mock();
_mergerMock = new Mock();
_logger = NullLogger.Instance;
_service = new EndpointDiscoveryService(
_discoveryProviderMock.Object,
_yamlLoaderMock.Object,
_mergerMock.Object,
_logger);
}
[Fact]
public void DiscoverEndpoints_CallsDiscoveryProvider()
{
var codeEndpoints = new List();
_discoveryProviderMock
.Setup(x => x.DiscoverEndpoints())
.Returns(codeEndpoints);
_mergerMock
.Setup(x => x.Merge(It.IsAny>(), It.IsAny()))
.Returns(codeEndpoints);
_service.DiscoverEndpoints();
_discoveryProviderMock.Verify(x => x.DiscoverEndpoints(), Times.Once);
}
[Fact]
public void DiscoverEndpoints_CallsYamlLoader()
{
var codeEndpoints = new List();
_discoveryProviderMock
.Setup(x => x.DiscoverEndpoints())
.Returns(codeEndpoints);
_mergerMock
.Setup(x => x.Merge(It.IsAny>(), It.IsAny()))
.Returns(codeEndpoints);
_service.DiscoverEndpoints();
_yamlLoaderMock.Verify(x => x.Load(), Times.Once);
}
[Fact]
public void DiscoverEndpoints_PassesCodeEndpointsAndYamlConfigToMerger()
{
var codeEndpoints = new List
{
CreateEndpoint("GET", "/api/test")
};
var yamlConfig = new MicroserviceYamlConfig
{
Endpoints =
[
new EndpointOverrideConfig { Method = "GET", Path = "/api/test" }
]
};
_discoveryProviderMock
.Setup(x => x.DiscoverEndpoints())
.Returns(codeEndpoints);
_yamlLoaderMock
.Setup(x => x.Load())
.Returns(yamlConfig);
_mergerMock
.Setup(x => x.Merge(codeEndpoints, yamlConfig))
.Returns(codeEndpoints);
_service.DiscoverEndpoints();
_mergerMock.Verify(x => x.Merge(codeEndpoints, yamlConfig), Times.Once);
}
[Fact]
public void DiscoverEndpoints_ReturnsMergedEndpoints()
{
var codeEndpoints = new List
{
CreateEndpoint("GET", "/api/test", TimeSpan.FromSeconds(10))
};
var mergedEndpoints = new List
{
CreateEndpoint("GET", "/api/test", TimeSpan.FromMinutes(5))
};
_discoveryProviderMock
.Setup(x => x.DiscoverEndpoints())
.Returns(codeEndpoints);
_mergerMock
.Setup(x => x.Merge(It.IsAny>(), It.IsAny()))
.Returns(mergedEndpoints);
var result = _service.DiscoverEndpoints();
result.Should().BeSameAs(mergedEndpoints);
}
[Fact]
public void DiscoverEndpoints_ContinuesWithNullYamlConfig_WhenLoaderReturnsNull()
{
var codeEndpoints = new List
{
CreateEndpoint("GET", "/api/test")
};
_discoveryProviderMock
.Setup(x => x.DiscoverEndpoints())
.Returns(codeEndpoints);
_yamlLoaderMock
.Setup(x => x.Load())
.Returns((MicroserviceYamlConfig?)null);
_mergerMock
.Setup(x => x.Merge(codeEndpoints, null))
.Returns(codeEndpoints);
var result = _service.DiscoverEndpoints();
_mergerMock.Verify(x => x.Merge(codeEndpoints, null), Times.Once);
result.Should().BeSameAs(codeEndpoints);
}
[Fact]
public void DiscoverEndpoints_ContinuesWithNullYamlConfig_WhenLoaderThrows()
{
var codeEndpoints = new List
{
CreateEndpoint("GET", "/api/test")
};
_discoveryProviderMock
.Setup(x => x.DiscoverEndpoints())
.Returns(codeEndpoints);
_yamlLoaderMock
.Setup(x => x.Load())
.Throws(new Exception("YAML parsing failed"));
_mergerMock
.Setup(x => x.Merge(codeEndpoints, null))
.Returns(codeEndpoints);
var result = _service.DiscoverEndpoints();
// Should not throw, should continue with null config
_mergerMock.Verify(x => x.Merge(codeEndpoints, null), Times.Once);
result.Should().BeSameAs(codeEndpoints);
}
private static EndpointDescriptor CreateEndpoint(
string method,
string path,
TimeSpan? timeout = null)
{
return new EndpointDescriptor
{
ServiceName = "test-service",
Version = "1.0.0",
Method = method,
Path = path,
DefaultTimeout = timeout ?? TimeSpan.FromSeconds(30)
};
}
}