NavisworksTransport/UnitTests/Integration/NavisworksTestAutomationClient.cs
tian 8f477c0e1c feat: 集成测试新增动画逐帧状态验证(P0 T04)
- 服务端新增 probe-animation-frames 端点:生成动画后 SeekToProgress 到多个进度点,返回物体跟踪位置/yaw
- 新增 AnimationFrameProbeAutomationTests:
  - Ground:起点/终点 XZ 匹配路径点、高度恒定(平面链)、中间帧在起终点之间
  - Hoisting:吊装剖面(起吊地面→悬挂升高→下降落地)+ 水平平移
- 集成测试 11/11 通过(P0 高风险区全部完成:真实物体 + 逐帧状态)
2026-08-04 17:11:57 +08:00

198 lines
7.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Globalization;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
namespace NavisworksTransport.UnitTests.Integration
{
internal sealed class NavisworksTestAutomationClient : IDisposable
{
private readonly HttpClient _httpClient;
public NavisworksTestAutomationClient()
{
_httpClient = new HttpClient
{
BaseAddress = new Uri("http://127.0.0.1:18777"),
Timeout = TimeSpan.FromSeconds(20)
};
}
public async Task EnsureServiceReadyAsync(TimeSpan timeout)
{
DateTime deadlineUtc = DateTime.UtcNow.Add(timeout);
Exception lastError = null;
while (DateTime.UtcNow < deadlineUtc)
{
try
{
JObject pingResponse = await GetJsonAsync("/api/test/ping").ConfigureAwait(false);
if (pingResponse.Value<bool?>("ok") == true)
{
return;
}
}
catch (Exception ex)
{
lastError = ex;
}
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
Assert.Fail(
"Navisworks 测试服务未就绪。请先用 start-navisworks.bat 启动 Navisworks并确保插件面板已加载。最后错误: {0}",
lastError?.Message ?? "unknown");
}
public async Task<JObject> RunVirtualCollisionTestAsync(string pathType, int timeoutSeconds)
{
return await RunVirtualCollisionTestCoreAsync(null, pathType, timeoutSeconds).ConfigureAwait(false);
}
public async Task<JObject> RunVirtualCollisionTestAsync(string routeName, string pathType, int timeoutSeconds)
{
return await RunVirtualCollisionTestCoreAsync(routeName, pathType, timeoutSeconds).ConfigureAwait(false);
}
/// <summary>
/// 运行碰撞测试(虚拟或真实物体)。
/// useVirtualObject=false 时必须提供 animatedObjectPathModelPath 定位,格式如 0/0/769/0
/// </summary>
public async Task<JObject> RunCollisionTestAsync(
string pathType,
int timeoutSeconds,
string routeName = null,
bool useVirtualObject = true,
string animatedObjectPath = null)
{
var queryParts = new System.Collections.Generic.List<string>
{
"pathType=" + Uri.EscapeDataString(pathType),
"timeoutSeconds=" + timeoutSeconds.ToString(CultureInfo.InvariantCulture),
"useVirtualObject=" + (useVirtualObject ? "true" : "false")
};
if (!string.IsNullOrWhiteSpace(routeName))
{
queryParts.Add("routeName=" + Uri.EscapeDataString(routeName));
}
if (!string.IsNullOrWhiteSpace(animatedObjectPath))
{
queryParts.Add("animatedObjectPath=" + Uri.EscapeDataString(animatedObjectPath));
}
string requestUri = "/api/test/run-virtual-collision-test?" + string.Join("&", queryParts);
return await PostJsonAsync(requestUri).ConfigureAwait(false);
}
private async Task<JObject> RunVirtualCollisionTestCoreAsync(string routeName, string pathType, int timeoutSeconds)
{
string requestUri = string.Format(
CultureInfo.InvariantCulture,
"/api/test/run-virtual-collision-test?routeName={0}&pathType={1}&timeoutSeconds={2}",
Uri.EscapeDataString(routeName ?? string.Empty),
Uri.EscapeDataString(pathType),
timeoutSeconds);
return await PostJsonAsync(requestUri).ConfigureAwait(false);
}
public async Task<JObject> ImportRouteFileAsync(string filePath, bool overwrite = false)
{
string requestUri = string.Format(
"/api/test/import-route-file?path={0}&overwrite={1}",
Uri.EscapeDataString(filePath),
overwrite ? "true" : "false");
return await PostJsonAsync(requestUri).ConfigureAwait(false);
}
public async Task<JObject> SelectRouteAsync(string routeName)
{
string requestUri = string.Format(
"/api/test/select-route?name={0}",
Uri.EscapeDataString(routeName));
return await PostJsonAsync(requestUri).ConfigureAwait(false);
}
public async Task<JObject> AnalyzeAutoPathGridAsync(string pathType)
{
string requestUri = string.Format(
"/api/test/analyze-auto-path-grid?pathType={0}",
Uri.EscapeDataString(pathType));
return await PostJsonAsync(requestUri).ConfigureAwait(false);
}
public async Task<JObject> RunAutoPathAsync(
string pathType,
string routeName,
double objectLengthInMeters,
double objectWidthInMeters,
double safetyMarginInMeters,
double gridSizeInMeters)
{
string requestUri = string.Format(
CultureInfo.InvariantCulture,
"/api/test/run-auto-path?pathType={0}&routeName={1}&objectLengthInMeters={2}&objectWidthInMeters={3}&safetyMarginInMeters={4}&gridSizeInMeters={5}",
Uri.EscapeDataString(pathType),
Uri.EscapeDataString(routeName),
objectLengthInMeters,
objectWidthInMeters,
safetyMarginInMeters,
gridSizeInMeters);
return await PostJsonAsync(requestUri).ConfigureAwait(false);
}
private async Task<JObject> GetJsonAsync(string requestUri)
{
using (HttpResponseMessage response = await _httpClient.GetAsync(requestUri).ConfigureAwait(false))
{
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return JObject.Parse(content);
}
}
/// <summary>
/// 动画逐帧状态探针:生成动画后 seek 到指定进度点,返回各点物体跟踪位置/yaw。
/// </summary>
public async Task<JObject> ProbeAnimationFramesAsync(
string pathType,
int timeoutSeconds,
string probeProgress = "0,0.25,0.5,0.75,1.0")
{
string requestUri = string.Format(
CultureInfo.InvariantCulture,
"/api/test/probe-animation-frames?pathType={0}&probeProgress={1}&timeoutSeconds={2}",
Uri.EscapeDataString(pathType),
Uri.EscapeDataString(probeProgress),
timeoutSeconds);
return await PostJsonAsync(requestUri).ConfigureAwait(false);
}
private async Task<JObject> PostJsonAsync(string requestUri)
{
using (HttpResponseMessage response = await _httpClient.PostAsync(requestUri, new StringContent(string.Empty)).ConfigureAwait(false))
{
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return JObject.Parse(content);
}
}
public void Dispose()
{
_httpClient.Dispose();
}
}
}