388 lines
14 KiB
C#
388 lines
14 KiB
C#
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<ITuningNavigation> CreateMockTuningNavigation()
|
|
{
|
|
var mock = new Mock<ITuningNavigation>();
|
|
mock.Setup(t => t.IsTestRunning).Returns(false);
|
|
mock.Setup(t => t.GetProgress())
|
|
.Returns(new TestProgress { ProgressPercent = 0.5 });
|
|
|
|
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 });
|
|
|
|
return mock;
|
|
}
|
|
|
|
private Mock<ITestRepository> CreateMockTestRepository()
|
|
{
|
|
return new Mock<ITestRepository>();
|
|
}
|
|
|
|
private Mock<IParameterManager> CreateMockParameterManager()
|
|
{
|
|
var mock = new Mock<IParameterManager>();
|
|
mock.Setup(p => p.Validate(It.IsAny<NavigationParameterSet>()))
|
|
.Returns(new ValidationResult { IsValid = true });
|
|
|
|
return mock;
|
|
}
|
|
|
|
private static Mock<IServiceScopeFactory> CreateMockScopeFactory(ITestRepository? testRepository = null)
|
|
{
|
|
var scopeFactoryMock = new Mock<IServiceScopeFactory>();
|
|
var scopeMock = new Mock<IServiceScope>();
|
|
var providerMock = new Mock<IServiceProvider>();
|
|
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<IRunningTestCancellationRegistry> CreateMockCancellationRegistry()
|
|
{
|
|
var mock = new Mock<IRunningTestCancellationRegistry>();
|
|
mock.Setup(r => r.Register(It.IsAny<Guid>(), It.IsAny<CancellationTokenSource>()));
|
|
mock.Setup(r => r.TryCancel(It.IsAny<Guid>())).Returns(false);
|
|
mock.Setup(r => r.Unregister(It.IsAny<Guid>()));
|
|
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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Guid?>(),
|
|
It.IsAny<CancellationToken>(),
|
|
It.IsAny<Action<TestExecutionResult>?>()))
|
|
.ReturnsAsync(new TestExecutionResult
|
|
{
|
|
TestRunId = Guid.NewGuid(),
|
|
Status = TestStatus.Completed,
|
|
TelemetryData = new List<TelemetryData>()
|
|
});
|
|
|
|
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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Guid?>(),
|
|
It.IsAny<CancellationToken>()), 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<NavigationParameterSet>()))
|
|
.Returns(new ValidationResult
|
|
{
|
|
IsValid = false,
|
|
Errors = new List<string> { "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<InvalidOperationException>(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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Guid?>(),
|
|
It.IsAny<CancellationToken>(),
|
|
It.IsAny<Action<TestExecutionResult>?>()))
|
|
.ReturnsAsync(new TestExecutionResult
|
|
{
|
|
Status = TestStatus.Completed,
|
|
TelemetryData = new List<TelemetryData>(),
|
|
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<TestScenario>
|
|
{
|
|
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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Guid?>(),
|
|
It.IsAny<CancellationToken>(),
|
|
It.IsAny<Action<TestExecutionResult>?>()), 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<TestScenario>(),
|
|
It.IsAny<NavigationParameterSet>(),
|
|
It.IsAny<Guid?>(),
|
|
It.IsAny<CancellationToken>(),
|
|
It.IsAny<Action<TestExecutionResult>?>()))
|
|
.ReturnsAsync(() => new TestExecutionResult
|
|
{
|
|
Status = TestStatus.Completed,
|
|
TelemetryData = new List<TelemetryData>(),
|
|
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<NavigationParameterSet>
|
|
{
|
|
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);
|
|
}
|
|
}
|