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; /// /// 使用可控 fake runtime 启动测试宿主,避免集成测试访问真实 FANUC 控制器。 /// public sealed class FlyshotServerFactory : WebApplicationFactory { /// protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { services.RemoveAll(); services.AddSingleton(); }); } } /// /// 为 HTTP 集成测试提供不访问硬件的控制器运行时。 /// 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(); private double[] _pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]; /// public void ResetRobot(RobotProfile robot, string robotName) { _robot = robot; _connected = false; _enabled = false; _joints = new double[robot.DegreesOfFreedom]; } /// public void Connect(string robotIp) { _ = _robot ?? throw new InvalidOperationException("Robot has not been setup."); _connected = true; } /// public void Disconnect() { _connected = false; _enabled = false; } /// public void EnableRobot(int bufferSize) { if (!_connected) { throw new InvalidOperationException("Controller has not been connected."); } _enabled = true; } /// public void DisableRobot() => _enabled = false; /// public void StopMove() { } /// public double GetSpeedRatio() => _speedRatio; /// public void SetSpeedRatio(double ratio) => _speedRatio = ratio; /// public IReadOnlyList GetTcp() => _tcp.ToArray(); /// public void SetTcp(double x, double y, double z) => _tcp = [x, y, z]; /// public bool GetIo(int port, string ioType) => false; /// public void SetIo(int port, bool value, string ioType) { } /// public IReadOnlyList GetJointPositions() => _joints.ToArray(); /// public IReadOnlyList GetPose() => _pose.ToArray(); /// public ControllerStateSnapshot GetSnapshot() { return new ControllerStateSnapshot( DateTimeOffset.UtcNow, _connected ? "Connected" : "Disconnected", _enabled, false, _speedRatio, _joints, _pose, Array.Empty()); } /// public void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions) { if (!_enabled) { throw new InvalidOperationException("Robot has not been enabled."); } _joints = finalJointPositions.ToArray(); } /// public void ExecuteCartesianTrajectory(TrajectoryResult result, IReadOnlyList finalPose) { if (!_enabled) { throw new InvalidOperationException("Robot has not been enabled."); } _pose = [.. finalPose, 1.0]; } } public sealed class HealthEndpointTests(FlyshotServerFactory factory) : IClassFixture { [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()); } }