using Flyshot.Core.Config;
using Flyshot.Core.Domain;
namespace Flyshot.Core.Tests;
///
/// 锁定 Task 3 的兼容输入行为,确保旧配置、.robot 元数据和路径策略都能被稳定加载。
///
public sealed class ConfigCompatibilityTests
{
///
/// 验证现有 RobotConfig.json 能被加载,并保持关键机器人参数与飞拍程序内容不变。
///
[Fact]
public void RobotConfigLoader_LoadsLegacyRobotConfig_AndPreservesPrograms()
{
var workspaceRoot = GetWorkspaceRoot();
var configPath = Path.Combine(workspaceRoot, "Rvbust", "EOL10_EAU_0", "RobotConfig.json");
var loaded = new RobotConfigLoader().Load(configPath);
Assert.True(loaded.Robot.UseDo);
Assert.Equal([7, 8], loaded.Robot.IoAddresses);
Assert.Equal(2, loaded.Robot.IoKeepCycles);
Assert.Equal(1.0, loaded.Robot.AccLimitScale);
Assert.Equal(1.0, loaded.Robot.JerkLimitScale);
Assert.Equal(5, loaded.Robot.AdaptIcspTryNum);
var program = Assert.Contains("EOL10_EAU_0", loaded.Programs);
Assert.Equal("EOL10_EAU_0", program.Name);
Assert.Equal(45, program.Waypoints.Count);
Assert.Equal(42, program.ShotWaypointCount);
Assert.Empty(program.AddressGroups[0].Addresses);
Assert.Equal([4, 3], program.AddressGroups[1].Addresses);
}
///
/// 验证旧配置缺少 offset_values 和 addr 字段时,会自动回填与旧系统一致的默认值。
///
[Fact]
public void RobotConfigLoader_FillsLegacyDefaults_WhenOptionalFieldsAreMissing()
{
var tempRoot = CreateTempDirectory();
try
{
var configPath = Path.Combine(tempRoot, "legacy.json");
File.WriteAllText(
configPath,
"""
{
"robot": {
"use_do": false,
"io_keep_cycles": 3,
"acc_limit": 0.5,
"jerk_limit": 0.25
},
"flying_shots": {
"demo": {
"traj_waypoints": [[0, 1], [2, 3], [4, 5]],
"shot_flags": [0, 1, 0]
}
}
}
""");
var loaded = new RobotConfigLoader().Load(configPath);
var program = Assert.Contains("demo", loaded.Programs);
Assert.False(loaded.Robot.UseDo);
Assert.Empty(loaded.Robot.IoAddresses);
Assert.Equal(3, loaded.Robot.IoKeepCycles);
Assert.Equal(0.5, loaded.Robot.AccLimitScale);
Assert.Equal(0.25, loaded.Robot.JerkLimitScale);
Assert.Equal([0, 0, 0], program.OffsetValues);
Assert.All(program.AddressGroups, group => Assert.Empty(group.Addresses));
}
finally
{
Directory.Delete(tempRoot, recursive: true);
}
}
///
/// 验证 .robot 解析会保留 Joint3 对 Joint2 的 couple 元数据,并构造规划侧可直接消费的 RobotProfile。
///
[Fact]
public void RobotModelLoader_LoadsRobotProfile_WithJointLimitsAndCoupling()
{
var workspaceRoot = GetWorkspaceRoot();
var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot");
var profile = new RobotModelLoader().LoadProfile(modelPath);
Assert.Equal("FANUC_LR_Mate_200iD_7L", profile.Name);
Assert.Equal(modelPath, profile.ModelPath);
Assert.Equal(6, profile.DegreesOfFreedom);
Assert.Equal(6.45, profile.JointLimits[0].VelocityLimit, precision: 2);
Assert.Equal(29.81, profile.JointLimits[2].AccelerationLimit, precision: 2);
var coupling = Assert.Single(profile.JointCouplings);
Assert.Equal("Joint3", coupling.SlaveJointName);
Assert.Equal("Joint2", coupling.MasterJointName);
Assert.Equal(1.0, coupling.Multiplier);
Assert.Equal(0.0, coupling.Offset);
}
///
/// 验证 RobotConfig 中的 acc_limit 和 jerk_limit 乘子会正确叠加到模型关节限制上。
///
[Fact]
public void RobotModelLoader_AppliesAccelerationAndJerkScales()
{
var workspaceRoot = GetWorkspaceRoot();
var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot");
var profile = new RobotModelLoader().LoadProfile(modelPath, accLimitScale: 0.5, jerkLimitScale: 0.25);
Assert.Equal(14.905, profile.JointLimits[2].AccelerationLimit, precision: 3);
Assert.Equal(62.115, profile.JointLimits[2].JerkLimit, precision: 3);
}
///
/// 验证路径兼容层既能补旧目录候选,也能按平台策略生成默认用户数据目录。
///
[Fact]
public void PathCompatibility_ResolvesLegacyCandidates_AndBuildsUserDataRoots()
{
var tempRoot = CreateTempDirectory();
try
{
var legacyConfigPath = Path.Combine(tempRoot, "Rvbust", "Install", "FlyingShot", "Config", "sample.json");
Directory.CreateDirectory(Path.GetDirectoryName(legacyConfigPath)!);
File.WriteAllText(legacyConfigPath, "{}");
var resolved = PathCompatibility.ResolveConfigPath("sample.json", tempRoot);
Assert.Equal(legacyConfigPath, resolved);
Assert.Equal("/home/tester/.Rvbust/Data", PathCompatibility.BuildUserDataRoot("/home/tester", CompatibilityPathStyle.Posix));
Assert.Equal(@"C:\Users\tester\.Rvbust\Data", PathCompatibility.BuildUserDataRoot(@"C:\Users\tester", CompatibilityPathStyle.Windows));
}
finally
{
Directory.Delete(tempRoot, recursive: true);
}
}
///
/// 定位当前工作区根目录,便于复用父仓库中的真实样本。
///
private static string GetWorkspaceRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
var slnPath = Path.Combine(current.FullName, "FlyshotReplacement.sln");
if (File.Exists(slnPath))
{
return Path.GetFullPath(Path.Combine(current.FullName, ".."));
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Unable to locate the flyshot workspace root.");
}
///
/// 创建当前测试专用的临时目录,避免不同测试之间相互污染。
///
private static string CreateTempDirectory()
{
var tempPath = Path.Combine(Path.GetTempPath(), "flyshot-config-tests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempPath);
return tempPath;
}
}