CounterDroneBackend/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs

504 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using CounterDrone.Core;
using CounterDrone.Core.Models;
using CounterDrone.Core.Repository;
using CounterDrone.Core.Services;
using SQLite;
using Xunit;
using ScenarioStatus = CounterDrone.Core.Models.ScenarioStatus;
namespace CounterDrone.Core.Tests
{
public class ScenarioServiceTests : IDisposable
{
private readonly string _testDir;
private readonly SQLiteConnection _db;
private readonly IScenarioService _service;
public ScenarioServiceTests()
{
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
var paths = new TestPathProvider(_testDir);
var dbManager = new DatabaseManager(paths);
_db = dbManager.OpenMainDb();
_service = new ScenarioService(
new ScenarioRepository(_db),
new CombatSceneRepository(_db),
new ControlZoneRepository(_db),
new ScenarioDroneRepository(_db),
new ScenarioUnitRepository(_db),
new CloudDispersalRepository(_db),
new RoutePlanRepository(_db),
new WaypointRepository(_db));
}
public void Dispose()
{
_db?.Close();
if (Directory.Exists(_testDir))
Directory.Delete(_testDir, true);
}
// ========== scenario CRUD ==========
[Fact]
public void CreateScenario_Valid_ReturnsScenario()
{
var scenario = _service.CreateScenario("测试任务", "SIM-20260611-001");
Assert.NotNull(scenario);
Assert.Equal("测试任务", scenario.Name);
Assert.Equal("SIM-20260611-001", scenario.ScenarioNumber);
Assert.Equal((int)ScenarioStatus.Configuring, scenario.Status);
Assert.Equal(1, scenario.CurrentStep);
Assert.NotEmpty(scenario.Id);
}
[Fact]
public void CreateScenario_AutoGeneratesScenarioNumber()
{
var scenario = _service.CreateScenario("自动编号", "");
Assert.StartsWith("SIM-", scenario.ScenarioNumber);
}
[Fact]
public void CreateScenario_EmptyName_Throws()
{
Assert.Throws<ArgumentException>(() =>
_service.CreateScenario("", "SIM-001"));
}
[Fact]
public void CreateScenario_DuplicateNumber_Throws()
{
_service.CreateScenario("scenario A", "SIM-001");
Assert.Throws<ArgumentException>(() =>
_service.CreateScenario("scenario B", "SIM-001"));
}
[Fact]
public void DeleteScenario_RemovesScenario()
{
var scenario = _service.CreateScenario("ToDelete", "");
_service.DeleteScenario(scenario.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Null(detail);
}
[Fact]
public void GetScenarioDetail_ReturnsFullConfig()
{
var scenario = _service.CreateScenario("Full Config", "");
// 保存各步骤配置
_service.SaveScene(scenario.Id, new CombatScene
{
SceneType = (int)SceneType.Mountain,
WindSpeed = 10.0,
Temperature = 25.0,
});
_service.SaveScenarioDrone(scenario.Id, TestData.CreateScenarioDrone("default", 3));
_service.SaveCloudDispersal(scenario.Id, new CloudDispersal
{
AerosolType = (int)AerosolType.ActiveMaterial,
Duration = 45.0,
});
_service.UpdateStep(scenario.Id, 4);
// 读取完整配置
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal("Full Config", detail!.Info.Name);
Assert.Equal(4, detail!.Info.CurrentStep);
Assert.Equal((int)SceneType.Mountain, detail!.Scene.SceneType);
Assert.Equal(10.0, detail!.Scene.WindSpeed);
Assert.Equal(3, detail!.Drones[0].Quantity);
Assert.Equal((int)AerosolType.ActiveMaterial, detail!.Cloud.AerosolType);
}
// ========== Step Save ==========
[Fact]
public void SaveScene_FirstTime_Inserts()
{
var scenario = _service.CreateScenario("Scene Test", "");
_service.SaveScene(scenario.Id, new CombatScene
{
WeatherType = (int)WeatherType.Fog,
Visibility = 2000.0,
TimeOfDay = "03:00",
});
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal((int)WeatherType.Fog, detail!.Scene.WeatherType);
Assert.Equal(2000.0, detail!.Scene.Visibility);
Assert.Equal("03:00", detail!.Scene.TimeOfDay);
}
[Fact]
public void SaveScene_SecondTime_Updates()
{
var scenario = _service.CreateScenario("Update Scene", "");
_service.SaveScene(scenario.Id, new CombatScene { WindSpeed = 5.0 });
_service.SaveScene(scenario.Id, new CombatScene { WindSpeed = 15.0 });
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(15.0, detail!.Scene.WindSpeed);
}
[Fact]
public void SaveControlZones_SavesAndRetrieves()
{
var scenario = _service.CreateScenario("Zone Test", "");
var zones = new List<ControlZone>
{
new ControlZone { Name = "禁区A", VerticesJson = "[{\"x\":0,\"y\":0,\"z\":0},{\"x\":100,\"y\":0,\"z\":0},{\"x\":100,\"y\":100,\"z\":0}]", MinAltitude = 0, MaxAltitude = 500 },
new ControlZone { Name = "禁区B", VerticesJson = "[{\"x\":200,\"y\":200,\"z\":0}]", MinAltitude = 100, MaxAltitude = 300 },
};
_service.SaveControlZones(scenario.Id, zones);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(2, detail!.ControlZones.Count);
Assert.Equal("禁区A", detail!.ControlZones[0].Name);
Assert.Equal(0, detail!.ControlZones[0].OrderIndex);
Assert.Equal("禁区B", detail!.ControlZones[1].Name);
Assert.Equal(1, detail!.ControlZones[1].OrderIndex);
}
[Fact]
public void SaveControlZones_ReplaceExisting()
{
var scenario = _service.CreateScenario("Zone Replace", "");
_service.SaveControlZones(scenario.Id, new List<ControlZone>
{
new ControlZone { Name = "Old" }
});
_service.SaveControlZones(scenario.Id, new List<ControlZone>
{
new ControlZone { Name = "New1" },
new ControlZone { Name = "New2" },
});
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(2, detail!.ControlZones.Count);
}
[Fact]
public void SaveScenarioDrone_SavesCorrectly()
{
var scenario = _service.CreateScenario("Drone Test", "");
_service.SaveScenarioDrone(scenario.Id, TestData.CreateScenarioDrone("default", 5));
var detail = _service.GetScenarioDetail(scenario.Id);
var target = detail!.Drones[0];
Assert.Equal((int)DroneType.FixedWing, TestData.DefaultDroneSpec.DroneType);
Assert.Equal(5, target.Quantity);
Assert.Equal(200.0, TestData.DefaultDroneSpec.TypicalSpeed);
}
[Fact]
public void SaveDeployment_SavesMultiple()
{
var scenario = _service.CreateScenario("Equip Test", "");
var unit1 = TestData.CreateScenarioUnit(1000, 0, 50, 0, 3);
var unit2 = TestData.CreateScenarioUnit(2000, 0, 50, 0, 5);
unit2.Id = Guid.NewGuid().ToString();
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit> { unit1, unit2 });
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(2, detail!.Units.Count);
Assert.Contains(detail!.Units, u => u.PositionX == 1000);
Assert.Contains(detail!.Units, u => u.PositionX == 2000);
}
[Fact]
public void SaveRoute_SavesWaypoints()
{
var scenario = _service.CreateScenario("Route Test", "");
var route = new RoutePlan
{
FormationMode = (int)FormationMode.Formation,
LateralSpacing = 60.0,
};
var waypoints = new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 0, PosZ = 100, Altitude = 500, Speed = 100 },
new Waypoint { PosX = 5000, PosY = 0, PosZ = 100, Altitude = 500, Speed = 100 },
new Waypoint { PosX = 10000, PosY = 0, PosZ = 100, Altitude = 300, Speed = 80 },
};
_service.SaveRoute(scenario.Id, "default", route, waypoints);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal((int)FormationMode.Formation, detail!.Routes[0].FormationMode);
Assert.Equal(3, detail!.WaypointGroups["default"].Count);
Assert.Equal(0, detail!.WaypointGroups["default"][0].OrderIndex);
Assert.Equal(2, detail!.WaypointGroups["default"][2].OrderIndex);
Assert.Equal(5000.0, detail!.WaypointGroups["default"][1].PosX);
}
// ========== Search & Pagination ==========
[Fact]
public void SearchScenarios_KeywordFilter()
{
_service.CreateScenario("城市防御演习", "");
_service.CreateScenario("平原拦截测试", "");
_service.CreateScenario("海岸边防演练", "");
var result = _service.SearchScenarios("城市", null, null, 1, 10);
Assert.Equal(1, result.TotalCount);
Assert.Equal("城市防御演习", result.Items[0].Name);
}
[Fact]
public void SearchScenarios_NoResults()
{
_service.CreateScenario("scenario A", "");
var result = _service.SearchScenarios("不存在的任务", null, null, 1, 10);
Assert.Equal(0, result.TotalCount);
}
[Fact]
public void SearchScenarios_Pagination()
{
for (int i = 0; i < 15; i++)
_service.CreateScenario($"scenario {i:D2}", "");
var page1 = _service.SearchScenarios("", null, null, 1, 5);
Assert.Equal(21, page1.TotalCount); // 15 + 6 预设想定
Assert.Equal(5, page1.Items.Count);
Assert.Equal(5, page1.TotalPages); // 21 ÷ 5 = 5 页
var page3 = _service.SearchScenarios("", null, null, 3, 5);
Assert.Equal(5, page3.Items.Count);
}
[Fact]
public void SearchScenarios_DateRangeFilter()
{
// All scenarios created today, so date filter should include them
_service.CreateScenario("Recent scenario", "");
var yesterday = DateTime.UtcNow.AddDays(-1).ToString("yyyy-MM-dd");
var tomorrow = DateTime.UtcNow.AddDays(1).ToString("yyyy-MM-dd");
var result = _service.SearchScenarios("", yesterday, tomorrow, 1, 10);
Assert.Equal(7, result.TotalCount); // 1 + 6 预设想定
var oldResult = _service.SearchScenarios("", "2020-01-01", "2020-12-31", 1, 10);
Assert.Equal(0, oldResult.TotalCount);
}
// ========== UpdateStep ==========
[Fact]
public void UpdateStep_ValidSteps()
{
var scenario = _service.CreateScenario("Step Test", "");
Assert.Equal(1, scenario.CurrentStep);
_service.UpdateStep(scenario.Id, 3);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(3, detail!.Info.CurrentStep);
_service.UpdateStep(scenario.Id, 5);
detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(5, detail!.Info.CurrentStep);
}
[Fact]
public void UpdateStep_ClampedToRange()
{
var scenario = _service.CreateScenario("Clamp Test", "");
_service.UpdateStep(scenario.Id, 0);
Assert.Equal(1, _service.GetScenarioDetail(scenario.Id)!.Info.CurrentStep);
_service.UpdateStep(scenario.Id, 10);
Assert.Equal(5, _service.GetScenarioDetail(scenario.Id)!.Info.CurrentStep);
}
// ========== Full Workflow ==========
[Fact]
public void FullWorkflow_CreateConfigureSearch()
{
// 1. 创建任务
var scenario = _service.CreateScenario("完整流程测试", "");
Assert.NotNull(scenario);
// 2. 步骤1作战场景
_service.SaveScene(scenario.Id, new CombatScene
{
SceneType = (int)SceneType.Urban,
WeatherType = (int)WeatherType.Rain,
WindSpeed = 8.0,
WindDirection = (int)WindDirection.SE,
Temperature = 18.0,
TimeOfDay = "14:00",
});
_service.UpdateStep(scenario.Id, 1);
// 3. 步骤1扩展管控区域
_service.SaveControlZones(scenario.Id, new List<ControlZone>
{
new ControlZone
{
Name = "核心区",
VerticesJson = "[{\"x\":0,\"y\":0,\"z\":0},{\"x\":500,\"y\":0,\"z\":0},{\"x\":500,\"y\":500,\"z\":0},{\"x\":0,\"y\":500,\"z\":0}]",
MinAltitude = 0, MaxAltitude = 1000,
}
});
// 4. 步骤2目标配置
_service.SaveScenarioDrone(scenario.Id, TestData.CreateScenarioDrone("default", 2));
_service.UpdateStep(scenario.Id, 2);
// 5. 步骤3装备部署
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
TestData.CreateScenarioUnit(0, 0, 50, 0, null),
TestData.CreateScenarioUnit(800, 0, 50, 0, 2),
});
_service.UpdateStep(scenario.Id, 3);
// 6. 步骤4云团抛撒
_service.SaveCloudDispersal(scenario.Id, new CloudDispersal
{
AerosolType = (int)AerosolType.ActiveMaterial,
TriggerMode = (int)TriggerMode.Area,
Duration = 50.0,
PositionX = 3000,
PositionY = 0,
PositionZ = 300,
});
_service.UpdateStep(scenario.Id, 4);
// 7. 步骤5航路规划
_service.SaveRoute(scenario.Id, "default", new RoutePlan
{
FormationMode = (int)FormationMode.Swarm,
LateralSpacing = 30.0,
}, new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 100, PosZ = 200, Altitude = 600, Speed = 250 },
new Waypoint { PosX = 10000, PosY = 100, PosZ = 200, Altitude = 600, Speed = 250 },
});
_service.UpdateStep(scenario.Id, 5);
// 8. 验证完整配置
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal("完整流程测试", detail!.Info.Name);
Assert.Equal(5, detail!.Info.CurrentStep);
Assert.Equal((int)ScenarioStatus.Configuring, detail.Info.Status);
// 场景
Assert.Equal((int)SceneType.Urban, detail!.Scene.SceneType);
Assert.Equal((int)WeatherType.Rain, detail!.Scene.WeatherType);
Assert.Equal("14:00", detail!.Scene.TimeOfDay);
// 管控区域
Assert.Single(detail.ControlZones);
Assert.Equal("核心区", detail!.ControlZones[0].Name);
// 目标
Assert.Single(detail.Drones);
Assert.Equal(2, detail!.Drones[0].Quantity);
Assert.Equal(200.0, TestData.DefaultDroneSpec.TypicalSpeed);
// 装备
Assert.NotEmpty(detail!.Units);
// 云团
Assert.Equal((int)AerosolType.ActiveMaterial, detail!.Cloud.AerosolType);
Assert.Equal(50.0, detail.Cloud.Duration);
// 航路
Assert.Equal((int)FormationMode.Swarm, detail!.Routes[0].FormationMode);
Assert.Equal(2, detail!.WaypointGroups["default"].Count);
Assert.Equal(250.0, detail!.WaypointGroups["default"][0].Speed);
// 9. 搜索验证
var searchResult = _service.SearchScenarios("完整流程", null, null, 1, 10);
Assert.Equal(1, searchResult.TotalCount);
}
// ═══════════════════════════════════════
// 探测设备独立 CRUD
// ═══════════════════════════════════════
[Fact]
public void AddDetection_PersistsAndQueryable()
{
var scenario = _service.CreateScenario("探测设备测试", "");
_service.AddDetection(scenario.Id, TestData.CreateDetectionUnit(5000, 0, 0));
var detections = _service.GetDetections(scenario.Id);
Assert.Single(detections);
Assert.Equal((int)EquipmentRole.Detection, detections[0].EquipmentRole);
Assert.Equal(5000.0, detections[0].PositionX);
}
[Fact]
public void GetDetections_FiltersOutLaunchPlatforms()
{
var scenario = _service.CreateScenario("探测过滤测试", "");
// 一个火力单元
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
TestData.CreateScenarioUnit(1000, 0, 50, 0, null),
});
// 两个探测设备(独立添加)
_service.AddDetection(scenario.Id, TestData.CreateScenarioUnit(0, 0, 50, 0, null));
_service.AddDetection(scenario.Id, TestData.CreateScenarioUnit(0, 0, 50, 0, null));
var detections = _service.GetDetections(scenario.Id);
Assert.Equal(2, detections.Count); // 只有探测设备,不含火力单元
}
[Fact]
public void DeleteDetection_RemovesOnlyOne()
{
var scenario = _service.CreateScenario("探测删除测试", "");
var det1 = TestData.CreateDetectionUnit(3000, 0, 0);
var det2 = TestData.CreateDetectionUnit(5000, 0, 0);
_service.AddDetection(scenario.Id, det1);
_service.AddDetection(scenario.Id, det2);
_service.DeleteDetection(det2.Id);
var detections = _service.GetDetections(scenario.Id);
Assert.Single(detections);
Assert.Equal(3000.0, detections[0].PositionX);
}
[Fact]
public void GetScenarioDetail_IncludesDetections()
{
var scenario = _service.CreateScenario("想定含探测", "");
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
TestData.CreateScenarioUnit(0, 0, 50, 0, null),
});
_service.AddDetection(scenario.Id, TestData.CreateScenarioUnit(0, 0, 50, 0, null));
var detail = _service.GetScenarioDetail(scenario.Id);
// Equipment 包含火力单元 + 探测设备
Assert.NotEmpty(detail!.Units);
Assert.Contains(detail!.Units, e => e.EquipmentRole == (int)EquipmentRole.Detection);
Assert.Contains(detail!.Units, e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
}
}
}