Files
yunxiao.zhu 5193f537b0 ✨ feat(planning): 添加离线飞拍规划能力
* 固化离线飞拍轨迹加载、保存和规划结果。

* 真机初始化失败时保留模型加载后的离线规划能力。

* 更新状态接口、调试页和回归测试覆盖。
2026-06-24 11:40:07 +08:00

165 lines
4.6 KiB
C#

using System.Net;
using System.Text.Json;
using Flyshot.Core.Domain;
using Flyshot.Runtime.Common;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Flyshot.Server.IntegrationTests;
/// <summary>
/// 使用可控 fake runtime 启动测试宿主,避免集成测试访问真实 FANUC 控制器。
/// </summary>
public sealed class FlyshotServerFactory : WebApplicationFactory<Program>
{
/// <inheritdoc />
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.RemoveAll<IControllerRuntime>();
services.AddSingleton<IControllerRuntime, IntegrationFakeControllerRuntime>();
});
}
}
/// <summary>
/// 为 HTTP 集成测试提供不访问硬件的控制器运行时。
/// </summary>
internal sealed class IntegrationFakeControllerRuntime : IControllerRuntime
{
private RobotProfile? _robot;
private bool _connected;
private bool _enabled;
private double _speedRatio = 1.0;
private double[] _tcp = [0.0, 0.0, 0.0];
private double[] _joints = Array.Empty<double>();
private double[] _pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0];
/// <inheritdoc />
public void ResetRobot(RobotProfile robot, string robotName)
{
_robot = robot;
_connected = false;
_enabled = false;
_joints = new double[robot.DegreesOfFreedom];
}
/// <inheritdoc />
public void Connect(string robotIp)
{
_ = _robot ?? throw new InvalidOperationException("Robot has not been setup.");
_connected = true;
}
/// <inheritdoc />
public void Disconnect()
{
_connected = false;
_enabled = false;
}
/// <inheritdoc />
public void EnableRobot(int bufferSize)
{
if (!_connected)
{
throw new InvalidOperationException("Controller has not been connected.");
}
_enabled = true;
}
/// <inheritdoc />
public void DisableRobot() => _enabled = false;
/// <inheritdoc />
public void StopMove()
{
}
/// <inheritdoc />
public double GetSpeedRatio() => _speedRatio;
/// <inheritdoc />
public void SetSpeedRatio(double ratio) => _speedRatio = ratio;
/// <inheritdoc />
public IReadOnlyList<double> GetTcp() => _tcp.ToArray();
/// <inheritdoc />
public void SetTcp(double x, double y, double z) => _tcp = [x, y, z];
/// <inheritdoc />
public bool GetIo(int port, string ioType) => false;
/// <inheritdoc />
public void SetIo(int port, bool value, string ioType)
{
}
/// <inheritdoc />
public IReadOnlyList<double> GetJointPositions() => _joints.ToArray();
/// <inheritdoc />
public IReadOnlyList<double> GetPose() => _pose.ToArray();
/// <inheritdoc />
public ControllerStateSnapshot GetSnapshot()
{
return new ControllerStateSnapshot(
DateTimeOffset.UtcNow,
_connected ? "Connected" : "Disconnected",
_enabled,
false,
_speedRatio,
_joints,
_pose,
Array.Empty<RuntimeAlarm>());
}
/// <inheritdoc />
public void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList<double> finalJointPositions)
{
if (!_enabled)
{
throw new InvalidOperationException("Robot has not been enabled.");
}
_joints = finalJointPositions.ToArray();
}
/// <inheritdoc />
public void ExecuteCartesianTrajectory(TrajectoryResult result, IReadOnlyList<double> finalPose)
{
if (!_enabled)
{
throw new InvalidOperationException("Robot has not been enabled.");
}
_pose = [.. finalPose, 1.0];
}
}
public sealed class HealthEndpointTests(FlyshotServerFactory factory) : IClassFixture<FlyshotServerFactory>
{
[Fact]
public async Task GetHealthz_ReturnsOkPayload()
{
using var client = factory.CreateClient();
using var response = await client.GetAsync("/healthz");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
await using var responseStream = await response.Content.ReadAsStreamAsync();
using var jsonDocument = await JsonDocument.ParseAsync(responseStream);
var root = jsonDocument.RootElement;
Assert.Equal("ok", root.GetProperty("status").GetString());
Assert.Equal("flyshot-server-host", root.GetProperty("service").GetString());
}
}