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