CounterDroneBackend/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs
tian 56d345189e refactor: 编组概念拆分为批次(WaveId)+火力单元(FireUnit)
Breaking: Group表/枚举/Service/Repository移除; GroupId->WaveId; DroneGroup->DroneWave; DroneGroupId->DroneWaveId; GroupManager删除

Docs: CHANGELOG 0.7.0; 总体架构 V10; DefensePlanner V4; 实施计划 V1.4; 对接文档 V1.2

Tests: 204/204 pass
2026-06-15 15:13:48 +08:00

565 lines
21 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 TaskStatus = CounterDrone.Core.Models.TaskStatus;
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 SimTaskRepository(_db),
new CombatSceneRepository(_db),
new ControlZoneRepository(_db),
new TargetConfigRepository(_db),
new EquipmentDeploymentRepository(_db),
new CloudDispersalRepository(_db),
new RoutePlanRepository(_db),
new WaypointRepository(_db));
}
public void Dispose()
{
_db?.Close();
if (Directory.Exists(_testDir))
Directory.Delete(_testDir, true);
}
// ========== Task CRUD ==========
[Fact]
public void CreateTask_Valid_ReturnsTask()
{
var task = _service.CreateTask("测试任务", "SIM-20260611-001");
Assert.NotNull(task);
Assert.Equal("测试任务", task.Name);
Assert.Equal("SIM-20260611-001", task.TaskNumber);
Assert.Equal((int)TaskStatus.Configuring, task.Status);
Assert.Equal(1, task.CurrentStep);
Assert.NotEmpty(task.Id);
}
[Fact]
public void CreateTask_AutoGeneratesTaskNumber()
{
var task = _service.CreateTask("自动编号", "");
Assert.StartsWith("SIM-", task.TaskNumber);
}
[Fact]
public void CreateTask_EmptyName_Throws()
{
Assert.Throws<ArgumentException>(() =>
_service.CreateTask("", "SIM-001"));
}
[Fact]
public void CreateTask_DuplicateNumber_Throws()
{
_service.CreateTask("Task A", "SIM-001");
Assert.Throws<ArgumentException>(() =>
_service.CreateTask("Task B", "SIM-001"));
}
[Fact]
public void DeleteTask_RemovesTask()
{
var task = _service.CreateTask("ToDelete", "");
_service.DeleteTask(task.Id);
var detail = _service.GetTaskDetail(task.Id);
Assert.Null(detail);
}
[Fact]
public void GetTaskDetail_ReturnsFullConfig()
{
var task = _service.CreateTask("Full Config", "");
// 保存各步骤配置
_service.SaveScene(task.Id, new CombatScene
{
SceneType = (int)SceneType.Mountain,
WindSpeed = 10.0,
Temperature = 25.0,
});
_service.SaveTarget(task.Id, new TargetConfig
{
WaveId = "default",
TargetType = (int)TargetType.FixedWing,
Quantity = 3,
});
_service.SaveCloudDispersal(task.Id, new CloudDispersal
{
AerosolType = (int)AerosolType.ActiveMaterial,
Duration = 45.0,
});
_service.UpdateStep(task.Id, 4);
// 读取完整配置
var detail = _service.GetTaskDetail(task.Id);
Assert.Equal("Full Config", detail.Task.Name);
Assert.Equal(4, detail.Task.CurrentStep);
Assert.Equal((int)SceneType.Mountain, detail.Scene.SceneType);
Assert.Equal(10.0, detail.Scene.WindSpeed);
Assert.Equal(3, detail.Targets[0].Quantity);
Assert.Equal((int)AerosolType.ActiveMaterial, detail.Cloud.AerosolType);
}
// ========== Step Save ==========
[Fact]
public void SaveScene_FirstTime_Inserts()
{
var task = _service.CreateTask("Scene Test", "");
_service.SaveScene(task.Id, new CombatScene
{
WeatherType = (int)WeatherType.Fog,
Visibility = 2000.0,
TimeOfDay = "03:00",
});
var detail = _service.GetTaskDetail(task.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 task = _service.CreateTask("Update Scene", "");
_service.SaveScene(task.Id, new CombatScene { WindSpeed = 5.0 });
_service.SaveScene(task.Id, new CombatScene { WindSpeed = 15.0 });
var detail = _service.GetTaskDetail(task.Id);
Assert.Equal(15.0, detail.Scene.WindSpeed);
}
[Fact]
public void SaveControlZones_SavesAndRetrieves()
{
var task = _service.CreateTask("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(task.Id, zones);
var detail = _service.GetTaskDetail(task.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 task = _service.CreateTask("Zone Replace", "");
_service.SaveControlZones(task.Id, new List<ControlZone>
{
new ControlZone { Name = "Old" }
});
_service.SaveControlZones(task.Id, new List<ControlZone>
{
new ControlZone { Name = "New1" },
new ControlZone { Name = "New2" },
});
var detail = _service.GetTaskDetail(task.Id);
Assert.Equal(2, detail.ControlZones.Count);
}
[Fact]
public void SaveTarget_SavesCorrectly()
{
var task = _service.CreateTask("Target Test", "");
_service.SaveTarget(task.Id, new TargetConfig
{
WaveId = "default",
TargetType = (int)TargetType.Piston,
Quantity = 5,
PowerType = (int)PowerType.Piston,
Wingspan = 2.5,
TypicalSpeed = 120.0,
TypicalAltitude = 800.0,
});
var detail = _service.GetTaskDetail(task.Id);
var target = detail.Targets[0];
Assert.Equal((int)TargetType.Piston, target.TargetType);
Assert.Equal(5, target.Quantity);
Assert.Equal(120.0, target.TypicalSpeed);
}
[Fact]
public void SaveDeployment_SavesMultiple()
{
var task = _service.CreateTask("Equip Test", "");
var equips = new List<EquipmentDeployment>
{
new EquipmentDeployment
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
Quantity = 2,
PositionX = 1000, PositionY = 0, PositionZ = 50,
MuzzleVelocity = 800.0,
MunitionCount = 3,
},
new EquipmentDeployment
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1,
RadarRange = 5000.0,
},
};
_service.SaveDeployment(task.Id, equips);
var detail = _service.GetTaskDetail(task.Id);
Assert.Equal(2, detail.Equipment.Count);
var platform = detail.Equipment.Find(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
Assert.NotNull(platform);
Assert.Equal(2, platform.Quantity);
Assert.Equal(800.0, platform.MuzzleVelocity);
}
[Fact]
public void SaveRoute_SavesWaypoints()
{
var task = _service.CreateTask("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(task.Id, "default", route, waypoints);
var detail = _service.GetTaskDetail(task.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 SearchTasks_KeywordFilter()
{
_service.CreateTask("城市防御演习", "");
_service.CreateTask("平原拦截测试", "");
_service.CreateTask("海岸边防演练", "");
var result = _service.SearchTasks("城市", null, null, 1, 10);
Assert.Equal(1, result.TotalCount);
Assert.Equal("城市防御演习", result.Items[0].Name);
}
[Fact]
public void SearchTasks_NoResults()
{
_service.CreateTask("Task A", "");
var result = _service.SearchTasks("不存在的任务", null, null, 1, 10);
Assert.Equal(0, result.TotalCount);
}
[Fact]
public void SearchTasks_Pagination()
{
for (int i = 0; i < 15; i++)
_service.CreateTask($"Task {i:D2}", "");
var page1 = _service.SearchTasks(null, null, null, 1, 5);
Assert.Equal(15, page1.TotalCount);
Assert.Equal(5, page1.Items.Count);
Assert.Equal(3, page1.TotalPages);
var page3 = _service.SearchTasks(null, null, null, 3, 5);
Assert.Equal(5, page3.Items.Count);
}
[Fact]
public void SearchTasks_DateRangeFilter()
{
// All tasks created today, so date filter should include them
_service.CreateTask("Recent Task", "");
var yesterday = DateTime.UtcNow.AddDays(-1).ToString("yyyy-MM-dd");
var tomorrow = DateTime.UtcNow.AddDays(1).ToString("yyyy-MM-dd");
var result = _service.SearchTasks(null, yesterday, tomorrow, 1, 10);
Assert.Equal(1, result.TotalCount);
var oldResult = _service.SearchTasks(null, "2020-01-01", "2020-12-31", 1, 10);
Assert.Equal(0, oldResult.TotalCount);
}
// ========== UpdateStep ==========
[Fact]
public void UpdateStep_ValidSteps()
{
var task = _service.CreateTask("Step Test", "");
Assert.Equal(1, task.CurrentStep);
_service.UpdateStep(task.Id, 3);
var detail = _service.GetTaskDetail(task.Id);
Assert.Equal(3, detail.Task.CurrentStep);
_service.UpdateStep(task.Id, 5);
detail = _service.GetTaskDetail(task.Id);
Assert.Equal(5, detail.Task.CurrentStep);
}
[Fact]
public void UpdateStep_ClampedToRange()
{
var task = _service.CreateTask("Clamp Test", "");
_service.UpdateStep(task.Id, 0);
Assert.Equal(1, _service.GetTaskDetail(task.Id).Task.CurrentStep);
_service.UpdateStep(task.Id, 10);
Assert.Equal(5, _service.GetTaskDetail(task.Id).Task.CurrentStep);
}
// ========== Full Workflow ==========
[Fact]
public void FullWorkflow_CreateConfigureSearch()
{
// 1. 创建任务
var task = _service.CreateTask("完整流程测试", "");
Assert.NotNull(task);
// 2. 步骤1作战场景
_service.SaveScene(task.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(task.Id, 1);
// 3. 步骤1扩展管控区域
_service.SaveControlZones(task.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.SaveTarget(task.Id, new TargetConfig
{
WaveId = "default",
TargetType = (int)TargetType.HighSpeed,
Quantity = 2,
PowerType = (int)PowerType.Jet,
TypicalSpeed = 300.0,
});
_service.UpdateStep(task.Id, 2);
// 5. 步骤3装备部署
_service.SaveDeployment(task.Id, new List<EquipmentDeployment>
{
new EquipmentDeployment
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1,
RadarRange = 6000.0,
},
new EquipmentDeployment
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
Quantity = 3,
PositionX = 800, PositionY = 0, PositionZ = 50,
MuzzleVelocity = 850.0,
MunitionCount = 2,
},
});
_service.UpdateStep(task.Id, 3);
// 6. 步骤4云团抛撒
_service.SaveCloudDispersal(task.Id, new CloudDispersal
{
AerosolType = (int)AerosolType.ActiveMaterial,
TriggerMode = (int)TriggerMode.Area,
Duration = 50.0,
PositionX = 3000,
PositionY = 0,
PositionZ = 300,
});
_service.UpdateStep(task.Id, 4);
// 7. 步骤5航路规划
_service.SaveRoute(task.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(task.Id, 5);
// 8. 验证完整配置
var detail = _service.GetTaskDetail(task.Id);
Assert.Equal("完整流程测试", detail.Task.Name);
Assert.Equal(5, detail.Task.CurrentStep);
Assert.Equal((int)TaskStatus.Configuring, detail.Task.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.Targets);
Assert.Equal(2, detail.Targets[0].Quantity);
Assert.Equal(300.0, detail.Targets[0].TypicalSpeed);
// 装备
Assert.Equal(2, detail.Equipment.Count);
// 云团
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.SearchTasks("完整流程", null, null, 1, 10);
Assert.Equal(1, searchResult.TotalCount);
}
// ═══════════════════════════════════════
// 探测设备独立 CRUD
// ═══════════════════════════════════════
[Fact]
public void AddDetection_PersistsAndQueryable()
{
var task = _service.CreateTask("探测设备测试", "");
_service.AddDetection(task.Id, new EquipmentDeployment
{
Quantity = 1,
PositionX = 5000, PositionY = 0, PositionZ = 0,
RadarRange = 8000, EORange = 4000, IRRange = 3000,
DetectionAccuracy = 50,
});
var detections = _service.GetDetections(task.Id);
Assert.Single(detections);
Assert.Equal((int)EquipmentRole.Detection, detections[0].EquipmentRole);
Assert.Equal(8000.0, detections[0].RadarRange);
Assert.Equal(50.0, detections[0].DetectionAccuracy);
}
[Fact]
public void GetDetections_FiltersOutLaunchPlatforms()
{
var task = _service.CreateTask("探测过滤测试", "");
// 一个火力单元
_service.SaveDeployment(task.Id, new List<EquipmentDeployment>
{
new EquipmentDeployment
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
Quantity = 1, PositionX = 1000,
},
});
// 两个探测设备(独立添加)
_service.AddDetection(task.Id, new EquipmentDeployment { RadarRange = 5000 });
_service.AddDetection(task.Id, new EquipmentDeployment { EORange = 3000 });
var detections = _service.GetDetections(task.Id);
Assert.Equal(2, detections.Count); // 只有探测设备,不含火力单元
}
[Fact]
public void DeleteDetection_RemovesOnlyOne()
{
var task = _service.CreateTask("探测删除测试", "");
_service.AddDetection(task.Id, new EquipmentDeployment { RadarRange = 5000 });
var det2 = new EquipmentDeployment { EORange = 3000 };
_service.AddDetection(task.Id, det2);
_service.DeleteDetection(det2.Id);
var detections = _service.GetDetections(task.Id);
Assert.Single(detections);
Assert.Equal(5000.0, detections[0].RadarRange);
}
[Fact]
public void GetTaskDetail_IncludesDetections()
{
var task = _service.CreateTask("想定含探测", "");
_service.SaveDeployment(task.Id, new List<EquipmentDeployment>
{
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 1 },
});
_service.AddDetection(task.Id, new EquipmentDeployment { RadarRange = 10000 });
var detail = _service.GetTaskDetail(task.Id);
// Equipment 包含火力单元 + 探测设备
Assert.Equal(2, detail.Equipment.Count);
Assert.Contains(detail.Equipment, e => e.EquipmentRole == (int)EquipmentRole.Detection);
Assert.Contains(detail.Equipment, e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
}
}
}