271 lines
8.3 KiB
C#
271 lines
8.3 KiB
C#
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<ITestExecutor> CreateMockTestExecutor()
|
|
{
|
|
var mock = new Mock<ITestExecutor>();
|
|
return mock;
|
|
}
|
|
|
|
private Mock<IMetricsCalculator> CreateMockMetricsCalculator()
|
|
{
|
|
var mock = new Mock<IMetricsCalculator>();
|
|
mock.Setup(m => m.CalculateMetrics(
|
|
It.IsAny<List<TelemetryData>>(),
|
|
It.IsAny<ReferencePath>()))
|
|
.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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Action<TelemetryData>>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new TestExecutionResult
|
|
{
|
|
TestRunId = Guid.NewGuid(),
|
|
Status = TestStatus.Completed,
|
|
TelemetryData = new List<TelemetryData>(),
|
|
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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Action<TelemetryData>>(),
|
|
It.IsAny<CancellationToken>()), 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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Action<TelemetryData>>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.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<InvalidOperationException>(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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Action<TelemetryData>>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.Callback<TestScenario, NavigationParameterSet, Action<TelemetryData>?, CancellationToken>(
|
|
(s, p, callback, ct) =>
|
|
{
|
|
callback?.Invoke(TestHelpers.CreateTelemetryData());
|
|
})
|
|
.ReturnsAsync(new TestExecutionResult
|
|
{
|
|
Status = TestStatus.Completed,
|
|
TelemetryData = new List<TelemetryData>()
|
|
});
|
|
|
|
var scenario = TestHelpers.CreateStraightLineScenario();
|
|
var parameters = TestHelpers.CreateDefaultParameterSet();
|
|
|
|
// Act
|
|
await tuningNav.ExecuteTestAsync(scenario, parameters);
|
|
|
|
// Assert
|
|
telemetryReceived.Should().BeTrue();
|
|
}
|
|
}
|