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; } }