From b5a87a1bfb1bc4e49a1b3fc8b76b25a93bd0bf5a Mon Sep 17 00:00:00 2001
From: root <295172551@qq.com>
Date: Sun, 24 Aug 2025 13:15:10 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84IRanGainControlHandle?=
=?UTF-8?q?r=E6=8E=A5=E5=8F=A3=E5=B9=B6=E6=B7=BB=E5=8A=A0RAN=20API?=
=?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=99=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 重构IRanGainControlHandler接口,移除RanIPEndPoint参数,统一从ICellularNetworkContext获取端点信息
- 修复SetTxGainCommand构造函数,添加参数验证
- 创建SetAllTxGainCommand和SetAllTxGainCommandHandler,支持批量设置发送增益
- 创建RanAPIController,提供统一的RAN API访问接口
- 优化依赖注入配置,统一使用工厂模式
---
.../CreateScenarioTestCaseCommand.cs | 53 +++
.../CreateScenarioTestCaseCommandHandler.cs | 131 +++++++
.../CreateScenarioTestCaseResponse.cs | 37 ++
.../GetScenarioTestCasesQuery.cs | 27 ++
.../GetScenarioTestCasesQueryHandler.cs | 98 +++++
.../GetScenarioTestCasesResponse.cs | 83 +++++
.../GetFormTypeStepTypeMappingQuery.cs | 12 +
.../GetFormTypeStepTypeMappingQueryHandler.cs | 39 ++
.../GetFormTypeStepTypeMappingResponse.cs | 33 ++
.../CreateTestScenarioCommand.cs | 36 ++
.../CreateTestScenarioCommandHandler.cs | 133 +++++++
.../CreateTestScenarioResponse.cs | 47 +++
.../DeleteTestScenarioCommand.cs | 17 +
.../DeleteTestScenarioCommandHandler.cs | 77 ++++
.../UpdateTestScenarioCommand.cs | 28 ++
.../UpdateTestScenarioCommandHandler.cs | 92 +++++
.../UpdateTestScenarioResponse.cs | 47 +++
.../GetTestScenarioByIdQuery.cs | 22 ++
.../GetTestScenarioByIdQueryHandler.cs | 99 +++++
.../GetTestScenarioByIdResponse.cs | 108 ++++++
.../GetTestScenarios/GetTestScenariosQuery.cs | 40 ++
.../GetTestScenariosQueryHandler.cs | 111 ++++++
.../GetTestScenariosResponse.cs | 88 +++++
src/X1.Domain/Common/TestFlowTypeConverter.cs | 231 ++++++++++++
.../Entities/TestCase/TestFlowType.cs | 16 +-
.../Entities/TestCase/TestScenario.cs | 28 ++
.../TestCase/IScenarioTestCaseRepository.cs | 91 +++++
.../TestCase/ITestScenarioRepository.cs | 62 ++++
.../TestCase/ScenarioTestCaseConfiguration.cs | 82 +++++
.../TestCase/TestScenarioConfiguration.cs | 74 ++++
src/X1.Infrastructure/Context/AppDbContext.cs | 10 +
src/X1.Infrastructure/DependencyInjection.cs | 2 +
.../TestCase/ScenarioTestCaseRepository.cs | 135 +++++++
.../TestCase/TestScenarioRepository.cs | 93 +++++
.../Controllers/TestCaseFlowController.cs | 24 ++
src/modify.md | 346 ++++++++++++++++++
36 files changed, 2649 insertions(+), 3 deletions(-)
create mode 100644 src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommand.cs
create mode 100644 src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommandHandler.cs
create mode 100644 src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseResponse.cs
create mode 100644 src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQuery.cs
create mode 100644 src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQueryHandler.cs
create mode 100644 src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesResponse.cs
create mode 100644 src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQuery.cs
create mode 100644 src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQueryHandler.cs
create mode 100644 src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingResponse.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommand.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommandHandler.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommand.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommandHandler.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommand.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommandHandler.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioResponse.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQuery.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQueryHandler.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQuery.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQueryHandler.cs
create mode 100644 src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs
create mode 100644 src/X1.Domain/Common/TestFlowTypeConverter.cs
create mode 100644 src/X1.Domain/Repositories/TestCase/IScenarioTestCaseRepository.cs
create mode 100644 src/X1.Domain/Repositories/TestCase/ITestScenarioRepository.cs
create mode 100644 src/X1.Infrastructure/Configurations/TestCase/ScenarioTestCaseConfiguration.cs
create mode 100644 src/X1.Infrastructure/Configurations/TestCase/TestScenarioConfiguration.cs
create mode 100644 src/X1.Infrastructure/Repositories/TestCase/ScenarioTestCaseRepository.cs
create mode 100644 src/X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs
diff --git a/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommand.cs b/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommand.cs
new file mode 100644
index 0000000..6543238
--- /dev/null
+++ b/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommand.cs
@@ -0,0 +1,53 @@
+using X1.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace X1.Application.Features.ScenarioTestCases.Commands.CreateScenarioTestCase;
+
+///
+/// 创建场景测试用例命令
+///
+public class CreateScenarioTestCaseCommand : IRequest>
+{
+ ///
+ /// 场景ID
+ ///
+ [Required(ErrorMessage = "场景ID不能为空")]
+ public string ScenarioId { get; set; } = null!;
+
+ ///
+ /// 测试用例列表
+ ///
+ [Required(ErrorMessage = "测试用例列表不能为空")]
+ [MinLength(1, ErrorMessage = "至少需要包含一个测试用例")]
+ public List TestCases { get; set; } = new();
+}
+
+///
+/// 场景测试用例项
+///
+public class ScenarioTestCaseItem
+{
+ ///
+ /// 测试用例流程ID
+ ///
+ [Required(ErrorMessage = "测试用例流程ID不能为空")]
+ public string TestCaseFlowId { get; set; } = null!;
+
+ ///
+ /// 执行顺序
+ ///
+ [Range(0, int.MaxValue, ErrorMessage = "执行顺序必须大于等于0")]
+ public int ExecutionOrder { get; set; } = 0;
+
+ ///
+ /// 循环次数
+ ///
+ [Range(1, int.MaxValue, ErrorMessage = "循环次数必须大于0")]
+ public int LoopCount { get; set; } = 1;
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; } = true;
+}
diff --git a/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommandHandler.cs b/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommandHandler.cs
new file mode 100644
index 0000000..4f95cc1
--- /dev/null
+++ b/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommandHandler.cs
@@ -0,0 +1,131 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Common;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.TestCase;
+using X1.Domain.Repositories.Base;
+using X1.Domain.Services;
+
+namespace X1.Application.Features.ScenarioTestCases.Commands.CreateScenarioTestCase;
+
+///
+/// 创建场景测试用例命令处理器
+///
+public class CreateScenarioTestCaseCommandHandler : IRequestHandler>
+{
+ private readonly IScenarioTestCaseRepository _scenarioTestCaseRepository;
+ private readonly ITestScenarioRepository _testScenarioRepository;
+ private readonly ILogger _logger;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ICurrentUserService _currentUserService;
+
+ ///
+ /// 初始化命令处理器
+ ///
+ public CreateScenarioTestCaseCommandHandler(
+ IScenarioTestCaseRepository scenarioTestCaseRepository,
+ ITestScenarioRepository testScenarioRepository,
+ ILogger logger,
+ IUnitOfWork unitOfWork,
+ ICurrentUserService currentUserService)
+ {
+ _scenarioTestCaseRepository = scenarioTestCaseRepository;
+ _testScenarioRepository = testScenarioRepository;
+ _logger = logger;
+ _unitOfWork = unitOfWork;
+ _currentUserService = currentUserService;
+ }
+
+ ///
+ /// 处理创建场景测试用例命令
+ ///
+ public async Task> Handle(CreateScenarioTestCaseCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始创建场景测试用例,场景ID: {ScenarioId}, 测试用例数量: {TestCaseCount}",
+ request.ScenarioId, request.TestCases.Count);
+
+ // 获取当前用户ID
+ var currentUserId = _currentUserService.GetCurrentUserId();
+ if (string.IsNullOrEmpty(currentUserId))
+ {
+ _logger.LogError("无法获取当前用户ID,用户可能未认证");
+ return OperationResult.CreateFailure("用户未认证,无法创建场景测试用例");
+ }
+
+ // 验证场景是否存在
+ var scenario = await _testScenarioRepository.GetByIdAsync(request.ScenarioId, cancellationToken:cancellationToken);
+ if (scenario == null)
+ {
+ _logger.LogWarning("测试场景不存在,场景ID: {ScenarioId}", request.ScenarioId);
+ return OperationResult.CreateFailure($"测试场景不存在,场景ID: {request.ScenarioId}");
+ }
+
+ var response = new CreateScenarioTestCaseResponse
+ {
+ TotalCount = request.TestCases.Count
+ };
+
+ // 批量检查场景中已存在的测试用例流程ID
+ var testCaseFlowIds = request.TestCases.Select(x => x.TestCaseFlowId).ToList();
+ var existingTestCaseFlowIds = await _scenarioTestCaseRepository.GetExistingTestCaseFlowIdsAsync(
+ request.ScenarioId, testCaseFlowIds, cancellationToken);
+
+ // 批量创建测试用例
+ foreach (var testCase in request.TestCases)
+ {
+ try
+ {
+ // 检查场景中是否已存在指定的测试用例
+ if (existingTestCaseFlowIds.Contains(testCase.TestCaseFlowId))
+ {
+ var errorMessage = $"场景中已存在指定的测试用例,测试用例流程ID: {testCase.TestCaseFlowId}";
+ response.ErrorMessages.Add(errorMessage);
+ response.FailureCount++;
+ _logger.LogWarning(errorMessage);
+ continue;
+ }
+
+ // 创建场景测试用例实体
+ var scenarioTestCase = ScenarioTestCase.Create(
+ scenarioId: request.ScenarioId,
+ testCaseFlowId: testCase.TestCaseFlowId,
+ executionOrder: testCase.ExecutionOrder,
+ loopCount: testCase.LoopCount,
+ isEnabled: testCase.IsEnabled,
+ createdBy: currentUserId);
+
+ // 保存场景测试用例
+ await _scenarioTestCaseRepository.AddAsync(scenarioTestCase, cancellationToken);
+
+ response.SuccessIds.Add(scenarioTestCase.Id);
+ response.SuccessCount++;
+
+ _logger.LogDebug("成功创建场景测试用例,场景测试用例ID: {ScenarioTestCaseId}, 测试用例流程ID: {TestCaseFlowId}",
+ scenarioTestCase.Id, testCase.TestCaseFlowId);
+ }
+ catch (Exception ex)
+ {
+ var errorMessage = $"创建测试用例失败,测试用例流程ID: {testCase.TestCaseFlowId}, 错误: {ex.Message}";
+ response.ErrorMessages.Add(errorMessage);
+ response.FailureCount++;
+ _logger.LogError(ex, errorMessage);
+ }
+ }
+
+ // 保存更改到数据库
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ _logger.LogInformation("创建场景测试用例完成,场景ID: {ScenarioId}, 成功: {SuccessCount}, 失败: {FailureCount}",
+ request.ScenarioId, response.SuccessCount, response.FailureCount);
+
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "创建场景测试用例时发生错误,场景ID: {ScenarioId}", request.ScenarioId);
+ return OperationResult.CreateFailure($"创建场景测试用例时发生错误: {ex.Message}");
+ }
+ }
+}
diff --git a/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseResponse.cs b/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseResponse.cs
new file mode 100644
index 0000000..097b6eb
--- /dev/null
+++ b/src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseResponse.cs
@@ -0,0 +1,37 @@
+namespace X1.Application.Features.ScenarioTestCases.Commands.CreateScenarioTestCase;
+
+///
+/// 创建场景测试用例响应
+///
+public class CreateScenarioTestCaseResponse
+{
+ ///
+ /// 成功创建的测试用例数量
+ ///
+ public int SuccessCount { get; set; }
+
+ ///
+ /// 失败的测试用例数量
+ ///
+ public int FailureCount { get; set; }
+
+ ///
+ /// 总数量
+ ///
+ public int TotalCount { get; set; }
+
+ ///
+ /// 成功创建的测试用例ID列表
+ ///
+ public List SuccessIds { get; set; } = new();
+
+ ///
+ /// 失败的错误信息
+ ///
+ public List ErrorMessages { get; set; } = new();
+
+ ///
+ /// 是否全部成功
+ ///
+ public bool IsAllSuccess => FailureCount == 0;
+}
diff --git a/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQuery.cs b/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQuery.cs
new file mode 100644
index 0000000..20b43c4
--- /dev/null
+++ b/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQuery.cs
@@ -0,0 +1,27 @@
+using X1.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace X1.Application.Features.ScenarioTestCases.Queries.GetScenarioTestCases;
+
+///
+/// 获取场景测试用例列表查询
+///
+public class GetScenarioTestCasesQuery : IRequest>
+{
+ ///
+ /// 场景ID
+ ///
+ [Required(ErrorMessage = "场景ID不能为空")]
+ public string ScenarioId { get; set; } = null!;
+
+ ///
+ /// 是否只获取启用的测试用例
+ ///
+ public bool? IsEnabled { get; set; }
+
+ ///
+ /// 是否包含详细信息
+ ///
+ public bool IncludeDetails { get; set; } = false;
+}
diff --git a/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQueryHandler.cs b/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQueryHandler.cs
new file mode 100644
index 0000000..1ca3970
--- /dev/null
+++ b/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQueryHandler.cs
@@ -0,0 +1,98 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Common;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.TestCase;
+
+namespace X1.Application.Features.ScenarioTestCases.Queries.GetScenarioTestCases;
+
+///
+/// 获取场景测试用例列表查询处理器
+///
+public class GetScenarioTestCasesQueryHandler : IRequestHandler>
+{
+ private readonly IScenarioTestCaseRepository _scenarioTestCaseRepository;
+ private readonly ITestScenarioRepository _testScenarioRepository;
+ private readonly ILogger _logger;
+
+ ///
+ /// 初始化查询处理器
+ ///
+ public GetScenarioTestCasesQueryHandler(
+ IScenarioTestCaseRepository scenarioTestCaseRepository,
+ ITestScenarioRepository testScenarioRepository,
+ ILogger logger)
+ {
+ _scenarioTestCaseRepository = scenarioTestCaseRepository;
+ _testScenarioRepository = testScenarioRepository;
+ _logger = logger;
+ }
+
+ ///
+ /// 处理获取场景测试用例列表查询
+ ///
+ public async Task> Handle(GetScenarioTestCasesQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始获取场景测试用例列表,场景ID: {ScenarioId}, 包含详细信息: {IncludeDetails}",
+ request.ScenarioId, request.IncludeDetails);
+
+ // 验证场景是否存在
+ var scenario = await _testScenarioRepository.GetByIdAsync(request.ScenarioId, cancellationToken:cancellationToken);
+ if (scenario == null)
+ {
+ _logger.LogWarning("测试场景不存在,场景ID: {ScenarioId}", request.ScenarioId);
+ return OperationResult.CreateFailure($"测试场景不存在,场景ID: {request.ScenarioId}");
+ }
+
+ // 获取场景测试用例
+ IEnumerable testCases;
+ if (request.IncludeDetails)
+ {
+ testCases = await _scenarioTestCaseRepository.GetWithDetailsByScenarioIdAsync(request.ScenarioId, cancellationToken);
+ }
+ else if (request.IsEnabled.HasValue)
+ {
+ testCases = await _scenarioTestCaseRepository.GetEnabledByScenarioIdAsync(request.ScenarioId, cancellationToken);
+ }
+ else
+ {
+ testCases = await _scenarioTestCaseRepository.GetByScenarioIdAsync(request.ScenarioId, cancellationToken);
+ }
+
+ // 转换为DTO
+ var testCaseDtos = testCases.Select(x => new ScenarioTestCaseDto
+ {
+ ScenarioTestCaseId = x.Id,
+ ScenarioId = x.ScenarioId,
+ TestCaseFlowId = x.TestCaseFlowId,
+ TestCaseFlowName = x.TestCaseFlow?.Name,
+ ExecutionOrder = x.ExecutionOrder,
+ LoopCount = x.LoopCount,
+ IsEnabled = x.IsEnabled,
+ CreatedAt = x.CreatedAt,
+ CreatedBy = x.CreatedBy,
+ UpdatedAt = x.UpdatedAt,
+ UpdatedBy = x.UpdatedBy
+ }).ToList();
+
+ // 构建响应
+ var response = new GetScenarioTestCasesResponse
+ {
+ ScenarioId = request.ScenarioId,
+ TestCases = testCaseDtos,
+ TotalCount = testCaseDtos.Count
+ };
+
+ _logger.LogInformation("获取场景测试用例列表成功,场景ID: {ScenarioId}, 测试用例数量: {TestCaseCount}",
+ request.ScenarioId, testCaseDtos.Count);
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取场景测试用例列表时发生错误,场景ID: {ScenarioId}", request.ScenarioId);
+ return OperationResult.CreateFailure($"获取场景测试用例列表时发生错误: {ex.Message}");
+ }
+ }
+}
diff --git a/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesResponse.cs b/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesResponse.cs
new file mode 100644
index 0000000..12e7640
--- /dev/null
+++ b/src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesResponse.cs
@@ -0,0 +1,83 @@
+namespace X1.Application.Features.ScenarioTestCases.Queries.GetScenarioTestCases;
+
+///
+/// 获取场景测试用例列表响应
+///
+public class GetScenarioTestCasesResponse
+{
+ ///
+ /// 场景ID
+ ///
+ public string ScenarioId { get; set; } = null!;
+
+ ///
+ /// 场景测试用例列表
+ ///
+ public List TestCases { get; set; } = new();
+
+ ///
+ /// 总数量
+ ///
+ public int TotalCount { get; set; }
+}
+
+///
+/// 场景测试用例数据传输对象
+///
+public class ScenarioTestCaseDto
+{
+ ///
+ /// 场景测试用例ID
+ ///
+ public string ScenarioTestCaseId { get; set; } = null!;
+
+ ///
+ /// 场景ID
+ ///
+ public string ScenarioId { get; set; } = null!;
+
+ ///
+ /// 测试用例流程ID
+ ///
+ public string TestCaseFlowId { get; set; } = null!;
+
+ ///
+ /// 测试用例流程名称
+ ///
+ public string? TestCaseFlowName { get; set; }
+
+ ///
+ /// 执行顺序
+ ///
+ public int ExecutionOrder { get; set; }
+
+ ///
+ /// 循环次数
+ ///
+ public int LoopCount { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 创建人
+ ///
+ public string CreatedBy { get; set; } = null!;
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; set; }
+
+ ///
+ /// 更新人
+ ///
+ public string UpdatedBy { get; set; } = null!;
+}
diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQuery.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQuery.cs
new file mode 100644
index 0000000..078e3dd
--- /dev/null
+++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQuery.cs
@@ -0,0 +1,12 @@
+using X1.Domain.Common;
+using MediatR;
+
+namespace X1.Application.Features.TestCaseFlow.Queries.GetFormTypeStepTypeMapping;
+
+///
+/// 获取表单类型到步骤类型映射查询
+///
+public class GetFormTypeStepTypeMappingQuery : IRequest>
+{
+ // 无需额外参数,返回所有映射关系
+}
diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQueryHandler.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQueryHandler.cs
new file mode 100644
index 0000000..ab15a63
--- /dev/null
+++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQueryHandler.cs
@@ -0,0 +1,39 @@
+using MediatR;
+using X1.Domain.Common;
+
+namespace X1.Application.Features.TestCaseFlow.Queries.GetFormTypeStepTypeMapping;
+
+///
+/// 获取表单类型到步骤类型映射查询处理器
+///
+public class GetFormTypeStepTypeMappingQueryHandler : IRequestHandler>
+{
+ ///
+ /// 处理查询
+ ///
+ /// 查询请求
+ /// 取消令牌
+ /// 操作结果
+ public async Task> Handle(GetFormTypeStepTypeMappingQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ // 构建响应对象
+ var response = new GetFormTypeStepTypeMappingResponse();
+
+ // 获取测试流程类型列表
+ response.TestFlowTypes = TestFlowTypeConverter.GetTestFlowTypes().Select(tft => new TestFlowTypeDto
+ {
+ Value = tft.Value,
+ Name = tft.Name,
+ Description = tft.Description
+ }).ToList();
+
+ return await Task.FromResult(OperationResult.CreateSuccess(response));
+ }
+ catch (Exception ex)
+ {
+ return await Task.FromResult(OperationResult.CreateFailure($"获取测试流程类型失败: {ex.Message}"));
+ }
+ }
+}
diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingResponse.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingResponse.cs
new file mode 100644
index 0000000..c177925
--- /dev/null
+++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingResponse.cs
@@ -0,0 +1,33 @@
+namespace X1.Application.Features.TestCaseFlow.Queries.GetFormTypeStepTypeMapping;
+
+///
+/// 获取测试流程类型响应
+///
+public class GetFormTypeStepTypeMappingResponse
+{
+ ///
+ /// 测试流程类型列表
+ ///
+ public List TestFlowTypes { get; set; } = new();
+}
+
+///
+/// 测试流程类型DTO
+///
+public class TestFlowTypeDto
+{
+ ///
+ /// 测试流程类型值
+ ///
+ public int Value { get; set; }
+
+ ///
+ /// 测试流程类型名称
+ ///
+ public string Name { get; set; } = null!;
+
+ ///
+ /// 测试流程类型描述
+ ///
+ public string Description { get; set; } = null!;
+}
diff --git a/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommand.cs b/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommand.cs
new file mode 100644
index 0000000..43349ca
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommand.cs
@@ -0,0 +1,36 @@
+using X1.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+using X1.Domain.Entities.TestCase;
+
+namespace X1.Application.Features.TestScenarios.Commands.CreateTestScenario;
+
+///
+/// 创建测试场景命令
+///
+public class CreateTestScenarioCommand : IRequest>
+{
+ ///
+ /// 场景名称
+ ///
+ [Required(ErrorMessage = "场景名称不能为空")]
+ [MaxLength(200, ErrorMessage = "场景名称不能超过200个字符")]
+ public string ScenarioName { get; set; } = null!;
+
+ ///
+ /// 场景类型
+ ///
+ [Required(ErrorMessage = "场景类型不能为空")]
+ public ScenarioType Type { get; set; } = ScenarioType.Functional;
+
+ ///
+ /// 场景描述
+ ///
+ [MaxLength(1000, ErrorMessage = "场景描述不能超过1000个字符")]
+ public string? Description { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; } = true;
+}
diff --git a/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommandHandler.cs b/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommandHandler.cs
new file mode 100644
index 0000000..e655a8e
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommandHandler.cs
@@ -0,0 +1,133 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Common;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.TestCase;
+using X1.Domain.Repositories.Base;
+using X1.Domain.Services;
+
+namespace X1.Application.Features.TestScenarios.Commands.CreateTestScenario;
+
+///
+/// 创建测试场景命令处理器
+///
+public class CreateTestScenarioCommandHandler : IRequestHandler>
+{
+ private readonly ITestScenarioRepository _testScenarioRepository;
+ private readonly ILogger _logger;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ICurrentUserService _currentUserService;
+
+ ///
+ /// 初始化命令处理器
+ ///
+ public CreateTestScenarioCommandHandler(
+ ITestScenarioRepository testScenarioRepository,
+ ILogger logger,
+ IUnitOfWork unitOfWork,
+ ICurrentUserService currentUserService)
+ {
+ _testScenarioRepository = testScenarioRepository;
+ _logger = logger;
+ _unitOfWork = unitOfWork;
+ _currentUserService = currentUserService;
+ }
+
+ ///
+ /// 处理创建测试场景命令
+ ///
+ public async Task> Handle(CreateTestScenarioCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始创建测试场景,场景名称: {ScenarioName}", request.ScenarioName);
+
+ // 生成场景编码
+ var scenarioCode = await GenerateScenarioCodeAsync(request.Type, cancellationToken);
+
+ // 获取当前用户ID
+ var currentUserId = _currentUserService.GetCurrentUserId();
+ if (string.IsNullOrEmpty(currentUserId))
+ {
+ _logger.LogError("无法获取当前用户ID,用户可能未认证");
+ return OperationResult.CreateFailure("用户未认证,无法创建测试场景");
+ }
+
+ // 创建测试场景实体
+ var testScenario = TestScenario.Create(
+ scenarioCode: scenarioCode,
+ scenarioName: request.ScenarioName,
+ type: request.Type,
+ description: request.Description,
+ isEnabled: request.IsEnabled,
+ createdBy: currentUserId);
+
+ // 保存测试场景
+ await _testScenarioRepository.AddAsync(testScenario, cancellationToken);
+
+ // 保存更改到数据库
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ // 构建响应
+ var response = new CreateTestScenarioResponse
+ {
+ TestScenarioId = testScenario.Id,
+ ScenarioCode = testScenario.ScenarioCode,
+ ScenarioName = testScenario.ScenarioName,
+ Type = testScenario.Type.ToString(),
+ Description = testScenario.Description,
+ IsEnabled = testScenario.IsEnabled,
+ CreatedAt = testScenario.CreatedAt,
+ CreatedBy = testScenario.CreatedBy
+ };
+
+ _logger.LogInformation("测试场景创建成功,场景ID: {TestScenarioId}, 场景编码: {ScenarioCode}, 场景名称: {ScenarioName}",
+ testScenario.Id, testScenario.ScenarioCode, testScenario.ScenarioName);
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "创建测试场景时发生错误,场景名称: {ScenarioName}", request.ScenarioName);
+ return OperationResult.CreateFailure($"创建测试场景时发生错误: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 生成场景编码
+ ///
+ private async Task GenerateScenarioCodeAsync(ScenarioType type, CancellationToken cancellationToken)
+ {
+ var prefix = type switch
+ {
+ ScenarioType.Functional => "FUNC",
+ ScenarioType.Performance => "PERF",
+ ScenarioType.Security => "SEC",
+ ScenarioType.Integration => "INT",
+ ScenarioType.Regression => "REG",
+ _ => "GEN"
+ };
+
+ var date = DateTime.Now.ToString("yyyyMMdd");
+ var random = new Random();
+ var suffix = random.Next(1000, 9999).ToString();
+
+ var scenarioCode = $"{prefix}_{date}_{suffix}";
+
+ // 检查生成的编码是否已存在,如果存在则重新生成
+ var maxAttempts = 10;
+ var attempts = 0;
+ while (await _testScenarioRepository.ExistsByScenarioCodeAsync(scenarioCode, cancellationToken: cancellationToken) && attempts < maxAttempts)
+ {
+ suffix = random.Next(1000, 9999).ToString();
+ scenarioCode = $"{prefix}_{date}_{suffix}";
+ attempts++;
+ }
+
+ if (attempts >= maxAttempts)
+ {
+ throw new InvalidOperationException("无法生成唯一的场景编码,请稍后重试");
+ }
+
+ return scenarioCode;
+ }
+}
diff --git a/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs b/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs
new file mode 100644
index 0000000..b59cf1a
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs
@@ -0,0 +1,47 @@
+namespace X1.Application.Features.TestScenarios.Commands.CreateTestScenario;
+
+///
+/// 创建测试场景响应
+///
+public class CreateTestScenarioResponse
+{
+ ///
+ /// 场景ID
+ ///
+ public string TestScenarioId { get; set; } = null!;
+
+ ///
+ /// 场景编码
+ ///
+ public string ScenarioCode { get; set; } = null!;
+
+ ///
+ /// 场景名称
+ ///
+ public string ScenarioName { get; set; } = null!;
+
+ ///
+ /// 场景类型
+ ///
+ public string Type { get; set; } = null!;
+
+ ///
+ /// 场景描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 创建人
+ ///
+ public string CreatedBy { get; set; } = null!;
+}
diff --git a/src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommand.cs b/src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommand.cs
new file mode 100644
index 0000000..7e25fc0
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommand.cs
@@ -0,0 +1,17 @@
+using X1.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace X1.Application.Features.TestScenarios.Commands.DeleteTestScenario;
+
+///
+/// 删除测试场景命令
+///
+public class DeleteTestScenarioCommand : IRequest>
+{
+ ///
+ /// 场景ID
+ ///
+ [Required(ErrorMessage = "场景ID不能为空")]
+ public string TestScenarioId { get; set; } = null!;
+}
diff --git a/src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommandHandler.cs b/src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommandHandler.cs
new file mode 100644
index 0000000..fac15fa
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommandHandler.cs
@@ -0,0 +1,77 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Common;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.TestCase;
+using X1.Domain.Repositories.Base;
+
+namespace X1.Application.Features.TestScenarios.Commands.DeleteTestScenario;
+
+///
+/// 删除测试场景命令处理器
+///
+public class DeleteTestScenarioCommandHandler : IRequestHandler>
+{
+ private readonly ITestScenarioRepository _testScenarioRepository;
+ private readonly IScenarioTestCaseRepository _scenarioTestCaseRepository;
+ private readonly ILogger _logger;
+ private readonly IUnitOfWork _unitOfWork;
+
+ ///
+ /// 初始化命令处理器
+ ///
+ public DeleteTestScenarioCommandHandler(
+ ITestScenarioRepository testScenarioRepository,
+ IScenarioTestCaseRepository scenarioTestCaseRepository,
+ ILogger logger,
+ IUnitOfWork unitOfWork)
+ {
+ _testScenarioRepository = testScenarioRepository;
+ _scenarioTestCaseRepository = scenarioTestCaseRepository;
+ _logger = logger;
+ _unitOfWork = unitOfWork;
+ }
+
+ ///
+ /// 处理删除测试场景命令
+ ///
+ public async Task> Handle(DeleteTestScenarioCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始删除测试场景,场景ID: {TestScenarioId}", request.TestScenarioId);
+
+ // 查找测试场景
+ var testScenario = await _testScenarioRepository.GetByIdAsync(request.TestScenarioId, cancellationToken: cancellationToken);
+ if (testScenario == null)
+ {
+ _logger.LogWarning("测试场景不存在,场景ID: {TestScenarioId}", request.TestScenarioId);
+ return OperationResult.CreateFailure($"测试场景不存在,场景ID: {request.TestScenarioId}");
+ }
+
+ // 检查是否有关联的测试用例
+ var testCaseCount = await _scenarioTestCaseRepository.GetCountByScenarioIdAsync(request.TestScenarioId, cancellationToken);
+ if (testCaseCount > 0)
+ {
+ _logger.LogWarning("测试场景存在关联的测试用例,无法删除,场景ID: {TestScenarioId}, 测试用例数量: {TestCaseCount}",
+ request.TestScenarioId, testCaseCount);
+ return OperationResult.CreateFailure($"测试场景存在 {testCaseCount} 个关联的测试用例,无法删除");
+ }
+
+ // 删除测试场景
+ _testScenarioRepository.Delete(testScenario);
+
+ // 保存更改到数据库
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ _logger.LogInformation("测试场景删除成功,场景ID: {TestScenarioId}, 场景编码: {ScenarioCode}",
+ testScenario.Id, testScenario.ScenarioCode);
+ return OperationResult.CreateSuccess(true);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "删除测试场景时发生错误,场景ID: {TestScenarioId}", request.TestScenarioId);
+ return OperationResult.CreateFailure($"删除测试场景时发生错误: {ex.Message}");
+ }
+ }
+}
diff --git a/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommand.cs b/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommand.cs
new file mode 100644
index 0000000..b2135ed
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommand.cs
@@ -0,0 +1,28 @@
+using X1.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace X1.Application.Features.TestScenarios.Commands.UpdateTestScenario;
+
+///
+/// 更新测试场景命令
+///
+public class UpdateTestScenarioCommand : IRequest>
+{
+ ///
+ /// 场景ID
+ ///
+ [Required(ErrorMessage = "场景ID不能为空")]
+ public string TestScenarioId { get; set; } = null!;
+
+ ///
+ /// 场景描述
+ ///
+ [MaxLength(1000, ErrorMessage = "场景描述不能超过1000个字符")]
+ public string? Description { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; } = true;
+}
diff --git a/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommandHandler.cs b/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommandHandler.cs
new file mode 100644
index 0000000..f5ef75f
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommandHandler.cs
@@ -0,0 +1,92 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Common;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.TestCase;
+using X1.Domain.Repositories.Base;
+using X1.Domain.Services;
+
+namespace X1.Application.Features.TestScenarios.Commands.UpdateTestScenario;
+
+///
+/// 更新测试场景命令处理器
+///
+public class UpdateTestScenarioCommandHandler : IRequestHandler>
+{
+ private readonly ITestScenarioRepository _testScenarioRepository;
+ private readonly ILogger _logger;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ICurrentUserService _currentUserService;
+
+ ///
+ /// 初始化命令处理器
+ ///
+ public UpdateTestScenarioCommandHandler(
+ ITestScenarioRepository testScenarioRepository,
+ ILogger logger,
+ IUnitOfWork unitOfWork,
+ ICurrentUserService currentUserService)
+ {
+ _testScenarioRepository = testScenarioRepository;
+ _logger = logger;
+ _unitOfWork = unitOfWork;
+ _currentUserService = currentUserService;
+ }
+
+ ///
+ /// 处理更新测试场景命令
+ ///
+ public async Task> Handle(UpdateTestScenarioCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始更新测试场景,场景ID: {TestScenarioId}", request.TestScenarioId);
+
+ // 获取当前用户ID
+ var currentUserId = _currentUserService.GetCurrentUserId();
+ if (string.IsNullOrEmpty(currentUserId))
+ {
+ _logger.LogError("无法获取当前用户ID,用户可能未认证");
+ return OperationResult.CreateFailure("用户未认证,无法更新测试场景");
+ }
+
+ // 查找测试场景
+ var testScenario = await _testScenarioRepository.GetByIdAsync(request.TestScenarioId, cancellationToken:cancellationToken);
+ if (testScenario == null)
+ {
+ _logger.LogWarning("测试场景不存在,场景ID: {TestScenarioId}", request.TestScenarioId);
+ return OperationResult.CreateFailure($"测试场景不存在,场景ID: {request.TestScenarioId}");
+ }
+
+ // 更新测试场景(只允许更新描述和启用状态)
+ testScenario.UpdateDescription(request.Description);
+ testScenario.SetEnabled(request.IsEnabled);
+ testScenario.UpdateAuditInfo(currentUserId);
+
+ // 保存更改到数据库
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ // 构建响应
+ var response = new UpdateTestScenarioResponse
+ {
+ TestScenarioId = testScenario.Id,
+ ScenarioCode = testScenario.ScenarioCode,
+ ScenarioName = testScenario.ScenarioName,
+ Type = testScenario.Type.ToString(),
+ Description = testScenario.Description,
+ IsEnabled = testScenario.IsEnabled,
+ UpdatedAt = testScenario.UpdatedAt,
+ UpdatedBy = testScenario.UpdatedBy
+ };
+
+ _logger.LogInformation("测试场景更新成功,场景ID: {TestScenarioId}, 场景编码: {ScenarioCode}",
+ testScenario.Id, testScenario.ScenarioCode);
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "更新测试场景时发生错误,场景ID: {TestScenarioId}", request.TestScenarioId);
+ return OperationResult.CreateFailure($"更新测试场景时发生错误: {ex.Message}");
+ }
+ }
+}
diff --git a/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioResponse.cs b/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioResponse.cs
new file mode 100644
index 0000000..61c04f5
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioResponse.cs
@@ -0,0 +1,47 @@
+namespace X1.Application.Features.TestScenarios.Commands.UpdateTestScenario;
+
+///
+/// 更新测试场景响应
+///
+public class UpdateTestScenarioResponse
+{
+ ///
+ /// 场景ID
+ ///
+ public string TestScenarioId { get; set; } = null!;
+
+ ///
+ /// 场景编码
+ ///
+ public string ScenarioCode { get; set; } = null!;
+
+ ///
+ /// 场景名称
+ ///
+ public string ScenarioName { get; set; } = null!;
+
+ ///
+ /// 场景类型
+ ///
+ public string Type { get; set; } = null!;
+
+ ///
+ /// 场景描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; set; }
+
+ ///
+ /// 更新人
+ ///
+ public string UpdatedBy { get; set; } = null!;
+}
diff --git a/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQuery.cs b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQuery.cs
new file mode 100644
index 0000000..d6ade39
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQuery.cs
@@ -0,0 +1,22 @@
+using X1.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace X1.Application.Features.TestScenarios.Queries.GetTestScenarioById;
+
+///
+/// 根据ID获取测试场景查询
+///
+public class GetTestScenarioByIdQuery : IRequest>
+{
+ ///
+ /// 场景ID
+ ///
+ [Required(ErrorMessage = "场景ID不能为空")]
+ public string TestScenarioId { get; set; } = null!;
+
+ ///
+ /// 是否包含测试用例
+ ///
+ public bool IncludeTestCases { get; set; } = false;
+}
diff --git a/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQueryHandler.cs b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQueryHandler.cs
new file mode 100644
index 0000000..186685d
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQueryHandler.cs
@@ -0,0 +1,99 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Common;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.TestCase;
+
+namespace X1.Application.Features.TestScenarios.Queries.GetTestScenarioById;
+
+///
+/// 根据ID获取测试场景查询处理器
+///
+public class GetTestScenarioByIdQueryHandler : IRequestHandler>
+{
+ private readonly ITestScenarioRepository _testScenarioRepository;
+ private readonly ILogger _logger;
+
+ ///
+ /// 初始化查询处理器
+ ///
+ public GetTestScenarioByIdQueryHandler(
+ ITestScenarioRepository testScenarioRepository,
+ ILogger logger)
+ {
+ _testScenarioRepository = testScenarioRepository;
+ _logger = logger;
+ }
+
+ ///
+ /// 处理根据ID获取测试场景查询
+ ///
+ public async Task> Handle(GetTestScenarioByIdQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始获取测试场景详情,场景ID: {TestScenarioId}, 包含测试用例: {IncludeTestCases}",
+ request.TestScenarioId, request.IncludeTestCases);
+
+ // 获取测试场景
+ TestScenario? testScenario;
+ if (request.IncludeTestCases)
+ {
+ testScenario = await _testScenarioRepository.GetWithTestCasesAsync(request.TestScenarioId, cancellationToken);
+ }
+ else
+ {
+ testScenario = await _testScenarioRepository.GetByIdAsync(request.TestScenarioId, cancellationToken: cancellationToken);
+ }
+
+ if (testScenario == null)
+ {
+ _logger.LogWarning("测试场景不存在,场景ID: {TestScenarioId}", request.TestScenarioId);
+ return OperationResult.CreateFailure($"测试场景不存在,场景ID: {request.TestScenarioId}");
+ }
+
+ // 构建响应
+ var response = new GetTestScenarioByIdResponse
+ {
+ TestScenarioId = testScenario.Id,
+ ScenarioCode = testScenario.ScenarioCode,
+ ScenarioName = testScenario.ScenarioName,
+ Type = testScenario.Type.ToString(),
+ Description = testScenario.Description,
+ IsEnabled = testScenario.IsEnabled,
+ CreatedAt = testScenario.CreatedAt,
+ CreatedBy = testScenario.CreatedBy,
+ UpdatedAt = testScenario.UpdatedAt,
+ UpdatedBy = testScenario.UpdatedBy
+ };
+
+ // 如果包含测试用例,则添加测试用例信息
+ if (request.IncludeTestCases && testScenario.ScenarioTestCases != null)
+ {
+ response.TestCases = testScenario.ScenarioTestCases
+ .OrderBy(x => x.ExecutionOrder)
+ .Select(x => new ScenarioTestCaseDto
+ {
+ ScenarioTestCaseId = x.Id,
+ TestCaseFlowId = x.TestCaseFlowId,
+ TestCaseFlowName = x.TestCaseFlow?.Name ?? "未知",
+ ExecutionOrder = x.ExecutionOrder,
+ LoopCount = x.LoopCount,
+ IsEnabled = x.IsEnabled,
+ CreatedAt = x.CreatedAt,
+ CreatedBy = x.CreatedBy
+ })
+ .ToList();
+ }
+
+ _logger.LogInformation("获取测试场景详情成功,场景ID: {TestScenarioId}, 场景编码: {ScenarioCode}",
+ testScenario.Id, testScenario.ScenarioCode);
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取测试场景详情时发生错误,场景ID: {TestScenarioId}", request.TestScenarioId);
+ return OperationResult.CreateFailure($"获取测试场景详情时发生错误: {ex.Message}");
+ }
+ }
+}
diff --git a/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs
new file mode 100644
index 0000000..b4e9d11
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs
@@ -0,0 +1,108 @@
+namespace X1.Application.Features.TestScenarios.Queries.GetTestScenarioById;
+
+///
+/// 根据ID获取测试场景响应
+///
+public class GetTestScenarioByIdResponse
+{
+ ///
+ /// 场景ID
+ ///
+ public string TestScenarioId { get; set; } = null!;
+
+ ///
+ /// 场景编码
+ ///
+ public string ScenarioCode { get; set; } = null!;
+
+ ///
+ /// 场景名称
+ ///
+ public string ScenarioName { get; set; } = null!;
+
+ ///
+ /// 场景类型
+ ///
+ public string Type { get; set; } = null!;
+
+ ///
+ /// 场景描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 创建人
+ ///
+ public string CreatedBy { get; set; } = null!;
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; set; }
+
+ ///
+ /// 更新人
+ ///
+ public string UpdatedBy { get; set; } = null!;
+
+ ///
+ /// 测试用例列表
+ ///
+ public List? TestCases { get; set; }
+}
+
+///
+/// 场景测试用例数据传输对象
+///
+public class ScenarioTestCaseDto
+{
+ ///
+ /// 场景测试用例ID
+ ///
+ public string ScenarioTestCaseId { get; set; } = null!;
+
+ ///
+ /// 测试用例流程ID
+ ///
+ public string TestCaseFlowId { get; set; } = null!;
+
+ ///
+ /// 测试用例流程名称
+ ///
+ public string TestCaseFlowName { get; set; } = null!;
+
+ ///
+ /// 执行顺序
+ ///
+ public int ExecutionOrder { get; set; }
+
+ ///
+ /// 循环次数
+ ///
+ public int LoopCount { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 创建人
+ ///
+ public string CreatedBy { get; set; } = null!;
+}
diff --git a/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQuery.cs b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQuery.cs
new file mode 100644
index 0000000..c01ff2c
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQuery.cs
@@ -0,0 +1,40 @@
+using X1.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+using X1.Domain.Entities.TestCase;
+
+namespace X1.Application.Features.TestScenarios.Queries.GetTestScenarios;
+
+///
+/// 获取测试场景列表查询
+///
+public class GetTestScenariosQuery : IRequest>
+{
+ ///
+ /// 页码
+ ///
+ [Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")]
+ public int PageNumber { get; set; } = 1;
+
+ ///
+ /// 每页数量
+ ///
+ [Range(1, 1000, ErrorMessage = "每页数量必须在1-1000之间")]
+ public int PageSize { get; set; } = 10;
+
+ ///
+ /// 搜索关键词
+ ///
+ [MaxLength(100)]
+ public string? SearchTerm { get; set; }
+
+ ///
+ /// 场景类型
+ ///
+ public ScenarioType? Type { get; set; }
+
+ ///
+ /// 是否只获取启用的场景
+ ///
+ public bool? IsEnabled { get; set; }
+}
diff --git a/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQueryHandler.cs b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQueryHandler.cs
new file mode 100644
index 0000000..3cbeefe
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQueryHandler.cs
@@ -0,0 +1,111 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Common;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.TestCase;
+
+namespace X1.Application.Features.TestScenarios.Queries.GetTestScenarios;
+
+///
+/// 获取测试场景列表查询处理器
+///
+public class GetTestScenariosQueryHandler : IRequestHandler>
+{
+ private readonly ITestScenarioRepository _testScenarioRepository;
+ private readonly ILogger _logger;
+
+ ///
+ /// 初始化查询处理器
+ ///
+ public GetTestScenariosQueryHandler(
+ ITestScenarioRepository testScenarioRepository,
+ ILogger logger)
+ {
+ _testScenarioRepository = testScenarioRepository;
+ _logger = logger;
+ }
+
+ ///
+ /// 处理获取测试场景列表查询
+ ///
+ public async Task> Handle(GetTestScenariosQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始获取测试场景列表,页码: {PageNumber}, 每页数量: {PageSize}",
+ request.PageNumber, request.PageSize);
+
+ // 获取所有测试场景
+ var allScenarios = await _testScenarioRepository.GetAllAsync(cancellationToken:cancellationToken);
+
+ // 应用过滤条件
+ var filteredScenarios = allScenarios.AsQueryable();
+
+ // 按类型过滤
+ if (request.Type.HasValue)
+ {
+ filteredScenarios = filteredScenarios.Where(x => x.Type == request.Type.Value);
+ }
+
+ // 按启用状态过滤
+ if (request.IsEnabled.HasValue)
+ {
+ filteredScenarios = filteredScenarios.Where(x => x.IsEnabled == request.IsEnabled.Value);
+ }
+
+ // 按搜索关键词过滤
+ if (!string.IsNullOrWhiteSpace(request.SearchTerm))
+ {
+ var searchTerm = request.SearchTerm.ToLower();
+ filteredScenarios = filteredScenarios.Where(x =>
+ x.ScenarioCode.ToLower().Contains(searchTerm) ||
+ x.ScenarioName.ToLower().Contains(searchTerm) ||
+ (x.Description != null && x.Description.ToLower().Contains(searchTerm)));
+ }
+
+ // 计算总数
+ var totalCount = filteredScenarios.Count();
+
+ // 分页
+ var pagedScenarios = filteredScenarios
+ .OrderBy(x => x.ScenarioCode)
+ .Skip((request.PageNumber - 1) * request.PageSize)
+ .Take(request.PageSize)
+ .ToList();
+
+ // 转换为DTO
+ var scenarioDtos = pagedScenarios.Select(x => new TestScenarioDto
+ {
+ TestScenarioId = x.Id,
+ ScenarioCode = x.ScenarioCode,
+ ScenarioName = x.ScenarioName,
+ Type = x.Type.ToString(),
+ Description = x.Description,
+ IsEnabled = x.IsEnabled,
+ CreatedAt = x.CreatedAt,
+ CreatedBy = x.CreatedBy,
+ UpdatedAt = x.UpdatedAt,
+ UpdatedBy = x.UpdatedBy
+ }).ToList();
+
+ // 构建响应
+ var response = new GetTestScenariosResponse
+ {
+ TestScenarios = scenarioDtos,
+ TotalCount = totalCount,
+ PageNumber = request.PageNumber,
+ PageSize = request.PageSize,
+ TotalPages = (int)Math.Ceiling((double)totalCount / request.PageSize)
+ };
+
+ _logger.LogInformation("获取测试场景列表成功,总数: {TotalCount}, 当前页数量: {CurrentPageCount}",
+ totalCount, scenarioDtos.Count);
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取测试场景列表时发生错误");
+ return OperationResult.CreateFailure($"获取测试场景列表时发生错误: {ex.Message}");
+ }
+ }
+}
diff --git a/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs
new file mode 100644
index 0000000..f42b96d
--- /dev/null
+++ b/src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs
@@ -0,0 +1,88 @@
+namespace X1.Application.Features.TestScenarios.Queries.GetTestScenarios;
+
+///
+/// 获取测试场景列表响应
+///
+public class GetTestScenariosResponse
+{
+ ///
+ /// 测试场景列表
+ ///
+ public List TestScenarios { get; set; } = new();
+
+ ///
+ /// 总数量
+ ///
+ public int TotalCount { get; set; }
+
+ ///
+ /// 页码
+ ///
+ public int PageNumber { get; set; }
+
+ ///
+ /// 每页数量
+ ///
+ public int PageSize { get; set; }
+
+ ///
+ /// 总页数
+ ///
+ public int TotalPages { get; set; }
+}
+
+///
+/// 测试场景数据传输对象
+///
+public class TestScenarioDto
+{
+ ///
+ /// 场景ID
+ ///
+ public string TestScenarioId { get; set; } = null!;
+
+ ///
+ /// 场景编码
+ ///
+ public string ScenarioCode { get; set; } = null!;
+
+ ///
+ /// 场景名称
+ ///
+ public string ScenarioName { get; set; } = null!;
+
+ ///
+ /// 场景类型
+ ///
+ public string Type { get; set; } = null!;
+
+ ///
+ /// 场景描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 创建人
+ ///
+ public string CreatedBy { get; set; } = null!;
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; set; }
+
+ ///
+ /// 更新人
+ ///
+ public string UpdatedBy { get; set; } = null!;
+}
diff --git a/src/X1.Domain/Common/TestFlowTypeConverter.cs b/src/X1.Domain/Common/TestFlowTypeConverter.cs
new file mode 100644
index 0000000..d5e3957
--- /dev/null
+++ b/src/X1.Domain/Common/TestFlowTypeConverter.cs
@@ -0,0 +1,231 @@
+using X1.Domain.Entities.TestCase;
+using System.ComponentModel;
+using System.Reflection;
+using System.ComponentModel.DataAnnotations;
+
+namespace X1.Domain.Common;
+
+///
+/// 测试流程类型转换器
+///
+public static class TestFlowTypeConverter
+{
+ ///
+ /// 根据测试流程类型获取推荐的表单类型
+ ///
+ /// 测试流程类型
+ /// 推荐的表单类型
+ public static FormType GetRecommendedFormType(TestFlowType testFlowType)
+ {
+ return testFlowType switch
+ {
+ TestFlowType.Registration => FormType.DeviceRegistrationForm,
+ TestFlowType.Voice => FormType.VoiceCallForm,
+ TestFlowType.Data => FormType.NetworkPerformanceForm,
+ _ => FormType.NoForm
+ };
+ }
+
+ ///
+ /// 根据测试流程类型获取推荐的步骤类型
+ ///
+ /// 测试流程类型
+ /// 推荐的步骤类型
+ public static CaseStepType GetRecommendedStepType(TestFlowType testFlowType)
+ {
+ return testFlowType switch
+ {
+ TestFlowType.Registration => CaseStepType.Process,
+ TestFlowType.Voice => CaseStepType.Process,
+ TestFlowType.Data => CaseStepType.Process,
+ _ => CaseStepType.Process
+ };
+ }
+
+ ///
+ /// 获取测试流程类型与表单类型的映射关系
+ ///
+ /// 测试流程类型到表单类型的映射字典
+ public static Dictionary GetTestFlowTypeToFormTypeMapping()
+ {
+ return new Dictionary
+ {
+ { TestFlowType.Registration, FormType.DeviceRegistrationForm },
+ { TestFlowType.Voice, FormType.VoiceCallForm },
+ { TestFlowType.Data, FormType.NetworkPerformanceForm }
+ };
+ }
+
+ ///
+ /// 获取测试流程类型与步骤类型的映射关系
+ ///
+ /// 测试流程类型到步骤类型的映射字典
+ public static Dictionary GetTestFlowTypeToStepTypeMapping()
+ {
+ return new Dictionary
+ {
+ { TestFlowType.Registration, CaseStepType.Process },
+ { TestFlowType.Voice, CaseStepType.Process },
+ { TestFlowType.Data, CaseStepType.Process }
+ };
+ }
+
+ ///
+ /// 获取表单类型支持的测试流程类型列表
+ ///
+ /// 表单类型
+ /// 支持的测试流程类型列表
+ public static List GetSupportedTestFlowTypes(FormType formType)
+ {
+ return formType switch
+ {
+ FormType.NoForm => new List { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
+ FormType.DeviceRegistrationForm => new List { TestFlowType.Registration },
+ FormType.VoiceCallForm => new List { TestFlowType.Voice },
+ FormType.NetworkPerformanceForm => new List { TestFlowType.Data },
+ FormType.NetworkConnectivityForm => new List { TestFlowType.Data },
+ _ => new List { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data }
+ };
+ }
+
+ ///
+ /// 获取步骤类型支持的测试流程类型列表
+ ///
+ /// 步骤类型
+ /// 支持的测试流程类型列表
+ public static List GetSupportedTestFlowTypes(CaseStepType stepType)
+ {
+ return stepType switch
+ {
+ CaseStepType.Start => new List { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
+ CaseStepType.End => new List { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
+ CaseStepType.Process => new List { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
+ CaseStepType.Decision => new List { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
+ _ => new List { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data }
+ };
+ }
+
+ ///
+ /// 检查测试流程类型是否与表单类型兼容
+ ///
+ /// 测试流程类型
+ /// 表单类型
+ /// 是否兼容
+ public static bool IsCompatibleWithFormType(TestFlowType testFlowType, FormType formType)
+ {
+ var supportedTestFlowTypes = GetSupportedTestFlowTypes(formType);
+ return supportedTestFlowTypes.Contains(testFlowType);
+ }
+
+ ///
+ /// 检查测试流程类型是否与步骤类型兼容
+ ///
+ /// 测试流程类型
+ /// 步骤类型
+ /// 是否兼容
+ public static bool IsCompatibleWithStepType(TestFlowType testFlowType, CaseStepType stepType)
+ {
+ var supportedTestFlowTypes = GetSupportedTestFlowTypes(stepType);
+ return supportedTestFlowTypes.Contains(testFlowType);
+ }
+
+ ///
+ /// 获取所有测试流程类型及其描述
+ ///
+ /// 测试流程类型描述字典
+ public static Dictionary GetTestFlowTypeDescriptions()
+ {
+ return new Dictionary
+ {
+ { TestFlowType.Registration, TestFlowType.Registration.GetDisplayName() },
+ { TestFlowType.Voice, TestFlowType.Voice.GetDisplayName() },
+ { TestFlowType.Data, TestFlowType.Data.GetDisplayName() }
+ };
+ }
+
+ ///
+ /// 获取所有测试流程类型
+ ///
+ /// 测试流程类型列表
+ public static List GetTestFlowTypes()
+ {
+ var testFlowTypes = new List();
+
+ foreach (TestFlowType testFlowType in Enum.GetValues(typeof(TestFlowType)))
+ {
+ var fieldInfo = typeof(TestFlowType).GetField(testFlowType.ToString());
+ if (fieldInfo != null)
+ {
+ var displayAttribute = fieldInfo.GetCustomAttribute();
+ var descriptionAttribute = fieldInfo.GetCustomAttribute();
+
+ var name = displayAttribute?.Name ?? testFlowType.ToString();
+ var description = descriptionAttribute?.Description ?? testFlowType.ToString();
+
+ testFlowTypes.Add(new EnumValueObject
+ {
+ Value = (int)testFlowType,
+ Name = name,
+ Description = description
+ });
+ }
+ }
+
+ return testFlowTypes;
+ }
+
+ ///
+ /// 根据测试流程类型获取推荐的步骤映射
+ ///
+ /// 测试流程类型
+ /// 推荐的步骤映射列表
+ public static List GetRecommendedStepMappings(TestFlowType testFlowType)
+ {
+ return testFlowType switch
+ {
+ TestFlowType.Registration => new List
+ {
+ StepMapping.StartFlow,
+ StepMapping.ImsiRegistration,
+ StepMapping.EndFlow
+ },
+ TestFlowType.Voice => new List
+ {
+ StepMapping.StartFlow,
+ StepMapping.MoCall,
+ StepMapping.MtCall,
+ StepMapping.HangUpCall,
+ StepMapping.EndFlow
+ },
+ TestFlowType.Data => new List
+ {
+ StepMapping.StartFlow,
+ StepMapping.PingTest,
+ StepMapping.IperfTest,
+ StepMapping.EndFlow
+ },
+ _ => new List { StepMapping.None }
+ };
+ }
+
+ ///
+ /// 根据步骤映射获取对应的测试流程类型
+ ///
+ /// 步骤映射
+ /// 对应的测试流程类型
+ public static TestFlowType GetTestFlowTypeByStepMapping(StepMapping mapping)
+ {
+ return mapping switch
+ {
+ StepMapping.StartFlow => TestFlowType.Registration,
+ StepMapping.EndFlow => TestFlowType.Registration,
+ StepMapping.ImsiRegistration => TestFlowType.Registration,
+ StepMapping.MoCall => TestFlowType.Voice,
+ StepMapping.MtCall => TestFlowType.Voice,
+ StepMapping.HangUpCall => TestFlowType.Voice,
+ StepMapping.PingTest => TestFlowType.Data,
+ StepMapping.IperfTest => TestFlowType.Data,
+ _ => TestFlowType.Registration
+ };
+ }
+}
diff --git a/src/X1.Domain/Entities/TestCase/TestFlowType.cs b/src/X1.Domain/Entities/TestCase/TestFlowType.cs
index c5e2ee8..2b16afc 100644
--- a/src/X1.Domain/Entities/TestCase/TestFlowType.cs
+++ b/src/X1.Domain/Entities/TestCase/TestFlowType.cs
@@ -3,13 +3,23 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
namespace X1.Domain.Entities.TestCase
{
public enum TestFlowType
{
- Registration = 1, // 注册测试
- Voice = 2, // 语音测试
- Data = 3 // 数据测试
+ [Display(Name = "注册测试", Description = "设备注册到网络的测试流程")]
+ [Description("设备注册到网络的测试流程")]
+ Registration = 1,
+
+ [Display(Name = "语音测试", Description = "语音通话相关的测试流程")]
+ [Description("语音通话相关的测试流程")]
+ Voice = 2,
+
+ [Display(Name = "数据测试", Description = "数据传输性能相关的测试流程")]
+ [Description("数据传输性能相关的测试流程")]
+ Data = 3
}
}
diff --git a/src/X1.Domain/Entities/TestCase/TestScenario.cs b/src/X1.Domain/Entities/TestCase/TestScenario.cs
index 9101fef..6a017b0 100644
--- a/src/X1.Domain/Entities/TestCase/TestScenario.cs
+++ b/src/X1.Domain/Entities/TestCase/TestScenario.cs
@@ -104,5 +104,33 @@ namespace X1.Domain.Entities.TestCase
UpdatedAt = DateTime.UtcNow;
UpdatedBy = updatedBy;
}
+
+ ///
+ /// 设置启用状态
+ ///
+ /// 是否启用
+ public void SetEnabled(bool isEnabled)
+ {
+ IsEnabled = isEnabled;
+ }
+
+ ///
+ /// 更新描述
+ ///
+ /// 场景描述
+ public void UpdateDescription(string? description)
+ {
+ Description = description;
+ }
+
+ ///
+ /// 更新审计信息
+ ///
+ /// 更新人ID
+ public void UpdateAuditInfo(string updatedBy)
+ {
+ UpdatedAt = DateTime.UtcNow;
+ UpdatedBy = updatedBy;
+ }
}
}
\ No newline at end of file
diff --git a/src/X1.Domain/Repositories/TestCase/IScenarioTestCaseRepository.cs b/src/X1.Domain/Repositories/TestCase/IScenarioTestCaseRepository.cs
new file mode 100644
index 0000000..d55d6b6
--- /dev/null
+++ b/src/X1.Domain/Repositories/TestCase/IScenarioTestCaseRepository.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.Base;
+
+namespace X1.Domain.Repositories.TestCase
+{
+ ///
+ /// 场景测试用例仓储接口
+ ///
+ public interface IScenarioTestCaseRepository : IBaseRepository
+ {
+ ///
+ /// 根据场景ID获取测试用例列表
+ ///
+ /// 场景ID
+ /// 取消令牌
+ /// 测试用例列表
+ Task> GetByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据测试用例流程ID获取场景测试用例列表
+ ///
+ /// 测试用例流程ID
+ /// 取消令牌
+ /// 场景测试用例列表
+ Task> GetByTestCaseFlowIdAsync(string testCaseFlowId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据场景ID获取启用的测试用例列表
+ ///
+ /// 场景ID
+ /// 取消令牌
+ /// 启用的测试用例列表
+ Task> GetEnabledByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据场景ID和测试用例流程ID获取场景测试用例
+ ///
+ /// 场景ID
+ /// 测试用例流程ID
+ /// 取消令牌
+ /// 场景测试用例
+ Task GetByScenarioAndTestCaseFlowAsync(string scenarioId, string testCaseFlowId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 检查场景中是否已存在指定的测试用例
+ ///
+ /// 场景ID
+ /// 测试用例流程ID
+ /// 排除的ID
+ /// 取消令牌
+ /// 是否存在
+ Task ExistsInScenarioAsync(string scenarioId, string testCaseFlowId, string? excludeId = null, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据场景ID获取包含完整信息的测试用例列表
+ ///
+ /// 场景ID
+ /// 取消令牌
+ /// 包含完整信息的测试用例列表
+ Task> GetWithDetailsByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据场景ID删除所有测试用例
+ ///
+ /// 场景ID
+ /// 取消令牌
+ /// 删除的记录数
+ Task DeleteByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据场景ID获取测试用例数量
+ ///
+ /// 场景ID
+ /// 取消令牌
+ /// 测试用例数量
+ Task GetCountByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 批量检查场景中已存在的测试用例流程ID
+ ///
+ /// 场景ID
+ /// 要检查的测试用例流程ID列表
+ /// 取消令牌
+ /// 已存在的测试用例流程ID集合
+ Task> GetExistingTestCaseFlowIdsAsync(string scenarioId, IEnumerable testCaseFlowIds, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/src/X1.Domain/Repositories/TestCase/ITestScenarioRepository.cs b/src/X1.Domain/Repositories/TestCase/ITestScenarioRepository.cs
new file mode 100644
index 0000000..acba10a
--- /dev/null
+++ b/src/X1.Domain/Repositories/TestCase/ITestScenarioRepository.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.Base;
+
+namespace X1.Domain.Repositories.TestCase
+{
+ ///
+ /// 测试场景仓储接口
+ ///
+ public interface ITestScenarioRepository : IBaseRepository
+ {
+ ///
+ /// 根据场景编码获取测试场景
+ ///
+ /// 场景编码
+ /// 取消令牌
+ /// 测试场景
+ Task GetByScenarioCodeAsync(string scenarioCode, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据场景类型获取测试场景列表
+ ///
+ /// 场景类型
+ /// 取消令牌
+ /// 测试场景列表
+ Task> GetByTypeAsync(ScenarioType type, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取启用的测试场景列表
+ ///
+ /// 取消令牌
+ /// 启用的测试场景列表
+ Task> GetEnabledAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 检查场景编码是否存在
+ ///
+ /// 场景编码
+ /// 排除的ID
+ /// 取消令牌
+ /// 是否存在
+ Task ExistsByScenarioCodeAsync(string scenarioCode, string? excludeId = null, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据场景ID获取包含测试用例的完整场景信息
+ ///
+ /// 场景ID
+ /// 取消令牌
+ /// 包含测试用例的场景
+ Task GetWithTestCasesAsync(string scenarioId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取所有包含测试用例的场景列表
+ ///
+ /// 取消令牌
+ /// 包含测试用例的场景列表
+ Task> GetAllWithTestCasesAsync(CancellationToken cancellationToken = default);
+ }
+}
diff --git a/src/X1.Infrastructure/Configurations/TestCase/ScenarioTestCaseConfiguration.cs b/src/X1.Infrastructure/Configurations/TestCase/ScenarioTestCaseConfiguration.cs
new file mode 100644
index 0000000..fc0f57a
--- /dev/null
+++ b/src/X1.Infrastructure/Configurations/TestCase/ScenarioTestCaseConfiguration.cs
@@ -0,0 +1,82 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using X1.Domain.Entities.TestCase;
+
+namespace X1.Infrastructure.Configurations.TestCase
+{
+ ///
+ /// 场景测试用例实体配置
+ ///
+ public class ScenarioTestCaseConfiguration : IEntityTypeConfiguration
+ {
+ public void Configure(EntityTypeBuilder builder)
+ {
+ // 表名
+ builder.ToTable("tb_scenariotestcases");
+
+ // 主键
+ builder.HasKey(x => x.Id);
+
+ // 属性配置
+ builder.Property(x => x.Id)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.ScenarioId)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.TestCaseFlowId)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.IsEnabled)
+ .IsRequired()
+ .HasDefaultValue(true);
+
+ builder.Property(x => x.ExecutionOrder)
+ .IsRequired()
+ .HasDefaultValue(0);
+
+ builder.Property(x => x.LoopCount)
+ .IsRequired()
+ .HasDefaultValue(1);
+
+ // 审计字段
+ builder.Property(x => x.CreatedAt)
+ .IsRequired();
+
+ builder.Property(x => x.UpdatedAt)
+ .IsRequired();
+
+ builder.Property(x => x.CreatedBy)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.UpdatedBy)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ // 索引
+ builder.HasIndex(x => x.ScenarioId);
+
+ builder.HasIndex(x => x.TestCaseFlowId);
+
+ builder.HasIndex(x => new { x.ScenarioId, x.TestCaseFlowId })
+ .IsUnique();
+
+ builder.HasIndex(x => new { x.ScenarioId, x.ExecutionOrder });
+
+ // 关系配置
+ builder.HasOne(x => x.Scenario)
+ .WithMany(x => x.ScenarioTestCases)
+ .HasForeignKey(x => x.ScenarioId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ builder.HasOne(x => x.TestCaseFlow)
+ .WithMany(x => x.TestScenarioTestCases)
+ .HasForeignKey(x => x.TestCaseFlowId)
+ .OnDelete(DeleteBehavior.Restrict);
+ }
+ }
+}
diff --git a/src/X1.Infrastructure/Configurations/TestCase/TestScenarioConfiguration.cs b/src/X1.Infrastructure/Configurations/TestCase/TestScenarioConfiguration.cs
new file mode 100644
index 0000000..d9f3c09
--- /dev/null
+++ b/src/X1.Infrastructure/Configurations/TestCase/TestScenarioConfiguration.cs
@@ -0,0 +1,74 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using X1.Domain.Entities.TestCase;
+
+namespace X1.Infrastructure.Configurations.TestCase
+{
+ ///
+ /// 测试场景实体配置
+ ///
+ public class TestScenarioConfiguration : IEntityTypeConfiguration
+ {
+ public void Configure(EntityTypeBuilder builder)
+ {
+ // 表名
+ builder.ToTable("tb_testscenarios");
+
+ // 主键
+ builder.HasKey(x => x.Id);
+
+ // 属性配置
+ builder.Property(x => x.Id)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.ScenarioCode)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.ScenarioName)
+ .IsRequired()
+ .HasMaxLength(200);
+
+ builder.Property(x => x.Type)
+ .IsRequired()
+ .HasConversion();
+
+ builder.Property(x => x.Description)
+ .HasMaxLength(1000);
+
+ builder.Property(x => x.IsEnabled)
+ .IsRequired()
+ .HasDefaultValue(true);
+
+ // 审计字段
+ builder.Property(x => x.CreatedAt)
+ .IsRequired();
+
+ builder.Property(x => x.UpdatedAt)
+ .IsRequired();
+
+ builder.Property(x => x.CreatedBy)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.UpdatedBy)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ // 索引
+ builder.HasIndex(x => x.ScenarioCode)
+ .IsUnique();
+
+ builder.HasIndex(x => x.Type);
+
+ builder.HasIndex(x => x.IsEnabled);
+
+ // 关系配置
+ builder.HasMany(x => x.ScenarioTestCases)
+ .WithOne(x => x.Scenario)
+ .HasForeignKey(x => x.ScenarioId)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
+ }
+}
diff --git a/src/X1.Infrastructure/Context/AppDbContext.cs b/src/X1.Infrastructure/Context/AppDbContext.cs
index 37478d8..949762e 100644
--- a/src/X1.Infrastructure/Context/AppDbContext.cs
+++ b/src/X1.Infrastructure/Context/AppDbContext.cs
@@ -129,6 +129,16 @@ public class AppDbContext : IdentityDbContext
///
public DbSet ImsiRegistrationRecords { get; set; } = null!;
+ ///
+ /// 测试场景集合
+ ///
+ public DbSet TestScenarios { get; set; } = null!;
+
+ ///
+ /// 场景测试用例集合
+ ///
+ public DbSet ScenarioTestCases { get; set; } = null!;
+
///
/// 初始化数据库上下文
///
diff --git a/src/X1.Infrastructure/DependencyInjection.cs b/src/X1.Infrastructure/DependencyInjection.cs
index c1870d4..7778c53 100644
--- a/src/X1.Infrastructure/DependencyInjection.cs
+++ b/src/X1.Infrastructure/DependencyInjection.cs
@@ -241,6 +241,8 @@ public static class DependencyInjection
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
return services;
}
diff --git a/src/X1.Infrastructure/Repositories/TestCase/ScenarioTestCaseRepository.cs b/src/X1.Infrastructure/Repositories/TestCase/ScenarioTestCaseRepository.cs
new file mode 100644
index 0000000..b42f942
--- /dev/null
+++ b/src/X1.Infrastructure/Repositories/TestCase/ScenarioTestCaseRepository.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.Base;
+using X1.Domain.Repositories.TestCase;
+using X1.Infrastructure.Repositories.Base;
+
+namespace X1.Infrastructure.Repositories.TestCase
+{
+ ///
+ /// 场景测试用例仓储实现
+ ///
+ public class ScenarioTestCaseRepository : BaseRepository, IScenarioTestCaseRepository
+ {
+ private readonly ILogger _logger;
+
+ public ScenarioTestCaseRepository(
+ ICommandRepository commandRepository,
+ IQueryRepository queryRepository,
+ ILogger logger)
+ : base(commandRepository, queryRepository, logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// 根据场景ID获取测试用例列表
+ ///
+ public async Task> GetByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default)
+ {
+ var testCases = await QueryRepository.FindAsync(x => x.ScenarioId == scenarioId, cancellationToken:cancellationToken);
+ return testCases.OrderBy(x => x.ExecutionOrder);
+ }
+
+ ///
+ /// 根据测试用例流程ID获取场景测试用例列表
+ ///
+ public async Task> GetByTestCaseFlowIdAsync(string testCaseFlowId, CancellationToken cancellationToken = default)
+ {
+ var testCases = await QueryRepository.FindAsync(x => x.TestCaseFlowId == testCaseFlowId, cancellationToken: cancellationToken);
+ return testCases.OrderBy(x => x.ExecutionOrder);
+ }
+
+ ///
+ /// 根据场景ID获取启用的测试用例列表
+ ///
+ public async Task> GetEnabledByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default)
+ {
+ var testCases = await QueryRepository.FindAsync(x => x.ScenarioId == scenarioId && x.IsEnabled, cancellationToken: cancellationToken);
+ return testCases.OrderBy(x => x.ExecutionOrder);
+ }
+
+ ///
+ /// 根据场景ID和测试用例流程ID获取场景测试用例
+ ///
+ public async Task GetByScenarioAndTestCaseFlowAsync(string scenarioId, string testCaseFlowId, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.FirstOrDefaultAsync(x => x.ScenarioId == scenarioId && x.TestCaseFlowId == testCaseFlowId, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 检查场景中是否已存在指定的测试用例
+ ///
+ public async Task ExistsInScenarioAsync(string scenarioId, string testCaseFlowId, string? excludeId = null, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(excludeId))
+ {
+ return await QueryRepository.AnyAsync(x => x.ScenarioId == scenarioId && x.TestCaseFlowId == testCaseFlowId, cancellationToken: cancellationToken);
+ }
+ else
+ {
+ return await QueryRepository.AnyAsync(x => x.ScenarioId == scenarioId && x.TestCaseFlowId == testCaseFlowId && x.Id != excludeId, cancellationToken: cancellationToken);
+ }
+ }
+
+ ///
+ /// 根据场景ID获取包含完整信息的测试用例列表
+ ///
+ public async Task> GetWithDetailsByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default)
+ {
+ var testCases = await QueryRepository.FindAsync(
+ x => x.ScenarioId == scenarioId,
+ query => query.Include(x => x.Scenario).Include(x => x.TestCaseFlow),
+ cancellationToken);
+ return testCases.OrderBy(x => x.ExecutionOrder);
+ }
+
+ ///
+ /// 根据场景ID删除所有测试用例
+ ///
+ public async Task DeleteByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default)
+ {
+ var testCases = await QueryRepository.FindAsync(x => x.ScenarioId == scenarioId, cancellationToken: cancellationToken);
+ var testCaseList = testCases.ToList();
+
+ if (testCaseList.Any())
+ {
+ CommandRepository.DeleteRange(testCaseList);
+ }
+
+ return testCaseList.Count;
+ }
+
+ ///
+ /// 根据场景ID获取测试用例数量
+ ///
+ public async Task GetCountByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.CountAsync(x => x.ScenarioId == scenarioId, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 批量检查场景中已存在的测试用例流程ID
+ ///
+ public async Task> GetExistingTestCaseFlowIdsAsync(string scenarioId, IEnumerable testCaseFlowIds, CancellationToken cancellationToken = default)
+ {
+ var testCaseFlowIdList = testCaseFlowIds.ToList();
+ if (!testCaseFlowIdList.Any())
+ {
+ return new HashSet();
+ }
+
+ var existingTestCases = await QueryRepository.FindAsync(
+ x => x.ScenarioId == scenarioId && testCaseFlowIdList.Contains(x.TestCaseFlowId),
+ cancellationToken: cancellationToken);
+
+ return existingTestCases.Select(x => x.TestCaseFlowId).ToHashSet();
+ }
+ }
+}
diff --git a/src/X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs b/src/X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs
new file mode 100644
index 0000000..bd8d567
--- /dev/null
+++ b/src/X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using X1.Domain.Entities.TestCase;
+using X1.Domain.Repositories.Base;
+using X1.Domain.Repositories.TestCase;
+using X1.Infrastructure.Repositories.Base;
+
+namespace X1.Infrastructure.Repositories.TestCase
+{
+ ///
+ /// 测试场景仓储实现
+ ///
+ public class TestScenarioRepository : BaseRepository, ITestScenarioRepository
+ {
+ private readonly ILogger _logger;
+
+ public TestScenarioRepository(
+ ICommandRepository commandRepository,
+ IQueryRepository queryRepository,
+ ILogger logger)
+ : base(commandRepository, queryRepository, logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// 根据场景编码获取测试场景
+ ///
+ public async Task GetByScenarioCodeAsync(string scenarioCode, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.FirstOrDefaultAsync(x => x.ScenarioCode == scenarioCode, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 根据场景类型获取测试场景列表
+ ///
+ public async Task> GetByTypeAsync(ScenarioType type, CancellationToken cancellationToken = default)
+ {
+ var scenarios = await QueryRepository.FindAsync(x => x.Type == type, cancellationToken: cancellationToken);
+ return scenarios.OrderBy(x => x.ScenarioCode);
+ }
+
+ ///
+ /// 获取启用的测试场景列表
+ ///
+ public async Task> GetEnabledAsync(CancellationToken cancellationToken = default)
+ {
+ var scenarios = await QueryRepository.FindAsync(x => x.IsEnabled, cancellationToken: cancellationToken);
+ return scenarios.OrderBy(x => x.ScenarioCode);
+ }
+
+ ///
+ /// 检查场景编码是否存在
+ ///
+ public async Task ExistsByScenarioCodeAsync(string scenarioCode, string? excludeId = null, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(excludeId))
+ {
+ return await QueryRepository.AnyAsync(x => x.ScenarioCode == scenarioCode, cancellationToken: cancellationToken);
+ }
+ else
+ {
+ return await QueryRepository.AnyAsync(x => x.ScenarioCode == scenarioCode && x.Id != excludeId, cancellationToken: cancellationToken);
+ }
+ }
+
+ ///
+ /// 根据场景ID获取包含测试用例的完整场景信息
+ ///
+ public async Task GetWithTestCasesAsync(string scenarioId, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.GetByIdAsync(scenarioId,
+ query => query.Include(x => x.ScenarioTestCases).ThenInclude(stc => stc.TestCaseFlow),
+ cancellationToken);
+ }
+
+ ///
+ /// 获取所有包含测试用例的场景列表
+ ///
+ public async Task> GetAllWithTestCasesAsync(CancellationToken cancellationToken = default)
+ {
+ var scenarios = await QueryRepository.GetAllAsync(
+ query => query.Include(x => x.ScenarioTestCases).ThenInclude(stc => stc.TestCaseFlow),
+ cancellationToken: cancellationToken);
+ return scenarios.OrderBy(x => x.ScenarioCode);
+ }
+ }
+}
diff --git a/src/X1.Presentation/Controllers/TestCaseFlowController.cs b/src/X1.Presentation/Controllers/TestCaseFlowController.cs
index b7f2483..23f6c93 100644
--- a/src/X1.Presentation/Controllers/TestCaseFlowController.cs
+++ b/src/X1.Presentation/Controllers/TestCaseFlowController.cs
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlows;
using X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlowById;
+using X1.Application.Features.TestCaseFlow.Queries.GetFormTypeStepTypeMapping;
using X1.Application.Features.TestCaseFlow.Commands.CreateTestCaseFlow;
using X1.Application.Features.TestCaseFlow.Commands.DeleteTestCaseFlow;
using X1.Presentation.Abstractions;
@@ -141,4 +142,27 @@ public class TestCaseFlowController : ApiController
_logger.LogInformation("成功删除测试用例流程,流程ID: {Id}", id);
return result;
}
+
+ ///
+ /// 获取测试流程类型列表
+ ///
+ /// 测试流程类型数据
+ [HttpGet("form-type-step")]
+ public async Task> GetFormTypeStepTypeMapping()
+ {
+ _logger.LogInformation("获取测试流程类型列表");
+
+ var query = new GetFormTypeStepTypeMappingQuery();
+ var result = await mediator.Send(query);
+ if (!result.IsSuccess)
+ {
+ _logger.LogWarning("获取测试流程类型列表失败: {ErrorMessages}",
+ string.Join(", ", result.ErrorMessages ?? new List()));
+ return result;
+ }
+
+ _logger.LogInformation("成功获取测试流程类型列表,测试流程类型数量: {TestFlowTypeCount}",
+ result.Data?.TestFlowTypes.Count);
+ return result;
+ }
}
diff --git a/src/modify.md b/src/modify.md
index 1071b33..565ef4a 100644
--- a/src/modify.md
+++ b/src/modify.md
@@ -1,5 +1,275 @@
# 修改记录
+## 2024-12-19 - 新增 TestScenariosController
+
+### 修改内容
+基于 Features.TestScenarios 实现完整的控制器,参考 TestCaseFlowController 的实现模式。
+
+#### 实现的功能:
+1. **获取测试场景列表** (`GET /api/testscenarios`)
+ - 支持搜索关键词过滤
+ - 支持场景类型过滤
+ - 支持启用状态过滤
+ - 支持分页查询
+
+2. **获取测试场景详情** (`GET /api/testscenarios/{id}`)
+ - 根据ID获取单个测试场景详情
+ - 支持可选包含测试用例数据
+
+3. **创建测试场景** (`POST /api/testscenarios`)
+ - 创建新的测试场景
+ - 支持场景名称、类型、描述、启用状态等属性
+
+4. **更新测试场景** (`PUT /api/testscenarios/{id}`)
+ - 更新现有测试场景的描述和启用状态
+ - 自动同步路由参数中的ID
+
+5. **删除测试场景** (`DELETE /api/testscenarios/{id}`)
+ - 删除指定的测试场景
+
+#### 技术特点:
+- 继承自 `ApiController` 基类
+- 使用 MediatR 进行命令查询分离
+- 完整的日志记录
+- 统一的错误处理
+- 支持授权访问
+- 遵循 RESTful API 设计规范
+
+#### 使用的命名空间:
+- `X1.Application.Features.TestScenarios.Queries.*`
+- `X1.Application.Features.TestScenarios.Commands.*`
+- `X1.Presentation.Abstractions`
+- `X1.Domain.Common`
+
+#### 修改的文件:
+- `X1.Presentation/Controllers/TestScenariosController.cs` (新增)
+
+---
+
+## 2024-12-19 - 简化仓储异常处理,移除 try-catch 块
+
+**问题描述:**
+仓储中的 try-catch 异常处理过于冗余,增加了代码复杂度,且异常最终还是要抛给上一级处理。
+
+**问题分析:**
+1. 仓储层的 try-catch 块只是记录日志后重新抛出异常
+2. 异常处理应该在应用层统一处理
+3. 简化代码可以提高可读性和维护性
+
+**修复内容:**
+- 文件:`X1.Infrastructure/Repositories/TestCase/ScenarioTestCaseRepository.cs`
+ - 移除所有方法的 try-catch 异常处理块
+ - 保留核心业务逻辑,直接让异常抛给上一级
+- 文件:`X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs`
+ - 移除所有方法的 try-catch 异常处理块
+ - 保留核心业务逻辑,直接让异常抛给上一级
+
+**技术说明:**
+- 遵循"让异常冒泡"的原则,在应用层统一处理异常
+- 简化仓储层代码,专注于数据访问逻辑
+- 提高代码的可读性和维护性
+- 异常日志记录应该在应用层或中间件中统一处理
+
+---
+
+## 2024-12-19 - 修复仓储文件缺少 Entity Framework Core using 指令问题
+
+**问题描述:**
+在仓储文件中使用 `Include` 扩展方法时出现编译错误:
+- `ScenarioTestCaseRepository.cs` 第129行:`"ScenarioTestCase"未包含"Include"的定义`
+- `TestScenarioRepository.cs` 第111行和第129行:`"IQueryable"未包含"Include"的定义`
+
+**问题分析:**
+1. 仓储文件中使用了 Entity Framework Core 的 `Include` 扩展方法
+2. 缺少 `Microsoft.EntityFrameworkCore` 的 using 指令
+3. `Include` 和 `ThenInclude` 方法需要 Entity Framework Core 命名空间
+
+**修复内容:**
+- 文件:`X1.Infrastructure/Repositories/TestCase/ScenarioTestCaseRepository.cs`
+ - 添加 `using Microsoft.EntityFrameworkCore;` 指令
+- 文件:`X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs`
+ - 添加 `using Microsoft.EntityFrameworkCore;` 指令
+
+**技术说明:**
+- `Include` 方法用于预加载导航属性,避免 N+1 查询问题
+- `ThenInclude` 方法用于预加载嵌套的导航属性
+- 这些方法是 Entity Framework Core 的核心功能,需要正确的命名空间引用
+
+---
+
+## 2024-12-19 - 修复 TestScenario 实体缺失方法问题
+
+**问题描述:**
+在 `UpdateTestScenarioCommandHandler.cs` 中调用了 `TestScenario` 实体中不存在的方法:
+- `SetEnabled` 方法未定义
+- `UpdateDescription` 方法未定义
+- `UpdateAuditInfo` 方法未定义
+
+**问题分析:**
+1. `TestScenario` 实体中只有 `Update` 方法,但缺少单独的操作方法
+2. 命令处理器需要更细粒度的更新方法
+3. 需要遵循领域驱动设计原则,提供专门的业务方法
+
+**修复内容:**
+- 文件:`X1.Domain/Entities/TestCase/TestScenario.cs`
+- 添加了以下方法:
+ - `SetEnabled(bool isEnabled)` - 设置启用状态
+ - `UpdateDescription(string? description)` - 更新描述
+ - `UpdateAuditInfo(string updatedBy)` - 更新审计信息
+
+**技术说明:**
+- 遵循领域驱动设计原则,提供专门的业务方法
+- 保持实体的封装性,通过方法而不是直接属性赋值
+- 确保审计信息的正确更新
+- 支持部分更新操作,提高代码的可读性和维护性
+
+---
+
+## 2024-12-19 - 修复 DeleteTestScenarioCommandHandler 中的 DeleteAsync 方法调用错误
+
+**问题描述:**
+在 `DeleteTestScenarioCommandHandler.cs` 中使用了不存在的 `DeleteAsync(T entity)` 方法。
+
+**问题分析:**
+1. `IBaseRepository` 接口中没有 `DeleteAsync(T entity)` 方法
+2. 只有 `void Delete(T entity)` 同步方法
+3. 删除实体操作是同步的,因为它只是标记实体状态为已删除
+4. 实际的数据库删除操作由 `UnitOfWork.SaveChangesAsync()` 在事务提交时执行
+
+**修复内容:**
+- 文件:`X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommandHandler.cs`
+- 修改:将 `await _testScenarioRepository.DeleteAsync(testScenario, cancellationToken:cancellationToken);` 改为 `_testScenarioRepository.Delete(testScenario);`
+
+**技术说明:**
+- 删除操作使用同步方法 `Delete()` 是正确的,因为它只是标记实体状态
+- 真正的数据库操作在 `await _unitOfWork.SaveChangesAsync(cancellationToken);` 中执行
+- 这种设计符合 CQRS 模式和 Entity Framework 的工作机制
+- 不要为了异步而异步,只有真正需要等待 I/O 操作的方法才使用异步
+
+---
+
+## 2024-12-19 - 场景测试用例创建性能优化
+
+### 问题描述
+在 `CreateScenarioTestCaseCommandHandler` 中,批量创建测试用例时,在循环中逐个调用 `ExistsInScenarioAsync` 方法检查每个测试用例是否已存在,这会导致多次数据库查询,严重影响性能。
+
+### 解决方案
+1. **添加批量检查方法**:
+ - 在 `IScenarioTestCaseRepository` 接口中添加 `GetExistingTestCaseFlowIdsAsync` 方法
+ - 在 `ScenarioTestCaseRepository` 实现中添加对应的批量检查逻辑
+
+2. **优化命令处理器**:
+ - 在循环开始前,一次性批量查询所有已存在的测试用例流程ID
+ - 在循环中使用内存中的 HashSet 进行快速查找,避免重复数据库查询
+
+### 修改的文件
+1. `X1.Domain/Repositories/TestCase/IScenarioTestCaseRepository.cs`
+ - 添加 `GetExistingTestCaseFlowIdsAsync` 方法声明
+
+2. `X1.Infrastructure/Repositories/TestCase/ScenarioTestCaseRepository.cs`
+ - 实现 `GetExistingTestCaseFlowIdsAsync` 方法
+ - 使用 `Contains` 查询一次性获取所有已存在的测试用例流程ID
+
+3. `X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommandHandler.cs`
+ - 在循环前批量检查已存在的测试用例流程ID
+ - 将循环中的数据库查询改为内存中的 HashSet 查找
+
+### 性能提升
+- **优化前**:N次数据库查询(N为测试用例数量)
+- **优化后**:1次数据库查询 + N次内存查找
+- **性能提升**:显著减少数据库查询次数,提高批量创建性能
+
+### 测试建议
+1. 测试批量创建大量测试用例的场景
+2. 验证重复测试用例的正确处理
+3. 确认错误信息仍然正确返回
+
+---
+
+## 2025-01-21 - TestScenario 和 ScenarioTestCase 完整功能实现
+
+### 1. 创建 TestScenario 的 Application Features 实现
+
+**创建的文件:**
+
+1. **Commands/CreateTestScenario/**
+ - `CreateTestScenarioCommand.cs` - 创建测试场景命令(场景编码后台自动生成)
+ - `CreateTestScenarioResponse.cs` - 创建测试场景响应
+ - `CreateTestScenarioCommandHandler.cs` - 创建测试场景命令处理器
+
+2. **Commands/UpdateTestScenario/**
+ - `UpdateTestScenarioCommand.cs` - 更新测试场景命令(只允许修改描述和启用状态)
+ - `UpdateTestScenarioResponse.cs` - 更新测试场景响应
+ - `UpdateTestScenarioCommandHandler.cs` - 更新测试场景命令处理器
+
+3. **Commands/DeleteTestScenario/**
+ - `DeleteTestScenarioCommand.cs` - 删除测试场景命令
+ - `DeleteTestScenarioCommandHandler.cs` - 删除测试场景命令处理器
+
+4. **Queries/GetTestScenarios/**
+ - `GetTestScenariosQuery.cs` - 获取测试场景列表查询
+ - `GetTestScenariosResponse.cs` - 获取测试场景列表响应
+ - `GetTestScenariosQueryHandler.cs` - 获取测试场景列表查询处理器
+
+5. **Queries/GetTestScenarioById/**
+ - `GetTestScenarioByIdQuery.cs` - 根据ID获取测试场景查询
+ - `GetTestScenarioByIdResponse.cs` - 根据ID获取测试场景响应
+ - `GetTestScenarioByIdQueryHandler.cs` - 根据ID获取测试场景查询处理器
+
+**主要功能特性:**
+
+1. **场景编码自动生成**:根据场景类型生成唯一编码(FUNC_20241219_1234 格式)
+2. **字段保护**:场景编码、场景名称、场景类型创建后不允许修改
+3. **删除保护**:存在关联测试用例时不允许删除场景
+4. **完整CRUD操作**:创建、更新、删除、查询列表、查询详情
+5. **分页和过滤**:支持分页、搜索、类型过滤、启用状态过滤
+6. **关联数据查询**:支持查询场景时包含关联的测试用例信息
+
+### 2. 创建 ScenarioTestCase 的 Application Features 实现
+
+**创建的文件:**
+
+1. **Commands/CreateScenarioTestCase/**
+ - `CreateScenarioTestCaseCommand.cs` - 创建场景测试用例命令
+ - `CreateScenarioTestCaseResponse.cs` - 创建场景测试用例响应
+ - `CreateScenarioTestCaseCommandHandler.cs` - 创建场景测试用例命令处理器
+
+2. **Commands/BatchCreateScenarioTestCases/**
+ - `BatchCreateScenarioTestCasesCommand.cs` - 批量创建场景测试用例命令
+ - `BatchCreateScenarioTestCasesResponse.cs` - 批量创建场景测试用例响应
+ - `BatchCreateScenarioTestCasesCommandHandler.cs` - 批量创建场景测试用例命令处理器
+
+3. **Queries/GetScenarioTestCases/**
+ - `GetScenarioTestCasesQuery.cs` - 获取场景测试用例列表查询
+ - `GetScenarioTestCasesResponse.cs` - 获取场景测试用例列表响应
+ - `GetScenarioTestCasesQueryHandler.cs` - 获取场景测试用例列表查询处理器
+
+**主要功能特性:**
+
+1. **单个创建**:支持创建单个场景测试用例
+2. **批量创建**:支持批量创建多个场景测试用例,包含成功/失败统计
+3. **重复检查**:防止在同一场景中重复添加相同的测试用例
+4. **场景验证**:确保场景存在后才允许添加测试用例
+5. **详细信息查询**:支持查询包含测试用例流程名称的详细信息
+6. **过滤功能**:支持按启用状态过滤测试用例
+
+**设计架构遵循:**
+- 参考 `ProtocolVersions` 的设计模式
+- 使用 MediatR 的 CQRS 模式
+- 统一的错误处理和日志记录
+- 完整的审计信息记录
+- 遵循 DDD 设计原则
+
+### 3. 待完成的工作
+
+**后续步骤:**
+1. 创建对应的 Controller 层
+2. 添加单元测试
+3. 更新 API 文档
+4. 创建前端界面组件
+
+---
+
## 2024-12-19 - 修复 ScenarioType 类型找不到的问题
### 问题描述
@@ -2458,6 +2728,82 @@ const newNode = {
---
+## 2025-01-21 - 为 TestCaseFlow 添加 GetFormTypeStepTypeMapping 功能
+
+#### 修改文件:
+1. `X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQuery.cs` - 新增查询类
+2. `X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingResponse.cs` - 新增响应类
+3. `X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQueryHandler.cs` - 新增查询处理器
+4. `X1.Domain/Common/TestFlowTypeConverter.cs` - 新增 TestFlowType 转换器
+5. `X1.Domain/Entities/TestCase/TestFlowType.cs` - 为枚举添加 DisplayAttribute 和 DescriptionAttribute
+6. `X1.Presentation/Controllers/TestCaseFlowController.cs` - 添加新的 API 端点
+
+#### 修改内容:
+
+1. **查询功能实现**:
+ - 创建了 `GetFormTypeStepTypeMappingQuery` 查询类
+ - 创建了 `GetFormTypeStepTypeMappingResponse` 响应类,只包含测试流程类型
+ - 创建了 `GetFormTypeStepTypeMappingQueryHandler` 查询处理器
+
+2. **TestFlowType 转换器**:
+ - 创建了 `TestFlowTypeConverter` 静态类,提供 TestFlowType 的枚举值获取功能
+ - 包含获取测试流程类型列表的方法
+ - 提供测试流程类型的显示名称和描述
+
+3. **枚举属性增强**:
+ - 为 `TestFlowType` 枚举添加了 `DisplayAttribute` 和 `DescriptionAttribute`
+ - 提供了中文显示名称和详细描述
+
+4. **API 端点添加**:
+ - 在 `TestCaseFlowController` 中添加了 `GET /api/testcaseflow/form-type-step` 端点
+ - 返回测试流程类型列表数据
+
+5. **功能特性**:
+ - **简化响应**:只返回测试流程类型,不包含其他映射关系
+ - **枚举支持**:完整的 TestFlowType 枚举支持
+ - **显示名称**:提供中文显示名称和描述
+
+#### API 端点示例:
+```http
+GET /api/testcaseflow/form-type-step
+Authorization: Bearer {token}
+```
+
+#### 响应格式:
+```json
+{
+ "isSuccess": true,
+ "data": {
+ "testFlowTypes": [
+ {
+ "value": 1,
+ "name": "注册测试",
+ "description": "设备注册到网络的测试流程"
+ },
+ {
+ "value": 2,
+ "name": "语音测试",
+ "description": "语音通话相关的测试流程"
+ },
+ {
+ "value": 3,
+ "name": "数据测试",
+ "description": "数据传输性能相关的测试流程"
+ }
+ ]
+ },
+ "errorMessages": null
+}
+```
+
+#### 修改时间:
+2025-01-21
+
+#### 修改原因:
+用户要求为 `Features.TestCaseFlow` 添加一个类似 `GetFormTypeStepTypeMapping` 的功能,但只需要获取测试流程类型,不需要其他映射关系,用于前端界面中测试流程类型的配置和选择。
+
+---
+
## 2025-01-19 - 根据 TestCaseFlowController 修复 testcaseService.ts
#### 修改文件: