using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; using RobotNet10.NavigationTune.Interfaces; using RobotNet10.NavigationTune.Services; using RobotNet10.NavigationTune.Shared.Interfaces; using RobotNet10.NavigationTune.Shared.Models; using RobotNet10.NavigationTune.Test.Helpers; namespace RobotNet10.NavigationTune.Test.Services; public class TuningOrchestratorTests { private Mock CreateMockTuningNavigation() { var mock = new Mock(); mock.Setup(t => t.IsTestRunning).Returns(false); mock.Setup(t => t.GetProgress()) .Returns(new TestProgress { ProgressPercent = 0.5 }); return mock; } private Mock CreateMockMetricsCalculator() { var mock = new Mock(); mock.Setup(m => m.CalculateMetrics( It.IsAny>(), It.IsAny())) .Returns(new TestMetrics { OverallScore = 0.85f }); return mock; } private Mock CreateMockTestRepository() { return new Mock(); } private Mock CreateMockParameterManager() { var mock = new Mock(); mock.Setup(p => p.Validate(It.IsAny())) .Returns(new ValidationResult { IsValid = true }); return mock; } private static Mock CreateMockScopeFactory(ITestRepository? testRepository = null) { var scopeFactoryMock = new Mock(); var scopeMock = new Mock(); var providerMock = new Mock(); providerMock.Setup(p => p.GetService(typeof(ITestRepository))).Returns(testRepository); scopeMock.Setup(s => s.ServiceProvider).Returns(providerMock.Object); scopeFactoryMock.Setup(f => f.CreateScope()).Returns(scopeMock.Object); return scopeFactoryMock; } private static Mock CreateMockCancellationRegistry() { var mock = new Mock(); mock.Setup(r => r.Register(It.IsAny(), It.IsAny())); mock.Setup(r => r.TryCancel(It.IsAny())).Returns(false); mock.Setup(r => r.Unregister(It.IsAny())); return mock; } [Fact] public void Constructor_WithDependencies_ShouldInitialize() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); // Act var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); // Assert orchestrator.Should().NotBeNull(); } [Fact] public async Task RunSingleTestAsync_WithValidInputs_ShouldReturnResult() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); tuningNav.Setup(t => t.ExecuteTestAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny?>())) .ReturnsAsync(new TestExecutionResult { TestRunId = Guid.NewGuid(), Status = TestStatus.Completed, TelemetryData = new List() }); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); var scenario = TestHelpers.CreateStraightLineScenario(); var parameters = TestHelpers.CreateDefaultParameterSet(); // Act var result = await orchestrator.RunSingleTestAsync(scenario, parameters); // Assert result.Should().NotBeNull(); result.Status.Should().Be(TestStatus.Completed); tuningNav.Verify(t => t.ExecuteTestAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] public async Task RunSingleTestAsync_WithInvalidParameters_ShouldThrow() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); paramManager.Setup(p => p.Validate(It.IsAny())) .Returns(new ValidationResult { IsValid = false, Errors = new List { "Invalid parameter" } }); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); var scenario = TestHelpers.CreateStraightLineScenario(); var parameters = TestHelpers.CreateDefaultParameterSet(); // Act & Assert await Assert.ThrowsAsync(async () => await orchestrator.RunSingleTestAsync(scenario, parameters)); } [Fact] public async Task RunBatchTestsAsync_WithMultipleScenarios_ShouldRunAll() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); tuningNav.Setup(t => t.ExecuteTestAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny?>())) .ReturnsAsync(new TestExecutionResult { Status = TestStatus.Completed, TelemetryData = new List(), Metrics = new TestMetrics { OverallScore = 0.8f } }); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); var scenarios = new List { TestHelpers.CreateStraightLineScenario(5.0), TestHelpers.CreateCircleScenario(2.0) }; var parameters = TestHelpers.CreateDefaultParameterSet(); // Act var result = await orchestrator.RunBatchTestsAsync(scenarios, parameters); // Assert result.Should().NotBeNull(); result.Results.Should().HaveCount(2); tuningNav.Verify(t => t.ExecuteTestAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny?>()), Times.Exactly(2)); } [Fact] public async Task CompareConfigurationsAsync_WithMultipleConfigs_ShouldCompare() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); var scores = new[] { 0.7, 0.9, 0.8f }; var callCount = 0; tuningNav.Setup(t => t.ExecuteTestAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny?>())) .ReturnsAsync(() => new TestExecutionResult { Status = TestStatus.Completed, TelemetryData = new List(), Metrics = new TestMetrics { OverallScore = scores[callCount++] } }); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); var scenario = TestHelpers.CreateStraightLineScenario(); var configs = new List { TestHelpers.CreateDefaultParameterSet(), TestHelpers.CreateDefaultParameterSet(), TestHelpers.CreateDefaultParameterSet() }; configs[0].Name = "Config1"; configs[1].Name = "Config2"; configs[2].Name = "Config3"; // Act var result = await orchestrator.CompareConfigurationsAsync(configs, scenario); // Assert result.Should().NotBeNull(); result.Results.Should().HaveCount(3); result.BestConfiguration.Should().Be("Config2"); // Highest score (0.9) } [Fact] public void PauseTest_ShouldDelegateToTuningNavigation() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); // Act orchestrator.PauseTest("test-id"); // Assert tuningNav.Verify(t => t.Pause(), Times.Once); } [Fact] public void ResumeTest_ShouldDelegateToTuningNavigation() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); // Act orchestrator.ResumeTest("test-id"); // Assert tuningNav.Verify(t => t.Resume(), Times.Once); } [Fact] public void StopTest_ShouldDelegateToTuningNavigation() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); // Act orchestrator.StopTest("test-id"); // Assert tuningNav.Verify(t => t.Stop(), Times.Once); } [Fact] public void EmergencyStop_ShouldDelegateToTuningNavigation() { // Arrange var tuningNav = CreateMockTuningNavigation(); var metricsCalc = CreateMockMetricsCalculator(); var testRepo = CreateMockTestRepository(); var paramManager = CreateMockParameterManager(); var scopeFactory = CreateMockScopeFactory(testRepo.Object); var cancellationRegistry = CreateMockCancellationRegistry(); var orchestrator = new TuningOrchestrator( tuningNav.Object, metricsCalc.Object, testRepo.Object, paramManager.Object, scopeFactory.Object, cancellationRegistry.Object); // Act orchestrator.EmergencyStop("test-id"); // Assert tuningNav.Verify(t => t.EmergencyStop(), Times.Once); } }