✨ feat: 实现 ControllerClient HTTP 兼容层及 FANUC 运行时
- 新增 Flyshot.ControllerClientCompat 兼容层模块 - 新增 Flyshot.Runtime.Fanuc 运行时模块 - 新增 LegacyHttpApiController 暴露 HTTP 兼容 API - 补充 RuntimeOrchestrationTests 等测试覆盖 - 补充 docs/ 兼容性需求与逆向工程文档 - 更新 Host 注册、配置及解决方案引用 变更概览: - Flyshot.ControllerClientCompat — 旧 ControllerClient 语义的 HTTP 适配 - Flyshot.Runtime.Fanuc — IControllerRuntime 的 FANUC 真机实现 - LegacyHttpApiController — HTTP API 兼容旧 SDK - docs/ — 兼容性需求与逆向工程分析文档 - 测试:RuntimeOrchestrationTests、LegacyHttpApiCompatibilityTests
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Flyshot.Server.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// 锁定标准 MVC 宿主需要提供的 Swagger 与 CORS 行为,避免后续回退成只够跑通的最小配置。
|
||||
/// </summary>
|
||||
public sealed class HostMvcConfigurationTests(FlyshotServerFactory factory) : IClassFixture<FlyshotServerFactory>
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证宿主会公开标准 Swagger JSON,并且文档标题和旧 HTTP 兼容路径都能从配置和控制器路由中导出。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task SwaggerDocument_ExposesConfiguredMetadataAndLegacyRoutes()
|
||||
{
|
||||
using var configuredFactory = CreateConfiguredFactory(factory);
|
||||
using var client = configuredFactory.CreateClient();
|
||||
|
||||
using var response = await client.GetAsync("/swagger/v1/swagger.json");
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
await using var responseStream = await response.Content.ReadAsStreamAsync();
|
||||
using var document = await JsonDocument.ParseAsync(responseStream);
|
||||
var root = document.RootElement;
|
||||
var paths = root.GetProperty("paths");
|
||||
|
||||
Assert.Equal("3.0.1", root.GetProperty("openapi").GetString());
|
||||
Assert.Equal("Flyshot Replacement HTTP API", root.GetProperty("info").GetProperty("title").GetString());
|
||||
// OpenAPI 文档会把部分带尾斜杠的路由规范化为无尾斜杠形式,这里同时接受两种键。
|
||||
Assert.True(paths.TryGetProperty("/robot_info/", out _) || paths.TryGetProperty("/robot_info", out _));
|
||||
Assert.True(paths.TryGetProperty("/healthz", out _) || paths.TryGetProperty("/healthz/", out _));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证宿主会按配置对旧 HTTP API 路由返回标准 CORS 预检响应。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CorsPreflight_ReturnsConfiguredAllowOriginHeaders()
|
||||
{
|
||||
using var configuredFactory = CreateConfiguredFactory(factory);
|
||||
using var client = configuredFactory.CreateClient();
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Options, "/robot_info/");
|
||||
request.Headers.Add("Origin", "http://localhost:3000");
|
||||
request.Headers.Add("Access-Control-Request-Method", "GET");
|
||||
request.Headers.Add("Access-Control-Request-Headers", "content-type");
|
||||
|
||||
using var response = await client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.True(response.Headers.TryGetValues("Access-Control-Allow-Origin", out var allowedOrigins));
|
||||
Assert.Contains("http://localhost:3000", allowedOrigins);
|
||||
Assert.True(response.Headers.TryGetValues("Access-Control-Allow-Methods", out var allowedMethods));
|
||||
Assert.Contains("GET", string.Join(",", allowedMethods));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为测试宿主注入标准 Swagger 与 CORS 配置,避免依赖开发机本地环境。
|
||||
/// </summary>
|
||||
private static WebApplicationFactory<Program> CreateConfiguredFactory(FlyshotServerFactory factory)
|
||||
{
|
||||
return factory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureAppConfiguration((_, configurationBuilder) =>
|
||||
{
|
||||
configurationBuilder.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["Swagger:Enabled"] = "true",
|
||||
["Swagger:DocumentName"] = "v1",
|
||||
["Swagger:Title"] = "Flyshot Replacement HTTP API",
|
||||
["Swagger:Version"] = "v1",
|
||||
["Cors:PolicyName"] = "LegacyHttpApi",
|
||||
["Cors:AllowedOrigins:0"] = "http://localhost:3000",
|
||||
["Cors:AllowedMethods:0"] = "GET",
|
||||
["Cors:AllowedMethods:1"] = "POST",
|
||||
["Cors:AllowedMethods:2"] = "OPTIONS",
|
||||
["Cors:AllowedHeaders:0"] = "content-type"
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user