using FluentAssertions; using Moq; using Xunit; using RobotNet10.NavigationTune.Execution; using RobotNet10.NavigationTune.Shared.Interfaces; using RobotNet10.NavigationTune.Shared.Models; using RobotNet10.NavigationTune.Test.Helpers; using RobotNet10.NavigationTune.Services; namespace RobotNet10.NavigationTune.Test.Execution; public class TuningNavigationTests { private Mock CreateMockTestExecutor() { var mock = new Mock(); return mock; } private Mock CreateMockMetricsCalculator() { var mock = new Mock(); mock.Setup(m => m.CalculateMetrics( It.IsAny>(), It.IsAny())) .Returns(new TestMetrics { OverallScore = 0.85f, CrossTrackErrorRMS = 0.05f, HeadingErrorRMS = 0.02f }); return mock; } [Fact] public void Constructor_WithDependencies_ShouldInitialize() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); // Act var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); // Assert tuningNav.Should().NotBeNull(); tuningNav.IsTestRunning.Should().BeFalse(); } [Fact] public async Task ExecuteTestAsync_WithValidInputs_ShouldReturnResult() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); testExecutor.Setup(e => e.ExecuteAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .ReturnsAsync(new TestExecutionResult { TestRunId = Guid.NewGuid(), Status = TestStatus.Completed, TelemetryData = new List(), StartTime = DateTime.UtcNow }); var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); var scenario = TestHelpers.CreateStraightLineScenario(); var parameters = TestHelpers.CreateDefaultParameterSet(); // Act var result = await tuningNav.ExecuteTestAsync(scenario, parameters); // Assert result.Should().NotBeNull(); result.Status.Should().Be(TestStatus.Completed); testExecutor.Verify(e => e.ExecuteAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); } [Fact] public async Task ExecuteTestAsync_WhenAlreadyRunning_ShouldThrow() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); // Simulate long-running test testExecutor.Setup(e => e.ExecuteAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Returns(async () => { await Task.Delay(1000); return new TestExecutionResult { Status = TestStatus.Completed }; }); var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); var scenario = TestHelpers.CreateStraightLineScenario(); var parameters = TestHelpers.CreateDefaultParameterSet(); // Act var task1 = tuningNav.ExecuteTestAsync(scenario, parameters); await Task.Delay(50); // Let first test start // Assert await Assert.ThrowsAsync(async () => await tuningNav.ExecuteTestAsync(scenario, parameters)); // Cleanup tuningNav.Stop(); await task1; } [Fact] public void GetProgress_ShouldDelegateToTestExecutor() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); testExecutor.Setup(e => e.GetProgress()) .Returns(new TestProgress { ProgressPercent = 0.5, DistanceTraveled = 5.0 }); var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); // Act var progress = tuningNav.GetProgress(); // Assert progress.Should().NotBeNull(); progress.ProgressPercent.Should().Be(0.5); testExecutor.Verify(e => e.GetProgress(), Times.Once); } [Fact] public void Pause_ShouldDelegateToTestExecutor() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); // Act tuningNav.Pause(); // Assert testExecutor.Verify(e => e.Pause(), Times.Once); } [Fact] public void Resume_ShouldDelegateToTestExecutor() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); // Act tuningNav.Resume(); // Assert testExecutor.Verify(e => e.Resume(), Times.Once); } [Fact] public void Stop_ShouldDelegateToTestExecutor() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); // Act tuningNav.Stop(); // Assert testExecutor.Verify(e => e.Stop(), Times.Once); } [Fact] public void EmergencyStop_ShouldDelegateToTestExecutor() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); // Act tuningNav.EmergencyStop(); // Assert testExecutor.Verify(e => e.EmergencyStop(), Times.Once); } [Fact] public async Task ExecuteTestAsync_ShouldInvokeTelemetryUpdateEvent() { // Arrange var testExecutor = CreateMockTestExecutor(); var metricsCalculator = CreateMockMetricsCalculator(); var telemetryReceived = false; var tuningNav = new TuningNavigation( testExecutor.Object, metricsCalculator.Object); tuningNav.OnTelemetryUpdate += (telemetry) => telemetryReceived = true; testExecutor.Setup(e => e.ExecuteAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Callback?, CancellationToken>( (s, p, callback, ct) => { callback?.Invoke(TestHelpers.CreateTelemetryData()); }) .ReturnsAsync(new TestExecutionResult { Status = TestStatus.Completed, TelemetryData = new List() }); var scenario = TestHelpers.CreateStraightLineScenario(); var parameters = TestHelpers.CreateDefaultParameterSet(); // Act await tuningNav.ExecuteTestAsync(scenario, parameters); // Assert telemetryReceived.Should().BeTrue(); } }