xigau
This commit is contained in:
86
OrpaonVision.Core/Production/IRunLayerSessionRepository.cs
Normal file
86
OrpaonVision.Core/Production/IRunLayerSessionRepository.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using OrpaonVision.Core.Results;
|
||||
using OrpaonVision.Model.Production;
|
||||
|
||||
namespace OrpaonVision.Core.Production;
|
||||
|
||||
/// <summary>
|
||||
/// 运行层级会话仓储接口。
|
||||
/// </summary>
|
||||
public interface IRunLayerSessionRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建层级会话。
|
||||
/// </summary>
|
||||
/// <param name="layerSession">层级会话模型。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>创建结果。</returns>
|
||||
Task<Result<RunLayerSessionModel>> CreateAsync(RunLayerSessionModel layerSession, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 更新层级会话。
|
||||
/// </summary>
|
||||
/// <param name="layerSession">层级会话模型。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>更新结果。</returns>
|
||||
Task<Result<RunLayerSessionModel>> UpdateAsync(RunLayerSessionModel layerSession, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取层级会话。
|
||||
/// </summary>
|
||||
/// <param name="layerSessionId">层级会话ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>层级会话模型。</returns>
|
||||
Task<Result<RunLayerSessionModel?>> GetByIdAsync(Guid layerSessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据产品会话ID获取层级会话列表。
|
||||
/// </summary>
|
||||
/// <param name="sessionId">产品会话ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>层级会话列表。</returns>
|
||||
Task<Result<List<RunLayerSessionModel>>> GetBySessionIdAsync(Guid sessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据状态获取层级会话列表。
|
||||
/// </summary>
|
||||
/// <param name="status">状态。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>层级会话列表。</returns>
|
||||
Task<Result<List<RunLayerSessionModel>>> GetByStatusAsync(RunLayerSessionStatus status, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询层级会话。
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页索引。</param>
|
||||
/// <param name="pageSize">页大小。</param>
|
||||
/// <param name="productTypeCode">产品类型编码(可选)。</param>
|
||||
/// <param name="status">状态(可选)。</param>
|
||||
/// <param name="startTime">开始时间(可选)。</param>
|
||||
/// <param name="endTime">结束时间(可选)。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>分页结果。</returns>
|
||||
Task<Result<PagedResult<RunLayerSessionModel>>> GetPagedAsync(
|
||||
int pageIndex,
|
||||
int pageSize,
|
||||
string? productTypeCode = null,
|
||||
RunLayerSessionStatus? status = null,
|
||||
DateTime? startTime = null,
|
||||
DateTime? endTime = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 删除层级会话。
|
||||
/// </summary>
|
||||
/// <param name="layerSessionId">层级会话ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>删除结果。</returns>
|
||||
Task<Result<bool>> DeleteAsync(Guid layerSessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除层级会话。
|
||||
/// </summary>
|
||||
/// <param name="sessionIds">产品会话ID列表。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>删除结果。</returns>
|
||||
Task<Result<bool>> DeleteBySessionIdsAsync(List<Guid> sessionIds, CancellationToken cancellationToken = default);
|
||||
}
|
||||
102
OrpaonVision.Core/Production/IRunPartJudgmentRepository.cs
Normal file
102
OrpaonVision.Core/Production/IRunPartJudgmentRepository.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using OrpaonVision.Core.Results;
|
||||
using OrpaonVision.Model.Production;
|
||||
|
||||
namespace OrpaonVision.Core.Production;
|
||||
|
||||
/// <summary>
|
||||
/// 运行部件判定仓储接口。
|
||||
/// </summary>
|
||||
public interface IRunPartJudgmentRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建部件判定。
|
||||
/// </summary>
|
||||
/// <param name="judgment">部件判定模型。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>创建结果。</returns>
|
||||
Task<Result<RunPartJudgmentModel>> CreateAsync(RunPartJudgmentModel judgment, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 更新部件判定。
|
||||
/// </summary>
|
||||
/// <param name="judgment">部件判定模型。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>更新结果。</returns>
|
||||
Task<Result<RunPartJudgmentModel>> UpdateAsync(RunPartJudgmentModel judgment, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取部件判定。
|
||||
/// </summary>
|
||||
/// <param name="judgmentId">判定ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>部件判定模型。</returns>
|
||||
Task<Result<RunPartJudgmentModel?>> GetByIdAsync(Guid judgmentId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据层级会话ID获取部件判定列表。
|
||||
/// </summary>
|
||||
/// <param name="layerSessionId">层级会话ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>部件判定列表。</returns>
|
||||
Task<Result<List<RunPartJudgmentModel>>> GetByLayerSessionIdAsync(Guid layerSessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据产品会话ID获取部件判定列表。
|
||||
/// </summary>
|
||||
/// <param name="sessionId">产品会话ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>部件判定列表。</returns>
|
||||
Task<Result<List<RunPartJudgmentModel>>> GetBySessionIdAsync(Guid sessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据结果获取部件判定列表。
|
||||
/// </summary>
|
||||
/// <param name="result">判定结果。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>部件判定列表。</returns>
|
||||
Task<Result<List<RunPartJudgmentModel>>> GetByResultAsync(RunPartJudgmentResult result, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询部件判定。
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页索引。</param>
|
||||
/// <param name="pageSize">页大小。</param>
|
||||
/// <param name="productTypeCode">产品类型编码(可选)。</param>
|
||||
/// <param name="result">判定结果(可选)。</param>
|
||||
/// <param name="startTime">开始时间(可选)。</param>
|
||||
/// <param name="endTime">结束时间(可选)。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>分页结果。</returns>
|
||||
Task<Result<PagedResult<RunPartJudgmentModel>>> GetPagedAsync(
|
||||
int pageIndex,
|
||||
int pageSize,
|
||||
string? productTypeCode = null,
|
||||
RunPartJudgmentResult? result = null,
|
||||
DateTime? startTime = null,
|
||||
DateTime? endTime = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 删除部件判定。
|
||||
/// </summary>
|
||||
/// <param name="judgmentId">判定ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>删除结果。</returns>
|
||||
Task<Result<bool>> DeleteAsync(Guid judgmentId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除部件判定。
|
||||
/// </summary>
|
||||
/// <param name="sessionIds">产品会话ID列表。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>删除结果。</returns>
|
||||
Task<Result<bool>> DeleteBySessionIdsAsync(List<Guid> sessionIds, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除层级会话的部件判定。
|
||||
/// </summary>
|
||||
/// <param name="layerSessionIds">层级会话ID列表。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>删除结果。</returns>
|
||||
Task<Result<bool>> DeleteByLayerSessionIdsAsync(List<Guid> layerSessionIds, CancellationToken cancellationToken = default);
|
||||
}
|
||||
104
OrpaonVision.Core/Production/IRunStateTransitionLogRepository.cs
Normal file
104
OrpaonVision.Core/Production/IRunStateTransitionLogRepository.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using OrpaonVision.Core.Results;
|
||||
using OrpaonVision.Model.Production;
|
||||
|
||||
namespace OrpaonVision.Core.Production;
|
||||
|
||||
/// <summary>
|
||||
/// 运行状态转换日志仓储接口。
|
||||
/// </summary>
|
||||
public interface IRunStateTransitionLogRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建状态转换日志。
|
||||
/// </summary>
|
||||
/// <param name="log">状态转换日志模型。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>创建结果。</returns>
|
||||
Task<Result<RunStateTransitionLogModel>> CreateAsync(RunStateTransitionLogModel log, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取状态转换日志。
|
||||
/// </summary>
|
||||
/// <param name="logId">日志ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>状态转换日志模型。</returns>
|
||||
Task<Result<RunStateTransitionLogModel?>> GetByIdAsync(Guid logId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据产品会话ID获取状态转换日志列表。
|
||||
/// </summary>
|
||||
/// <param name="sessionId">产品会话ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>状态转换日志列表。</returns>
|
||||
Task<Result<List<RunStateTransitionLogModel>>> GetBySessionIdAsync(Guid sessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据层级会话ID获取状态转换日志列表。
|
||||
/// </summary>
|
||||
/// <param name="layerSessionId">层级会话ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>状态转换日志列表。</returns>
|
||||
Task<Result<List<RunStateTransitionLogModel>>> GetByLayerSessionIdAsync(Guid layerSessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据转换类型获取状态转换日志列表。
|
||||
/// </summary>
|
||||
/// <param name="transitionType">转换类型。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>状态转换日志列表。</returns>
|
||||
Task<Result<List<RunStateTransitionLogModel>>> GetByTransitionTypeAsync(RunStateTransitionType transitionType, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据人工干预标志获取状态转换日志列表。
|
||||
/// </summary>
|
||||
/// <param name="isManualIntervention">是否人工干预。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>状态转换日志列表。</returns>
|
||||
Task<Result<List<RunStateTransitionLogModel>>> GetByManualInterventionAsync(bool isManualIntervention, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询状态转换日志。
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页索引。</param>
|
||||
/// <param name="pageSize">页大小。</param>
|
||||
/// <param name="productTypeCode">产品类型编码(可选)。</param>
|
||||
/// <param name="transitionType">转换类型(可选)。</param>
|
||||
/// <param name="isManualIntervention">是否人工干预(可选)。</param>
|
||||
/// <param name="startTime">开始时间(可选)。</param>
|
||||
/// <param name="endTime">结束时间(可选)。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>分页结果。</returns>
|
||||
Task<Result<PagedResult<RunStateTransitionLogModel>>> GetPagedAsync(
|
||||
int pageIndex,
|
||||
int pageSize,
|
||||
string? productTypeCode = null,
|
||||
RunStateTransitionType? transitionType = null,
|
||||
bool? isManualIntervention = null,
|
||||
DateTime? startTime = null,
|
||||
DateTime? endTime = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 删除状态转换日志。
|
||||
/// </summary>
|
||||
/// <param name="logId">日志ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>删除结果。</returns>
|
||||
Task<Result<bool>> DeleteAsync(Guid logId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除状态转换日志。
|
||||
/// </summary>
|
||||
/// <param name="sessionIds">产品会话ID列表。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>删除结果。</returns>
|
||||
Task<Result<bool>> DeleteBySessionIdsAsync(List<Guid> sessionIds, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 清理指定日期之前的日志。
|
||||
/// </summary>
|
||||
/// <param name="beforeDate">指定日期。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>删除的记录数。</returns>
|
||||
Task<Result<int>> CleanupLogsBeforeAsync(DateTime beforeDate, CancellationToken cancellationToken = default);
|
||||
}
|
||||
174
OrpaonVision.Model/Production/RunLayerSessionModel.cs
Normal file
174
OrpaonVision.Model/Production/RunLayerSessionModel.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
namespace OrpaonVision.Model.Production;
|
||||
|
||||
/// <summary>
|
||||
/// 运行层级会话模型。
|
||||
/// </summary>
|
||||
public sealed class RunLayerSessionModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 层级会话ID。
|
||||
/// </summary>
|
||||
public Guid LayerSessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产品会话ID。
|
||||
/// </summary>
|
||||
public Guid SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 层级序号。
|
||||
/// </summary>
|
||||
public int LayerNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 层级名称。
|
||||
/// </summary>
|
||||
public string LayerName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 层级编码。
|
||||
/// </summary>
|
||||
public string LayerCode { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime StartedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime? EndedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 层级状态。
|
||||
/// </summary>
|
||||
public RunLayerSessionStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 检测到的部件数量。
|
||||
/// </summary>
|
||||
public int DetectedPartsCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 合格部件数量。
|
||||
/// </summary>
|
||||
public int QualifiedPartsCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NG部件数量。
|
||||
/// </summary>
|
||||
public int NgPartsCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理的图像数量。
|
||||
/// </summary>
|
||||
public int ProcessedImageCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 首张截图路径。
|
||||
/// </summary>
|
||||
public string? FirstScreenshotPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 末张截图路径。
|
||||
/// </summary>
|
||||
public string? LastScreenshotPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 层级结果。
|
||||
/// </summary>
|
||||
public RunLayerSessionResult Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NG原因描述。
|
||||
/// </summary>
|
||||
public string? NgReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理耗时(毫秒)。
|
||||
/// </summary>
|
||||
public long ProcessingTimeMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime UpdatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人。
|
||||
/// </summary>
|
||||
public string CreatedBy { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 更新人。
|
||||
/// </summary>
|
||||
public string UpdatedBy { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行层级会话状态。
|
||||
/// </summary>
|
||||
public enum RunLayerSessionStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 未开始。
|
||||
/// </summary>
|
||||
NotStarted = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 进行中。
|
||||
/// </summary>
|
||||
InProgress = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 已完成(OK)。
|
||||
/// </summary>
|
||||
CompletedOk = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 已完成(NG)。
|
||||
/// </summary>
|
||||
CompletedNg = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 已跳过。
|
||||
/// </summary>
|
||||
Skipped = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 异常终止。
|
||||
/// </summary>
|
||||
Error = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行层级会话结果。
|
||||
/// </summary>
|
||||
public enum RunLayerSessionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 待定。
|
||||
/// </summary>
|
||||
Pending = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 合格。
|
||||
/// </summary>
|
||||
Ok = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 不合格。
|
||||
/// </summary>
|
||||
Ng = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 跳过。
|
||||
/// </summary>
|
||||
Skipped = 3
|
||||
}
|
||||
229
OrpaonVision.Model/Production/RunPartJudgmentModel.cs
Normal file
229
OrpaonVision.Model/Production/RunPartJudgmentModel.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
namespace OrpaonVision.Model.Production;
|
||||
|
||||
/// <summary>
|
||||
/// 运行部件判定模型。
|
||||
/// </summary>
|
||||
public sealed class RunPartJudgmentModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 判定ID。
|
||||
/// </summary>
|
||||
public Guid JudgmentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产品会话ID。
|
||||
/// </summary>
|
||||
public Guid SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 层级会话ID。
|
||||
/// </summary>
|
||||
public Guid LayerSessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 部件序号(在层级中的序号)。
|
||||
/// </summary>
|
||||
public int PartSequence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 部件编码。
|
||||
/// </summary>
|
||||
public string PartCode { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 部件名称。
|
||||
/// </summary>
|
||||
public string PartName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 检测到的类别。
|
||||
/// </summary>
|
||||
public string DetectedClass { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 置信度。
|
||||
/// </summary>
|
||||
public decimal Confidence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 边界框X坐标。
|
||||
/// </summary>
|
||||
public float BoundingBoxX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 边界框Y坐标。
|
||||
/// </summary>
|
||||
public float BoundingBoxY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 边界框宽度。
|
||||
/// </summary>
|
||||
public float BoundingBoxWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 边界框高度。
|
||||
/// </summary>
|
||||
public float BoundingBoxHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际中心点X坐标。
|
||||
/// </summary>
|
||||
public float ActualCenterX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际中心点Y坐标。
|
||||
/// </summary>
|
||||
public float ActualCenterY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期望中心点X坐标。
|
||||
/// </summary>
|
||||
public float ExpectedCenterX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期望中心点Y坐标。
|
||||
/// </summary>
|
||||
public float ExpectedCenterY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 位置偏差(像素)。
|
||||
/// </summary>
|
||||
public float PositionDeviation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定状态。
|
||||
/// </summary>
|
||||
public RunPartJudgmentStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定结果。
|
||||
/// </summary>
|
||||
public RunPartJudgmentResult Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定规则ID。
|
||||
/// </summary>
|
||||
public Guid? RuleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定规则名称。
|
||||
/// </summary>
|
||||
public string? RuleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NG原因。
|
||||
/// </summary>
|
||||
public string? NgReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 截图路径。
|
||||
/// </summary>
|
||||
public string? ScreenshotPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime JudgedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 推理耗时(毫秒)。
|
||||
/// </summary>
|
||||
public long InferenceTimeMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime UpdatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人。
|
||||
/// </summary>
|
||||
public string CreatedBy { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 更新人。
|
||||
/// </summary>
|
||||
public string UpdatedBy { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行部件判定状态。
|
||||
/// </summary>
|
||||
public enum RunPartJudgmentStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 待检测。
|
||||
/// </summary>
|
||||
Pending = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 检测中。
|
||||
/// </summary>
|
||||
Detecting = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 已检测。
|
||||
/// </summary>
|
||||
Detected = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 判定中。
|
||||
/// </summary>
|
||||
Judging = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 已判定。
|
||||
/// </summary>
|
||||
Judged = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 异常。
|
||||
/// </summary>
|
||||
Error = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 违行部件判定结果。
|
||||
/// </summary>
|
||||
public enum RunPartJudgmentResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 待定。
|
||||
/// </summary>
|
||||
Pending = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 合格。
|
||||
/// </summary>
|
||||
Ok = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 不合格。
|
||||
/// </summary>
|
||||
Ng = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 缺失。
|
||||
/// </summary>
|
||||
Missing = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 位置错误。
|
||||
/// </summary>
|
||||
PositionError = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 禁装。
|
||||
/// </summary>
|
||||
Forbidden = 5,
|
||||
|
||||
/// <summary>
|
||||
/// 重复。
|
||||
/// </summary>
|
||||
Duplicate = 6
|
||||
}
|
||||
133
OrpaonVision.Model/Production/RunStateTransitionLogModel.cs
Normal file
133
OrpaonVision.Model/Production/RunStateTransitionLogModel.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
namespace OrpaonVision.Model.Production;
|
||||
|
||||
/// <summary>
|
||||
/// 运行状态转换日志模型。
|
||||
/// </summary>
|
||||
public sealed class RunStateTransitionLogModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志ID。
|
||||
/// </summary>
|
||||
public Guid LogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产品会话ID。
|
||||
/// </summary>
|
||||
public Guid SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 层级会话ID(可选)。
|
||||
/// </summary>
|
||||
public Guid? LayerSessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转换前状态。
|
||||
/// </summary>
|
||||
public string FromState { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 转换后状态。
|
||||
/// </summary>
|
||||
public string ToState { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件。
|
||||
/// </summary>
|
||||
public string TriggerEvent { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 转换原因。
|
||||
/// </summary>
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 转换类型。
|
||||
/// </summary>
|
||||
public RunStateTransitionType TransitionType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作者ID。
|
||||
/// </summary>
|
||||
public string? OperatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作者姓名。
|
||||
/// </summary>
|
||||
public string? OperatorName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否人工干预。
|
||||
/// </summary>
|
||||
public bool IsManualIntervention { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联数据(JSON格式)。
|
||||
/// </summary>
|
||||
public string? ContextJson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 截图路径(可选)。
|
||||
/// </summary>
|
||||
public string? ScreenshotPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转换时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime TransitionAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理耗时(毫秒)。
|
||||
/// </summary>
|
||||
public long ProcessingTimeMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息(如果有)。
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人。
|
||||
/// </summary>
|
||||
public string CreatedBy { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行状态转换类型。
|
||||
/// </summary>
|
||||
public enum RunStateTransitionType
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动转换。
|
||||
/// </summary>
|
||||
Automatic = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 人工干预。
|
||||
/// </summary>
|
||||
Manual = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 系统触发。
|
||||
/// </summary>
|
||||
System = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 错误恢复。
|
||||
/// </summary>
|
||||
ErrorRecovery = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 超时处理。
|
||||
/// </summary>
|
||||
Timeout = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 异常终止。
|
||||
/// </summary>
|
||||
Exception = 5
|
||||
}
|
||||
29
OrpaonVision.SiteApp.Tests/OrpaonVision.SiteApp.Tests.csproj
Normal file
29
OrpaonVision.SiteApp.Tests/OrpaonVision.SiteApp.Tests.csproj
Normal file
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.0.4" />
|
||||
<PackageReference Include="Moq" Version="4.20.69" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OrpaonVision.Core\OrpaonVision.Core.csproj" />
|
||||
<ProjectReference Include="..\OrpaonVision.Model\OrpaonVision.Model.csproj" />
|
||||
<ProjectReference Include="..\OrpaonVision.SiteApp\OrpaonVision.SiteApp.csproj" />
|
||||
<ProjectReference Include="..\OrpaonVision.ConfigApp\OrpaonVision.ConfigApp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,128 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using OrpaonVision.Core.Results;
|
||||
using OrpaonVision.SiteApp.Runtime.Contracts;
|
||||
using OrpaonVision.SiteApp.Runtime.Services;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Tests.Runtime.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 运行时状态机服务单元测试。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class RuntimeStateMachineServiceTests
|
||||
{
|
||||
private IRuntimeStateMachineService _stateMachine = null!;
|
||||
private Mock<ILogger<IRuntimeStateMachineService>> _loggerMock = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
_loggerMock = new Mock<ILogger<IRuntimeStateMachineService>>();
|
||||
|
||||
// 使用简单状态机实现进行测试
|
||||
_stateMachine = new SimpleRuntimeStateMachineService(_loggerMock.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetSnapshot_InitialState_ReturnsCorrectSnapshot()
|
||||
{
|
||||
// Act
|
||||
var snapshot = _stateMachine.GetSnapshot();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(snapshot);
|
||||
Assert.AreEqual(0, snapshot.CurrentLayer);
|
||||
Assert.AreEqual(0, snapshot.TotalLayers);
|
||||
Assert.IsTrue(snapshot.StateText.Contains("Idle") || snapshot.StateText.Contains("初始"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MoveToNextLayer_FromInitialState_ReturnsSuccess()
|
||||
{
|
||||
// Act
|
||||
var result = _stateMachine.MoveToNextLayer();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Succeeded);
|
||||
var snapshot = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(1, snapshot.CurrentLayer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MoveToNextLayer_SequentialCalls_ReturnsSuccess()
|
||||
{
|
||||
// Act & Assert - 第一层
|
||||
var result1 = _stateMachine.MoveToNextLayer();
|
||||
Assert.IsTrue(result1.Succeeded);
|
||||
var snapshot1 = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(1, snapshot1.CurrentLayer);
|
||||
|
||||
// Act & Assert - 第二层
|
||||
var result2 = _stateMachine.MoveToNextLayer();
|
||||
Assert.IsTrue(result2.Succeeded);
|
||||
var snapshot2 = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(2, snapshot2.CurrentLayer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_AfterProgress_ReturnsToInitialState()
|
||||
{
|
||||
// Arrange - 推进几层
|
||||
_stateMachine.MoveToNextLayer();
|
||||
_stateMachine.MoveToNextLayer();
|
||||
var snapshotBefore = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(2, snapshotBefore.CurrentLayer);
|
||||
|
||||
// Act
|
||||
_stateMachine.Reset();
|
||||
|
||||
// Assert
|
||||
var snapshotAfter = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(0, snapshotAfter.CurrentLayer);
|
||||
Assert.AreEqual(0, snapshotAfter.TotalLayers);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StateTransitionFlow_CompleteWorkflow_ReturnsExpectedStates()
|
||||
{
|
||||
// Arrange & Act - 初始状态
|
||||
var initialSnapshot = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(0, initialSnapshot.CurrentLayer);
|
||||
|
||||
// Act & Assert - 第一层
|
||||
var result1 = _stateMachine.MoveToNextLayer();
|
||||
Assert.IsTrue(result1.Succeeded);
|
||||
var snapshot1 = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(1, snapshot1.CurrentLayer);
|
||||
|
||||
// Act & Assert - 第二层
|
||||
var result2 = _stateMachine.MoveToNextLayer();
|
||||
Assert.IsTrue(result2.Succeeded);
|
||||
var snapshot2 = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(2, snapshot2.CurrentLayer);
|
||||
|
||||
// Act & Assert - 重置
|
||||
_stateMachine.Reset();
|
||||
var finalSnapshot = _stateMachine.GetSnapshot();
|
||||
Assert.AreEqual(0, finalSnapshot.CurrentLayer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetSnapshot_AfterMultipleTransitions_ReturnsCorrectCurrentState()
|
||||
{
|
||||
// Arrange
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
_stateMachine.MoveToNextLayer();
|
||||
}
|
||||
|
||||
// Act
|
||||
var snapshot = _stateMachine.GetSnapshot();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(5, snapshot.CurrentLayer);
|
||||
Assert.IsNotNull(snapshot.StateText);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(snapshot.StateText));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using OrpaonVision.Core.Results;
|
||||
using OrpaonVision.Core.Training;
|
||||
using OrpaonVision.Core.Training.Contracts;
|
||||
using OrpaonVision.Core.Training.Contracts.Commands;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Tests.Training;
|
||||
|
||||
/// <summary>
|
||||
/// 模型包导入激活单元测试。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ModelPackageImportActivationTests
|
||||
{
|
||||
private Mock<IModelPackageAppService> _modelPackageServiceMock = null!;
|
||||
private Mock<ILogger<IModelPackageAppService>> _loggerMock = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
_modelPackageServiceMock = new Mock<IModelPackageAppService>();
|
||||
_loggerMock = new Mock<ILogger<IModelPackageAppService>>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ImportPackageAsync_ValidPackage_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var packagePath = "test_package.ovpkg";
|
||||
var expectedResult = new ModelPackageImportResultDto
|
||||
{
|
||||
ModelPackageId = Guid.NewGuid(),
|
||||
Status = ModelPackageImportStatus.Imported,
|
||||
Message = "导入成功",
|
||||
ImportedAtUtc = DateTime.UtcNow,
|
||||
ImportedBy = "TestUser"
|
||||
};
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ImportPackageAsync(packagePath, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(Result<ModelPackageImportResultDto>.Success(expectedResult));
|
||||
|
||||
// Act
|
||||
var result = await _modelPackageServiceMock.Object.ImportPackageAsync(packagePath);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Succeeded);
|
||||
Assert.IsNotNull(result.Data);
|
||||
Assert.AreEqual(ModelPackageImportStatus.Imported, result.Data.Status);
|
||||
Assert.AreEqual("导入成功", result.Data.Message);
|
||||
Assert.AreEqual("TestUser", result.Data.ImportedBy);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ImportPackageAsync_InvalidPackage_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var packagePath = "invalid_package.ovpkg";
|
||||
var expectedError = Result<ModelPackageImportResultDto>.Fail("INVALID_PACKAGE", "模型包格式无效");
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ImportPackageAsync(packagePath, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(expectedError);
|
||||
|
||||
// Act
|
||||
var result = await _modelPackageServiceMock.Object.ImportPackageAsync(packagePath);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result.Succeeded);
|
||||
Assert.AreEqual("INVALID_PACKAGE", result.Code);
|
||||
Assert.AreEqual("模型包格式无效", result.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ImportPackageAsync_MissingFile_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var packagePath = "missing_package.ovpkg";
|
||||
var expectedError = Result<ModelPackageImportResultDto>.Fail("FILE_NOT_FOUND", "模型包文件不存在");
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ImportPackageAsync(packagePath, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(expectedError);
|
||||
|
||||
// Act
|
||||
var result = await _modelPackageServiceMock.Object.ImportPackageAsync(packagePath);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result.Succeeded);
|
||||
Assert.AreEqual("FILE_NOT_FOUND", result.Code);
|
||||
Assert.AreEqual("模型包文件不存在", result.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ActivatePackageAsync_ValidPackage_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var packageId = Guid.NewGuid();
|
||||
var expectedResult = new ModelPackageActivationResultDto
|
||||
{
|
||||
ModelPackageId = packageId,
|
||||
Status = ModelPackageActivationStatus.Activated,
|
||||
Message = "激活成功",
|
||||
ActivatedAtUtc = DateTime.UtcNow,
|
||||
ActivatedBy = "TestUser"
|
||||
};
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ActivatePackageAsync(packageId, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(Result<ModelPackageActivationResultDto>.Success(expectedResult));
|
||||
|
||||
// Act
|
||||
var result = await _modelPackageServiceMock.Object.ActivatePackageAsync(packageId);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Succeeded);
|
||||
Assert.IsNotNull(result.Data);
|
||||
Assert.AreEqual(ModelPackageActivationStatus.Activated, result.Data.Status);
|
||||
Assert.AreEqual("激活成功", result.Data.Message);
|
||||
Assert.AreEqual("TestUser", result.Data.ActivatedBy);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ActivatePackageAsync_PackageNotFound_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var packageId = Guid.NewGuid();
|
||||
var expectedError = Result<ModelPackageActivationResultDto>.Fail("PACKAGE_NOT_FOUND", "模型包不存在");
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ActivatePackageAsync(packageId, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(expectedError);
|
||||
|
||||
// Act
|
||||
var result = await _modelPackageServiceMock.Object.ActivatePackageAsync(packageId);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result.Succeeded);
|
||||
Assert.AreEqual("PACKAGE_NOT_FOUND", result.Code);
|
||||
Assert.AreEqual("模型包不存在", result.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ActivatePackageAsync_AlreadyActivated_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var packageId = Guid.NewGuid();
|
||||
var expectedError = Result<ModelPackageActivationResultDto>.Fail("ALREADY_ACTIVATED", "模型包已激活");
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ActivatePackageAsync(packageId, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(expectedError);
|
||||
|
||||
// Act
|
||||
var result = await _modelPackageServiceMock.Object.ActivatePackageAsync(packageId);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result.Succeeded);
|
||||
Assert.AreEqual("ALREADY_ACTIVATED", result.Code);
|
||||
Assert.AreEqual("模型包已激活", result.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ImportAndActivate_CompleteWorkflow_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var packagePath = "test_package.ovpkg";
|
||||
var packageId = Guid.NewGuid();
|
||||
|
||||
var importResult = new ModelPackageImportResultDto
|
||||
{
|
||||
ModelPackageId = packageId,
|
||||
Status = ModelPackageImportStatus.Imported,
|
||||
Message = "导入成功",
|
||||
ImportedAtUtc = DateTime.UtcNow,
|
||||
ImportedBy = "TestUser"
|
||||
};
|
||||
|
||||
var activationResult = new ModelPackageActivationResultDto
|
||||
{
|
||||
ModelPackageId = packageId,
|
||||
Status = ModelPackageActivationStatus.Activated,
|
||||
Message = "激活成功",
|
||||
ActivatedAtUtc = DateTime.UtcNow,
|
||||
ActivatedBy = "TestUser"
|
||||
};
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ImportPackageAsync(packagePath, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(Result<ModelPackageImportResultDto>.Success(importResult));
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ActivatePackageAsync(packageId, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(Result<ModelPackageActivationResultDto>.Success(activationResult));
|
||||
|
||||
// Act - Import
|
||||
var importStep = await _modelPackageServiceMock.Object.ImportPackageAsync(packagePath);
|
||||
|
||||
// Assert - Import
|
||||
Assert.IsTrue(importStep.Succeeded);
|
||||
Assert.AreEqual(packageId, importStep.Data.ModelPackageId);
|
||||
|
||||
// Act - Activate
|
||||
var activationStep = await _modelPackageServiceMock.Object.ActivatePackageAsync(packageId);
|
||||
|
||||
// Assert - Activate
|
||||
Assert.IsTrue(activationStep.Succeeded);
|
||||
Assert.AreEqual(packageId, activationStep.Data.ModelPackageId);
|
||||
Assert.AreEqual(ModelPackageActivationStatus.Activated, activationStep.Data.Status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ImportAndActivate_ImportFailure_StopsWorkflow()
|
||||
{
|
||||
// Arrange
|
||||
var packagePath = "invalid_package.ovpkg";
|
||||
var packageId = Guid.NewGuid();
|
||||
|
||||
var importError = Result<ModelPackageImportResultDto>.Fail("INVALID_FORMAT", "模型包格式无效");
|
||||
|
||||
_modelPackageServiceMock
|
||||
.Setup(x => x.ImportPackageAsync(packagePath, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(importError);
|
||||
|
||||
// Act
|
||||
var importStep = await _modelPackageServiceMock.Object.ImportPackageAsync(packagePath);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(importStep.Succeeded);
|
||||
Assert.AreEqual("INVALID_FORMAT", importStep.Code);
|
||||
|
||||
// Verify that activation was not attempted
|
||||
_modelPackageServiceMock.Verify(
|
||||
x => x.ActivatePackageAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using OrpaonVision.Core.Results;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.Contracts;
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user