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 #### 修改文件: