using System.Net; using System.Net.Http.Json; using System.Text.Json; namespace Flyshot.Server.IntegrationTests; /// /// 锁定 flyshot-uaes-interface 现有 FastAPI 层的 HTTP 路径、参数绑定和返回 JSON 外形。 /// public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory) : IClassFixture { /// /// 验证根路径会返回旧 HTTP 服务使用的 Hello World JSON,而不是跳转到健康检查页。 /// [Fact] public async Task Root_ReturnsLegacyHelloWorldPayload() { using var client = factory.CreateClient(); using var response = await client.GetAsync("/"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); using var json = await ReadJsonAsync(response); Assert.Equal("Hello World", json.RootElement.GetProperty("message").GetString()); } /// /// 验证初始化链路和机器人信息接口会保持旧 FastAPI 服务的路径与返回字段风格。 /// [Fact] public async Task InitEndpoints_ExposeLegacyRobotInfoAndSpeedRatioShape() { using var client = factory.CreateClient(); using (var connectServerResponse = await client.PostAsync("/connect_server/?server_ip=127.0.0.1&port=50001", content: null)) { Assert.Equal(HttpStatusCode.OK, connectServerResponse.StatusCode); using var connectServerJson = await ReadJsonAsync(connectServerResponse); Assert.Equal("connected", connectServerJson.RootElement.GetProperty("status").GetString()); } using (var setupResponse = await client.PostAsync("/setup_robot/?robot_name=FANUC_LR_Mate_200iD", content: null)) { Assert.Equal(HttpStatusCode.OK, setupResponse.StatusCode); using var setupJson = await ReadJsonAsync(setupResponse); Assert.Equal("robot setup", setupJson.RootElement.GetProperty("status").GetString()); } using (var isSetupResponse = await client.GetAsync("/is_setup/")) { Assert.Equal(HttpStatusCode.OK, isSetupResponse.StatusCode); using var isSetupJson = await ReadJsonAsync(isSetupResponse); Assert.True(isSetupJson.RootElement.GetProperty("is_setup").GetBoolean()); } using (var activeControllerResponse = await client.PostAsync("/set_active_controller/?sim=true", content: null)) { Assert.Equal(HttpStatusCode.OK, activeControllerResponse.StatusCode); using var activeControllerJson = await ReadJsonAsync(activeControllerResponse); Assert.Equal("active controller set", activeControllerJson.RootElement.GetProperty("status").GetString()); } using (var connectRobotResponse = await client.PostAsync("/connect_robot/?ip=192.168.10.101", content: null)) { Assert.Equal(HttpStatusCode.OK, connectRobotResponse.StatusCode); using var connectRobotJson = await ReadJsonAsync(connectRobotResponse); Assert.Equal("robot connected", connectRobotJson.RootElement.GetProperty("status").GetString()); } using (var enableRobotResponse = await client.GetAsync("/enable_robot/")) { Assert.Equal(HttpStatusCode.OK, enableRobotResponse.StatusCode); using var enableRobotJson = await ReadJsonAsync(enableRobotResponse); Assert.True(enableRobotJson.RootElement.GetProperty("enable_robot").GetBoolean()); } using (var robotInfoResponse = await client.GetAsync("/robot_info/")) { Assert.Equal(HttpStatusCode.OK, robotInfoResponse.StatusCode); using var robotInfoJson = await ReadJsonAsync(robotInfoResponse); var robotInfoRoot = robotInfoJson.RootElement; Assert.Equal("FANUC_LR_Mate_200iD", robotInfoRoot.GetProperty("name").GetString()); Assert.Equal("flyshot-replacement-controller-client-compat/0.1.0", robotInfoRoot.GetProperty("server_version").GetString()); Assert.Equal(6, robotInfoRoot.GetProperty("dof").GetInt32()); Assert.Equal(1.0, robotInfoRoot.GetProperty("speed_ratio").GetDouble(), precision: 6); } using (var setSpeedRatioResponse = await client.PostAsJsonAsync("/set_speedRatio/", new { speed = 0.8 })) { Assert.Equal(HttpStatusCode.OK, setSpeedRatioResponse.StatusCode); using var setSpeedRatioJson = await ReadJsonAsync(setSpeedRatioResponse); Assert.Equal("set_speedRatio executed", setSpeedRatioJson.RootElement.GetProperty("message").GetString()); Assert.Equal(0, setSpeedRatioJson.RootElement.GetProperty("returnCode").GetInt32()); } using var updatedRobotInfoResponse = await client.GetAsync("/robot_info/"); Assert.Equal(HttpStatusCode.OK, updatedRobotInfoResponse.StatusCode); using var updatedRobotInfoJson = await ReadJsonAsync(updatedRobotInfoResponse); Assert.Equal(0.8, updatedRobotInfoJson.RootElement.GetProperty("speed_ratio").GetDouble(), precision: 6); } /// /// 验证 TCP、关节位置和位姿相关 HTTP 接口会保持旧服务的请求体与响应体结构。 /// [Fact] public async Task MotionStateEndpoints_RoundTripLegacyPayloadShapes() { using var client = factory.CreateClient(); await InitializeRobotAsync(client); using (var setTcpResponse = await client.PostAsJsonAsync("/set_tcp/", new { x = 1.0, y = 2.0, z = 3.0 })) { Assert.Equal(HttpStatusCode.OK, setTcpResponse.StatusCode); using var setTcpJson = await ReadJsonAsync(setTcpResponse); Assert.Equal("TCP set", setTcpJson.RootElement.GetProperty("status").GetString()); } using (var getTcpResponse = await client.GetAsync("/get_tcp/")) { Assert.Equal(HttpStatusCode.OK, getTcpResponse.StatusCode); using var getTcpJson = await ReadJsonAsync(getTcpResponse); var tcpValues = getTcpJson.RootElement.GetProperty("tcp").EnumerateArray().Select(static value => value.GetDouble()).ToArray(); Assert.Equal([1.0, 2.0, 3.0], tcpValues); } using (var moveJointResponse = await client.PostAsJsonAsync("/move_joint/", new { joints = new[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 } })) { Assert.Equal(HttpStatusCode.OK, moveJointResponse.StatusCode); using var moveJointJson = await ReadJsonAsync(moveJointResponse); Assert.Equal("robot moved", moveJointJson.RootElement.GetProperty("status").GetString()); } using (var getJointPositionResponse = await client.GetAsync("/get_joint_position/")) { Assert.Equal(HttpStatusCode.OK, getJointPositionResponse.StatusCode); using var getJointPositionJson = await ReadJsonAsync(getJointPositionResponse); var root = getJointPositionJson.RootElement; Assert.True(root.GetProperty("success").GetBoolean()); var jointValues = root.GetProperty("points").EnumerateArray().Select(static value => value.GetDouble()).ToArray(); Assert.Equal([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], jointValues); } using var getPoseResponse = await client.GetAsync("/get_pose"); Assert.Equal(HttpStatusCode.OK, getPoseResponse.StatusCode); using var getPoseJson = await ReadJsonAsync(getPoseResponse); Assert.Equal(7, getPoseJson.RootElement.GetProperty("pose").GetArrayLength()); using (var executeTrajectoryResponse = await client.PostAsJsonAsync("/execute_trajectory/", new { method = "icsp", save_traj = true, waypoints = new[] { new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, new[] { 0.1, 0.0, 0.0, 0.0, 0.0, 0.0 }, new[] { 0.2, 0.0, 0.0, 0.0, 0.0, 0.0 }, new[] { 0.3, 0.0, 0.0, 0.0, 0.0, 0.0 } } })) { Assert.Equal(HttpStatusCode.OK, executeTrajectoryResponse.StatusCode); using var executeTrajectoryJson = await ReadJsonAsync(executeTrajectoryResponse); Assert.Equal("trajectory executed", executeTrajectoryJson.RootElement.GetProperty("status").GetString()); } } /// /// 验证飞拍 HTTP 接口可以按旧 API 层的路径和字段完成上传、列出、执行与删除。 /// [Fact] public async Task FlyshotEndpoints_RoundTripLegacyUploadExecuteAndDeleteFlow() { using var client = factory.CreateClient(); await InitializeRobotAsync(client); var uploadPayload = new { addrs = new[] { new[] { 7, 8 }, new[] { 7, 8 }, Array.Empty(), Array.Empty() }, name = "demo-http-flyshot", offset_values = new[] { 0.0, 1.0, 0.0, 0.0 }, shot_flags = new[] { false, true, false, false }, waypoints = new[] { new[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6 }, new[] { 0.2, 0.2, 0.3, 0.4, 0.5, 0.6 }, new[] { 0.3, 0.2, 0.3, 0.4, 0.5, 0.6 }, new[] { 0.4, 0.2, 0.3, 0.4, 0.5, 0.6 } } }; using (var uploadResponse = await client.PostAsJsonAsync("/upload_flyshot/", uploadPayload)) { Assert.Equal(HttpStatusCode.OK, uploadResponse.StatusCode); using var uploadJson = await ReadJsonAsync(uploadResponse); Assert.Equal("FlyShot uploaded", uploadJson.RootElement.GetProperty("status").GetString()); } using (var listResponse = await client.GetAsync("/list_flyShotTraj/")) { Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); using var listJson = await ReadJsonAsync(listResponse); var names = listJson.RootElement.GetProperty("flyshot_trajs").EnumerateArray().Select(static value => value.GetString()).ToArray(); Assert.Contains("demo-http-flyshot", names); } using (var validResponse = await client.PostAsJsonAsync("/is_flyShotTrajValid/", new { name = "demo-http-flyshot", method = "icsp", save_traj = false })) { Assert.Equal(HttpStatusCode.OK, validResponse.StatusCode); using var validJson = await ReadJsonAsync(validResponse); Assert.True(validJson.RootElement.GetProperty("valid").GetBoolean()); Assert.True(validJson.RootElement.GetProperty("time").GetDouble() > 0.0); } using (var executeResponse = await client.PostAsJsonAsync("/execute_flyshot/", new { name = "demo-http-flyshot", move_to_start = true, method = "icsp", save_traj = true, use_cache = true })) { Assert.Equal(HttpStatusCode.OK, executeResponse.StatusCode); using var executeJson = await ReadJsonAsync(executeResponse); var executeRoot = executeJson.RootElement; Assert.Equal("FlyShot executed", executeRoot.GetProperty("status").GetString()); Assert.True(executeRoot.GetProperty("success").GetBoolean()); } using (var saveInfoResponse = await client.PostAsJsonAsync("/save_traj_info/", new { name = "demo-http-flyshot", method = "icsp" })) { Assert.Equal(HttpStatusCode.OK, saveInfoResponse.StatusCode); using var saveInfoJson = await ReadJsonAsync(saveInfoResponse); Assert.True(saveInfoJson.RootElement.GetProperty("success").GetBoolean()); } using (var deleteResponse = await client.PostAsJsonAsync("/delete_flyshot/", new { name = "demo-http-flyshot" })) { Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); using var deleteJson = await ReadJsonAsync(deleteResponse); Assert.Equal("FlyShot deleted", deleteJson.RootElement.GetProperty("status").GetString()); } } /// /// 复用旧 API 层常见的初始化顺序,把当前宿主推进到可执行动作的最小状态。 /// private static async Task InitializeRobotAsync(HttpClient client) { using var initResponse = await client.PostAsJsonAsync("/init_mpc_robt", new { server_ip = "127.0.0.1", port = 50001, robot_name = "FANUC_LR_Mate_200iD", robot_ip = "192.168.10.101", sim = true }); Assert.Equal(HttpStatusCode.OK, initResponse.StatusCode); using var initJson = await ReadJsonAsync(initResponse); Assert.Equal("init_Success", initJson.RootElement.GetProperty("message").GetString()); Assert.Equal(0, initJson.RootElement.GetProperty("returnCode").GetInt32()); } /// /// 统一把 HTTP 响应体解析成 JsonDocument,便于对旧接口的字段形状做精确断言。 /// private static async Task ReadJsonAsync(HttpResponseMessage response) { await using var responseStream = await response.Content.ReadAsStreamAsync(); return await JsonDocument.ParseAsync(responseStream); } }