Browse Source

feat: 重构IRanGainControlHandler接口并添加RAN API控制器

- 重构IRanGainControlHandler接口,移除RanIPEndPoint参数,统一从ICellularNetworkContext获取端点信息
- 修复SetTxGainCommand构造函数,添加参数验证
- 创建SetAllTxGainCommand和SetAllTxGainCommandHandler,支持批量设置发送增益
- 创建RanAPIController,提供统一的RAN API访问接口
- 优化依赖注入配置,统一使用工厂模式
release/web-ui-v1.0.0
root 4 months ago
parent
commit
b5a87a1bfb
  1. 53
      src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommand.cs
  2. 131
      src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseCommandHandler.cs
  3. 37
      src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseResponse.cs
  4. 27
      src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQuery.cs
  5. 98
      src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesQueryHandler.cs
  6. 83
      src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesResponse.cs
  7. 12
      src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQuery.cs
  8. 39
      src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingQueryHandler.cs
  9. 33
      src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingResponse.cs
  10. 36
      src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommand.cs
  11. 133
      src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommandHandler.cs
  12. 47
      src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs
  13. 17
      src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommand.cs
  14. 77
      src/X1.Application/Features/TestScenarios/Commands/DeleteTestScenario/DeleteTestScenarioCommandHandler.cs
  15. 28
      src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommand.cs
  16. 92
      src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioCommandHandler.cs
  17. 47
      src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioResponse.cs
  18. 22
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQuery.cs
  19. 99
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQueryHandler.cs
  20. 108
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs
  21. 40
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQuery.cs
  22. 111
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQueryHandler.cs
  23. 88
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs
  24. 231
      src/X1.Domain/Common/TestFlowTypeConverter.cs
  25. 16
      src/X1.Domain/Entities/TestCase/TestFlowType.cs
  26. 28
      src/X1.Domain/Entities/TestCase/TestScenario.cs
  27. 91
      src/X1.Domain/Repositories/TestCase/IScenarioTestCaseRepository.cs
  28. 62
      src/X1.Domain/Repositories/TestCase/ITestScenarioRepository.cs
  29. 82
      src/X1.Infrastructure/Configurations/TestCase/ScenarioTestCaseConfiguration.cs
  30. 74
      src/X1.Infrastructure/Configurations/TestCase/TestScenarioConfiguration.cs
  31. 10
      src/X1.Infrastructure/Context/AppDbContext.cs
  32. 2
      src/X1.Infrastructure/DependencyInjection.cs
  33. 135
      src/X1.Infrastructure/Repositories/TestCase/ScenarioTestCaseRepository.cs
  34. 93
      src/X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs
  35. 24
      src/X1.Presentation/Controllers/TestCaseFlowController.cs
  36. 346
      src/modify.md

53
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;
/// <summary>
/// 创建场景测试用例命令
/// </summary>
public class CreateScenarioTestCaseCommand : IRequest<OperationResult<CreateScenarioTestCaseResponse>>
{
/// <summary>
/// 场景ID
/// </summary>
[Required(ErrorMessage = "场景ID不能为空")]
public string ScenarioId { get; set; } = null!;
/// <summary>
/// 测试用例列表
/// </summary>
[Required(ErrorMessage = "测试用例列表不能为空")]
[MinLength(1, ErrorMessage = "至少需要包含一个测试用例")]
public List<ScenarioTestCaseItem> TestCases { get; set; } = new();
}
/// <summary>
/// 场景测试用例项
/// </summary>
public class ScenarioTestCaseItem
{
/// <summary>
/// 测试用例流程ID
/// </summary>
[Required(ErrorMessage = "测试用例流程ID不能为空")]
public string TestCaseFlowId { get; set; } = null!;
/// <summary>
/// 执行顺序
/// </summary>
[Range(0, int.MaxValue, ErrorMessage = "执行顺序必须大于等于0")]
public int ExecutionOrder { get; set; } = 0;
/// <summary>
/// 循环次数
/// </summary>
[Range(1, int.MaxValue, ErrorMessage = "循环次数必须大于0")]
public int LoopCount { get; set; } = 1;
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; } = true;
}

131
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;
/// <summary>
/// 创建场景测试用例命令处理器
/// </summary>
public class CreateScenarioTestCaseCommandHandler : IRequestHandler<CreateScenarioTestCaseCommand, OperationResult<CreateScenarioTestCaseResponse>>
{
private readonly IScenarioTestCaseRepository _scenarioTestCaseRepository;
private readonly ITestScenarioRepository _testScenarioRepository;
private readonly ILogger<CreateScenarioTestCaseCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public CreateScenarioTestCaseCommandHandler(
IScenarioTestCaseRepository scenarioTestCaseRepository,
ITestScenarioRepository testScenarioRepository,
ILogger<CreateScenarioTestCaseCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_scenarioTestCaseRepository = scenarioTestCaseRepository;
_testScenarioRepository = testScenarioRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
/// 处理创建场景测试用例命令
/// </summary>
public async Task<OperationResult<CreateScenarioTestCaseResponse>> 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<CreateScenarioTestCaseResponse>.CreateFailure("用户未认证,无法创建场景测试用例");
}
// 验证场景是否存在
var scenario = await _testScenarioRepository.GetByIdAsync(request.ScenarioId, cancellationToken:cancellationToken);
if (scenario == null)
{
_logger.LogWarning("测试场景不存在,场景ID: {ScenarioId}", request.ScenarioId);
return OperationResult<CreateScenarioTestCaseResponse>.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<CreateScenarioTestCaseResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建场景测试用例时发生错误,场景ID: {ScenarioId}", request.ScenarioId);
return OperationResult<CreateScenarioTestCaseResponse>.CreateFailure($"创建场景测试用例时发生错误: {ex.Message}");
}
}
}

37
src/X1.Application/Features/ScenarioTestCases/Commands/CreateScenarioTestCase/CreateScenarioTestCaseResponse.cs

@ -0,0 +1,37 @@
namespace X1.Application.Features.ScenarioTestCases.Commands.CreateScenarioTestCase;
/// <summary>
/// 创建场景测试用例响应
/// </summary>
public class CreateScenarioTestCaseResponse
{
/// <summary>
/// 成功创建的测试用例数量
/// </summary>
public int SuccessCount { get; set; }
/// <summary>
/// 失败的测试用例数量
/// </summary>
public int FailureCount { get; set; }
/// <summary>
/// 总数量
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 成功创建的测试用例ID列表
/// </summary>
public List<string> SuccessIds { get; set; } = new();
/// <summary>
/// 失败的错误信息
/// </summary>
public List<string> ErrorMessages { get; set; } = new();
/// <summary>
/// 是否全部成功
/// </summary>
public bool IsAllSuccess => FailureCount == 0;
}

27
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;
/// <summary>
/// 获取场景测试用例列表查询
/// </summary>
public class GetScenarioTestCasesQuery : IRequest<OperationResult<GetScenarioTestCasesResponse>>
{
/// <summary>
/// 场景ID
/// </summary>
[Required(ErrorMessage = "场景ID不能为空")]
public string ScenarioId { get; set; } = null!;
/// <summary>
/// 是否只获取启用的测试用例
/// </summary>
public bool? IsEnabled { get; set; }
/// <summary>
/// 是否包含详细信息
/// </summary>
public bool IncludeDetails { get; set; } = false;
}

98
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;
/// <summary>
/// 获取场景测试用例列表查询处理器
/// </summary>
public class GetScenarioTestCasesQueryHandler : IRequestHandler<GetScenarioTestCasesQuery, OperationResult<GetScenarioTestCasesResponse>>
{
private readonly IScenarioTestCaseRepository _scenarioTestCaseRepository;
private readonly ITestScenarioRepository _testScenarioRepository;
private readonly ILogger<GetScenarioTestCasesQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetScenarioTestCasesQueryHandler(
IScenarioTestCaseRepository scenarioTestCaseRepository,
ITestScenarioRepository testScenarioRepository,
ILogger<GetScenarioTestCasesQueryHandler> logger)
{
_scenarioTestCaseRepository = scenarioTestCaseRepository;
_testScenarioRepository = testScenarioRepository;
_logger = logger;
}
/// <summary>
/// 处理获取场景测试用例列表查询
/// </summary>
public async Task<OperationResult<GetScenarioTestCasesResponse>> 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<GetScenarioTestCasesResponse>.CreateFailure($"测试场景不存在,场景ID: {request.ScenarioId}");
}
// 获取场景测试用例
IEnumerable<ScenarioTestCase> 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<GetScenarioTestCasesResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取场景测试用例列表时发生错误,场景ID: {ScenarioId}", request.ScenarioId);
return OperationResult<GetScenarioTestCasesResponse>.CreateFailure($"获取场景测试用例列表时发生错误: {ex.Message}");
}
}
}

83
src/X1.Application/Features/ScenarioTestCases/Queries/GetScenarioTestCases/GetScenarioTestCasesResponse.cs

@ -0,0 +1,83 @@
namespace X1.Application.Features.ScenarioTestCases.Queries.GetScenarioTestCases;
/// <summary>
/// 获取场景测试用例列表响应
/// </summary>
public class GetScenarioTestCasesResponse
{
/// <summary>
/// 场景ID
/// </summary>
public string ScenarioId { get; set; } = null!;
/// <summary>
/// 场景测试用例列表
/// </summary>
public List<ScenarioTestCaseDto> TestCases { get; set; } = new();
/// <summary>
/// 总数量
/// </summary>
public int TotalCount { get; set; }
}
/// <summary>
/// 场景测试用例数据传输对象
/// </summary>
public class ScenarioTestCaseDto
{
/// <summary>
/// 场景测试用例ID
/// </summary>
public string ScenarioTestCaseId { get; set; } = null!;
/// <summary>
/// 场景ID
/// </summary>
public string ScenarioId { get; set; } = null!;
/// <summary>
/// 测试用例流程ID
/// </summary>
public string TestCaseFlowId { get; set; } = null!;
/// <summary>
/// 测试用例流程名称
/// </summary>
public string? TestCaseFlowName { get; set; }
/// <summary>
/// 执行顺序
/// </summary>
public int ExecutionOrder { get; set; }
/// <summary>
/// 循环次数
/// </summary>
public int LoopCount { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 创建人
/// </summary>
public string CreatedBy { get; set; } = null!;
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 更新人
/// </summary>
public string UpdatedBy { get; set; } = null!;
}

12
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;
/// <summary>
/// 获取表单类型到步骤类型映射查询
/// </summary>
public class GetFormTypeStepTypeMappingQuery : IRequest<OperationResult<GetFormTypeStepTypeMappingResponse>>
{
// 无需额外参数,返回所有映射关系
}

39
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;
/// <summary>
/// 获取表单类型到步骤类型映射查询处理器
/// </summary>
public class GetFormTypeStepTypeMappingQueryHandler : IRequestHandler<GetFormTypeStepTypeMappingQuery, OperationResult<GetFormTypeStepTypeMappingResponse>>
{
/// <summary>
/// 处理查询
/// </summary>
/// <param name="request">查询请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>操作结果</returns>
public async Task<OperationResult<GetFormTypeStepTypeMappingResponse>> 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<GetFormTypeStepTypeMappingResponse>.CreateSuccess(response));
}
catch (Exception ex)
{
return await Task.FromResult(OperationResult<GetFormTypeStepTypeMappingResponse>.CreateFailure($"获取测试流程类型失败: {ex.Message}"));
}
}
}

33
src/X1.Application/Features/TestCaseFlow/Queries/GetFormTypeStepTypeMapping/GetFormTypeStepTypeMappingResponse.cs

@ -0,0 +1,33 @@
namespace X1.Application.Features.TestCaseFlow.Queries.GetFormTypeStepTypeMapping;
/// <summary>
/// 获取测试流程类型响应
/// </summary>
public class GetFormTypeStepTypeMappingResponse
{
/// <summary>
/// 测试流程类型列表
/// </summary>
public List<TestFlowTypeDto> TestFlowTypes { get; set; } = new();
}
/// <summary>
/// 测试流程类型DTO
/// </summary>
public class TestFlowTypeDto
{
/// <summary>
/// 测试流程类型值
/// </summary>
public int Value { get; set; }
/// <summary>
/// 测试流程类型名称
/// </summary>
public string Name { get; set; } = null!;
/// <summary>
/// 测试流程类型描述
/// </summary>
public string Description { get; set; } = null!;
}

36
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;
/// <summary>
/// 创建测试场景命令
/// </summary>
public class CreateTestScenarioCommand : IRequest<OperationResult<CreateTestScenarioResponse>>
{
/// <summary>
/// 场景名称
/// </summary>
[Required(ErrorMessage = "场景名称不能为空")]
[MaxLength(200, ErrorMessage = "场景名称不能超过200个字符")]
public string ScenarioName { get; set; } = null!;
/// <summary>
/// 场景类型
/// </summary>
[Required(ErrorMessage = "场景类型不能为空")]
public ScenarioType Type { get; set; } = ScenarioType.Functional;
/// <summary>
/// 场景描述
/// </summary>
[MaxLength(1000, ErrorMessage = "场景描述不能超过1000个字符")]
public string? Description { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; } = true;
}

133
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;
/// <summary>
/// 创建测试场景命令处理器
/// </summary>
public class CreateTestScenarioCommandHandler : IRequestHandler<CreateTestScenarioCommand, OperationResult<CreateTestScenarioResponse>>
{
private readonly ITestScenarioRepository _testScenarioRepository;
private readonly ILogger<CreateTestScenarioCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public CreateTestScenarioCommandHandler(
ITestScenarioRepository testScenarioRepository,
ILogger<CreateTestScenarioCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_testScenarioRepository = testScenarioRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
/// 处理创建测试场景命令
/// </summary>
public async Task<OperationResult<CreateTestScenarioResponse>> 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<CreateTestScenarioResponse>.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<CreateTestScenarioResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建测试场景时发生错误,场景名称: {ScenarioName}", request.ScenarioName);
return OperationResult<CreateTestScenarioResponse>.CreateFailure($"创建测试场景时发生错误: {ex.Message}");
}
}
/// <summary>
/// 生成场景编码
/// </summary>
private async Task<string> 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;
}
}

47
src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs

@ -0,0 +1,47 @@
namespace X1.Application.Features.TestScenarios.Commands.CreateTestScenario;
/// <summary>
/// 创建测试场景响应
/// </summary>
public class CreateTestScenarioResponse
{
/// <summary>
/// 场景ID
/// </summary>
public string TestScenarioId { get; set; } = null!;
/// <summary>
/// 场景编码
/// </summary>
public string ScenarioCode { get; set; } = null!;
/// <summary>
/// 场景名称
/// </summary>
public string ScenarioName { get; set; } = null!;
/// <summary>
/// 场景类型
/// </summary>
public string Type { get; set; } = null!;
/// <summary>
/// 场景描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 创建人
/// </summary>
public string CreatedBy { get; set; } = null!;
}

17
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;
/// <summary>
/// 删除测试场景命令
/// </summary>
public class DeleteTestScenarioCommand : IRequest<OperationResult<bool>>
{
/// <summary>
/// 场景ID
/// </summary>
[Required(ErrorMessage = "场景ID不能为空")]
public string TestScenarioId { get; set; } = null!;
}

77
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;
/// <summary>
/// 删除测试场景命令处理器
/// </summary>
public class DeleteTestScenarioCommandHandler : IRequestHandler<DeleteTestScenarioCommand, OperationResult<bool>>
{
private readonly ITestScenarioRepository _testScenarioRepository;
private readonly IScenarioTestCaseRepository _scenarioTestCaseRepository;
private readonly ILogger<DeleteTestScenarioCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 初始化命令处理器
/// </summary>
public DeleteTestScenarioCommandHandler(
ITestScenarioRepository testScenarioRepository,
IScenarioTestCaseRepository scenarioTestCaseRepository,
ILogger<DeleteTestScenarioCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_testScenarioRepository = testScenarioRepository;
_scenarioTestCaseRepository = scenarioTestCaseRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
/// <summary>
/// 处理删除测试场景命令
/// </summary>
public async Task<OperationResult<bool>> 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<bool>.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<bool>.CreateFailure($"测试场景存在 {testCaseCount} 个关联的测试用例,无法删除");
}
// 删除测试场景
_testScenarioRepository.Delete(testScenario);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("测试场景删除成功,场景ID: {TestScenarioId}, 场景编码: {ScenarioCode}",
testScenario.Id, testScenario.ScenarioCode);
return OperationResult<bool>.CreateSuccess(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除测试场景时发生错误,场景ID: {TestScenarioId}", request.TestScenarioId);
return OperationResult<bool>.CreateFailure($"删除测试场景时发生错误: {ex.Message}");
}
}
}

28
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;
/// <summary>
/// 更新测试场景命令
/// </summary>
public class UpdateTestScenarioCommand : IRequest<OperationResult<UpdateTestScenarioResponse>>
{
/// <summary>
/// 场景ID
/// </summary>
[Required(ErrorMessage = "场景ID不能为空")]
public string TestScenarioId { get; set; } = null!;
/// <summary>
/// 场景描述
/// </summary>
[MaxLength(1000, ErrorMessage = "场景描述不能超过1000个字符")]
public string? Description { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; } = true;
}

92
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;
/// <summary>
/// 更新测试场景命令处理器
/// </summary>
public class UpdateTestScenarioCommandHandler : IRequestHandler<UpdateTestScenarioCommand, OperationResult<UpdateTestScenarioResponse>>
{
private readonly ITestScenarioRepository _testScenarioRepository;
private readonly ILogger<UpdateTestScenarioCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public UpdateTestScenarioCommandHandler(
ITestScenarioRepository testScenarioRepository,
ILogger<UpdateTestScenarioCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_testScenarioRepository = testScenarioRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
/// 处理更新测试场景命令
/// </summary>
public async Task<OperationResult<UpdateTestScenarioResponse>> 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<UpdateTestScenarioResponse>.CreateFailure("用户未认证,无法更新测试场景");
}
// 查找测试场景
var testScenario = await _testScenarioRepository.GetByIdAsync(request.TestScenarioId, cancellationToken:cancellationToken);
if (testScenario == null)
{
_logger.LogWarning("测试场景不存在,场景ID: {TestScenarioId}", request.TestScenarioId);
return OperationResult<UpdateTestScenarioResponse>.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<UpdateTestScenarioResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "更新测试场景时发生错误,场景ID: {TestScenarioId}", request.TestScenarioId);
return OperationResult<UpdateTestScenarioResponse>.CreateFailure($"更新测试场景时发生错误: {ex.Message}");
}
}
}

47
src/X1.Application/Features/TestScenarios/Commands/UpdateTestScenario/UpdateTestScenarioResponse.cs

@ -0,0 +1,47 @@
namespace X1.Application.Features.TestScenarios.Commands.UpdateTestScenario;
/// <summary>
/// 更新测试场景响应
/// </summary>
public class UpdateTestScenarioResponse
{
/// <summary>
/// 场景ID
/// </summary>
public string TestScenarioId { get; set; } = null!;
/// <summary>
/// 场景编码
/// </summary>
public string ScenarioCode { get; set; } = null!;
/// <summary>
/// 场景名称
/// </summary>
public string ScenarioName { get; set; } = null!;
/// <summary>
/// 场景类型
/// </summary>
public string Type { get; set; } = null!;
/// <summary>
/// 场景描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 更新人
/// </summary>
public string UpdatedBy { get; set; } = null!;
}

22
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;
/// <summary>
/// 根据ID获取测试场景查询
/// </summary>
public class GetTestScenarioByIdQuery : IRequest<OperationResult<GetTestScenarioByIdResponse>>
{
/// <summary>
/// 场景ID
/// </summary>
[Required(ErrorMessage = "场景ID不能为空")]
public string TestScenarioId { get; set; } = null!;
/// <summary>
/// 是否包含测试用例
/// </summary>
public bool IncludeTestCases { get; set; } = false;
}

99
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;
/// <summary>
/// 根据ID获取测试场景查询处理器
/// </summary>
public class GetTestScenarioByIdQueryHandler : IRequestHandler<GetTestScenarioByIdQuery, OperationResult<GetTestScenarioByIdResponse>>
{
private readonly ITestScenarioRepository _testScenarioRepository;
private readonly ILogger<GetTestScenarioByIdQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetTestScenarioByIdQueryHandler(
ITestScenarioRepository testScenarioRepository,
ILogger<GetTestScenarioByIdQueryHandler> logger)
{
_testScenarioRepository = testScenarioRepository;
_logger = logger;
}
/// <summary>
/// 处理根据ID获取测试场景查询
/// </summary>
public async Task<OperationResult<GetTestScenarioByIdResponse>> 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<GetTestScenarioByIdResponse>.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<GetTestScenarioByIdResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取测试场景详情时发生错误,场景ID: {TestScenarioId}", request.TestScenarioId);
return OperationResult<GetTestScenarioByIdResponse>.CreateFailure($"获取测试场景详情时发生错误: {ex.Message}");
}
}
}

108
src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs

@ -0,0 +1,108 @@
namespace X1.Application.Features.TestScenarios.Queries.GetTestScenarioById;
/// <summary>
/// 根据ID获取测试场景响应
/// </summary>
public class GetTestScenarioByIdResponse
{
/// <summary>
/// 场景ID
/// </summary>
public string TestScenarioId { get; set; } = null!;
/// <summary>
/// 场景编码
/// </summary>
public string ScenarioCode { get; set; } = null!;
/// <summary>
/// 场景名称
/// </summary>
public string ScenarioName { get; set; } = null!;
/// <summary>
/// 场景类型
/// </summary>
public string Type { get; set; } = null!;
/// <summary>
/// 场景描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 创建人
/// </summary>
public string CreatedBy { get; set; } = null!;
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 更新人
/// </summary>
public string UpdatedBy { get; set; } = null!;
/// <summary>
/// 测试用例列表
/// </summary>
public List<ScenarioTestCaseDto>? TestCases { get; set; }
}
/// <summary>
/// 场景测试用例数据传输对象
/// </summary>
public class ScenarioTestCaseDto
{
/// <summary>
/// 场景测试用例ID
/// </summary>
public string ScenarioTestCaseId { get; set; } = null!;
/// <summary>
/// 测试用例流程ID
/// </summary>
public string TestCaseFlowId { get; set; } = null!;
/// <summary>
/// 测试用例流程名称
/// </summary>
public string TestCaseFlowName { get; set; } = null!;
/// <summary>
/// 执行顺序
/// </summary>
public int ExecutionOrder { get; set; }
/// <summary>
/// 循环次数
/// </summary>
public int LoopCount { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 创建人
/// </summary>
public string CreatedBy { get; set; } = null!;
}

40
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;
/// <summary>
/// 获取测试场景列表查询
/// </summary>
public class GetTestScenariosQuery : IRequest<OperationResult<GetTestScenariosResponse>>
{
/// <summary>
/// 页码
/// </summary>
[Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")]
public int PageNumber { get; set; } = 1;
/// <summary>
/// 每页数量
/// </summary>
[Range(1, 1000, ErrorMessage = "每页数量必须在1-1000之间")]
public int PageSize { get; set; } = 10;
/// <summary>
/// 搜索关键词
/// </summary>
[MaxLength(100)]
public string? SearchTerm { get; set; }
/// <summary>
/// 场景类型
/// </summary>
public ScenarioType? Type { get; set; }
/// <summary>
/// 是否只获取启用的场景
/// </summary>
public bool? IsEnabled { get; set; }
}

111
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;
/// <summary>
/// 获取测试场景列表查询处理器
/// </summary>
public class GetTestScenariosQueryHandler : IRequestHandler<GetTestScenariosQuery, OperationResult<GetTestScenariosResponse>>
{
private readonly ITestScenarioRepository _testScenarioRepository;
private readonly ILogger<GetTestScenariosQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetTestScenariosQueryHandler(
ITestScenarioRepository testScenarioRepository,
ILogger<GetTestScenariosQueryHandler> logger)
{
_testScenarioRepository = testScenarioRepository;
_logger = logger;
}
/// <summary>
/// 处理获取测试场景列表查询
/// </summary>
public async Task<OperationResult<GetTestScenariosResponse>> 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<GetTestScenariosResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取测试场景列表时发生错误");
return OperationResult<GetTestScenariosResponse>.CreateFailure($"获取测试场景列表时发生错误: {ex.Message}");
}
}
}

88
src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs

@ -0,0 +1,88 @@
namespace X1.Application.Features.TestScenarios.Queries.GetTestScenarios;
/// <summary>
/// 获取测试场景列表响应
/// </summary>
public class GetTestScenariosResponse
{
/// <summary>
/// 测试场景列表
/// </summary>
public List<TestScenarioDto> TestScenarios { get; set; } = new();
/// <summary>
/// 总数量
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 页码
/// </summary>
public int PageNumber { get; set; }
/// <summary>
/// 每页数量
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// 总页数
/// </summary>
public int TotalPages { get; set; }
}
/// <summary>
/// 测试场景数据传输对象
/// </summary>
public class TestScenarioDto
{
/// <summary>
/// 场景ID
/// </summary>
public string TestScenarioId { get; set; } = null!;
/// <summary>
/// 场景编码
/// </summary>
public string ScenarioCode { get; set; } = null!;
/// <summary>
/// 场景名称
/// </summary>
public string ScenarioName { get; set; } = null!;
/// <summary>
/// 场景类型
/// </summary>
public string Type { get; set; } = null!;
/// <summary>
/// 场景描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 创建人
/// </summary>
public string CreatedBy { get; set; } = null!;
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 更新人
/// </summary>
public string UpdatedBy { get; set; } = null!;
}

231
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;
/// <summary>
/// 测试流程类型转换器
/// </summary>
public static class TestFlowTypeConverter
{
/// <summary>
/// 根据测试流程类型获取推荐的表单类型
/// </summary>
/// <param name="testFlowType">测试流程类型</param>
/// <returns>推荐的表单类型</returns>
public static FormType GetRecommendedFormType(TestFlowType testFlowType)
{
return testFlowType switch
{
TestFlowType.Registration => FormType.DeviceRegistrationForm,
TestFlowType.Voice => FormType.VoiceCallForm,
TestFlowType.Data => FormType.NetworkPerformanceForm,
_ => FormType.NoForm
};
}
/// <summary>
/// 根据测试流程类型获取推荐的步骤类型
/// </summary>
/// <param name="testFlowType">测试流程类型</param>
/// <returns>推荐的步骤类型</returns>
public static CaseStepType GetRecommendedStepType(TestFlowType testFlowType)
{
return testFlowType switch
{
TestFlowType.Registration => CaseStepType.Process,
TestFlowType.Voice => CaseStepType.Process,
TestFlowType.Data => CaseStepType.Process,
_ => CaseStepType.Process
};
}
/// <summary>
/// 获取测试流程类型与表单类型的映射关系
/// </summary>
/// <returns>测试流程类型到表单类型的映射字典</returns>
public static Dictionary<TestFlowType, FormType> GetTestFlowTypeToFormTypeMapping()
{
return new Dictionary<TestFlowType, FormType>
{
{ TestFlowType.Registration, FormType.DeviceRegistrationForm },
{ TestFlowType.Voice, FormType.VoiceCallForm },
{ TestFlowType.Data, FormType.NetworkPerformanceForm }
};
}
/// <summary>
/// 获取测试流程类型与步骤类型的映射关系
/// </summary>
/// <returns>测试流程类型到步骤类型的映射字典</returns>
public static Dictionary<TestFlowType, CaseStepType> GetTestFlowTypeToStepTypeMapping()
{
return new Dictionary<TestFlowType, CaseStepType>
{
{ TestFlowType.Registration, CaseStepType.Process },
{ TestFlowType.Voice, CaseStepType.Process },
{ TestFlowType.Data, CaseStepType.Process }
};
}
/// <summary>
/// 获取表单类型支持的测试流程类型列表
/// </summary>
/// <param name="formType">表单类型</param>
/// <returns>支持的测试流程类型列表</returns>
public static List<TestFlowType> GetSupportedTestFlowTypes(FormType formType)
{
return formType switch
{
FormType.NoForm => new List<TestFlowType> { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
FormType.DeviceRegistrationForm => new List<TestFlowType> { TestFlowType.Registration },
FormType.VoiceCallForm => new List<TestFlowType> { TestFlowType.Voice },
FormType.NetworkPerformanceForm => new List<TestFlowType> { TestFlowType.Data },
FormType.NetworkConnectivityForm => new List<TestFlowType> { TestFlowType.Data },
_ => new List<TestFlowType> { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data }
};
}
/// <summary>
/// 获取步骤类型支持的测试流程类型列表
/// </summary>
/// <param name="stepType">步骤类型</param>
/// <returns>支持的测试流程类型列表</returns>
public static List<TestFlowType> GetSupportedTestFlowTypes(CaseStepType stepType)
{
return stepType switch
{
CaseStepType.Start => new List<TestFlowType> { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
CaseStepType.End => new List<TestFlowType> { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
CaseStepType.Process => new List<TestFlowType> { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
CaseStepType.Decision => new List<TestFlowType> { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data },
_ => new List<TestFlowType> { TestFlowType.Registration, TestFlowType.Voice, TestFlowType.Data }
};
}
/// <summary>
/// 检查测试流程类型是否与表单类型兼容
/// </summary>
/// <param name="testFlowType">测试流程类型</param>
/// <param name="formType">表单类型</param>
/// <returns>是否兼容</returns>
public static bool IsCompatibleWithFormType(TestFlowType testFlowType, FormType formType)
{
var supportedTestFlowTypes = GetSupportedTestFlowTypes(formType);
return supportedTestFlowTypes.Contains(testFlowType);
}
/// <summary>
/// 检查测试流程类型是否与步骤类型兼容
/// </summary>
/// <param name="testFlowType">测试流程类型</param>
/// <param name="stepType">步骤类型</param>
/// <returns>是否兼容</returns>
public static bool IsCompatibleWithStepType(TestFlowType testFlowType, CaseStepType stepType)
{
var supportedTestFlowTypes = GetSupportedTestFlowTypes(stepType);
return supportedTestFlowTypes.Contains(testFlowType);
}
/// <summary>
/// 获取所有测试流程类型及其描述
/// </summary>
/// <returns>测试流程类型描述字典</returns>
public static Dictionary<TestFlowType, string> GetTestFlowTypeDescriptions()
{
return new Dictionary<TestFlowType, string>
{
{ TestFlowType.Registration, TestFlowType.Registration.GetDisplayName() },
{ TestFlowType.Voice, TestFlowType.Voice.GetDisplayName() },
{ TestFlowType.Data, TestFlowType.Data.GetDisplayName() }
};
}
/// <summary>
/// 获取所有测试流程类型
/// </summary>
/// <returns>测试流程类型列表</returns>
public static List<EnumValueObject> GetTestFlowTypes()
{
var testFlowTypes = new List<EnumValueObject>();
foreach (TestFlowType testFlowType in Enum.GetValues(typeof(TestFlowType)))
{
var fieldInfo = typeof(TestFlowType).GetField(testFlowType.ToString());
if (fieldInfo != null)
{
var displayAttribute = fieldInfo.GetCustomAttribute<DisplayAttribute>();
var descriptionAttribute = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
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;
}
/// <summary>
/// 根据测试流程类型获取推荐的步骤映射
/// </summary>
/// <param name="testFlowType">测试流程类型</param>
/// <returns>推荐的步骤映射列表</returns>
public static List<StepMapping> GetRecommendedStepMappings(TestFlowType testFlowType)
{
return testFlowType switch
{
TestFlowType.Registration => new List<StepMapping>
{
StepMapping.StartFlow,
StepMapping.ImsiRegistration,
StepMapping.EndFlow
},
TestFlowType.Voice => new List<StepMapping>
{
StepMapping.StartFlow,
StepMapping.MoCall,
StepMapping.MtCall,
StepMapping.HangUpCall,
StepMapping.EndFlow
},
TestFlowType.Data => new List<StepMapping>
{
StepMapping.StartFlow,
StepMapping.PingTest,
StepMapping.IperfTest,
StepMapping.EndFlow
},
_ => new List<StepMapping> { StepMapping.None }
};
}
/// <summary>
/// 根据步骤映射获取对应的测试流程类型
/// </summary>
/// <param name="mapping">步骤映射</param>
/// <returns>对应的测试流程类型</returns>
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
};
}
}

16
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
}
}

28
src/X1.Domain/Entities/TestCase/TestScenario.cs

@ -104,5 +104,33 @@ namespace X1.Domain.Entities.TestCase
UpdatedAt = DateTime.UtcNow;
UpdatedBy = updatedBy;
}
/// <summary>
/// 设置启用状态
/// </summary>
/// <param name="isEnabled">是否启用</param>
public void SetEnabled(bool isEnabled)
{
IsEnabled = isEnabled;
}
/// <summary>
/// 更新描述
/// </summary>
/// <param name="description">场景描述</param>
public void UpdateDescription(string? description)
{
Description = description;
}
/// <summary>
/// 更新审计信息
/// </summary>
/// <param name="updatedBy">更新人ID</param>
public void UpdateAuditInfo(string updatedBy)
{
UpdatedAt = DateTime.UtcNow;
UpdatedBy = updatedBy;
}
}
}

91
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
{
/// <summary>
/// 场景测试用例仓储接口
/// </summary>
public interface IScenarioTestCaseRepository : IBaseRepository<ScenarioTestCase>
{
/// <summary>
/// 根据场景ID获取测试用例列表
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例列表</returns>
Task<IEnumerable<ScenarioTestCase>> GetByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据测试用例流程ID获取场景测试用例列表
/// </summary>
/// <param name="testCaseFlowId">测试用例流程ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>场景测试用例列表</returns>
Task<IEnumerable<ScenarioTestCase>> GetByTestCaseFlowIdAsync(string testCaseFlowId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据场景ID获取启用的测试用例列表
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>启用的测试用例列表</returns>
Task<IEnumerable<ScenarioTestCase>> GetEnabledByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据场景ID和测试用例流程ID获取场景测试用例
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="testCaseFlowId">测试用例流程ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>场景测试用例</returns>
Task<ScenarioTestCase?> GetByScenarioAndTestCaseFlowAsync(string scenarioId, string testCaseFlowId, CancellationToken cancellationToken = default);
/// <summary>
/// 检查场景中是否已存在指定的测试用例
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="testCaseFlowId">测试用例流程ID</param>
/// <param name="excludeId">排除的ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否存在</returns>
Task<bool> ExistsInScenarioAsync(string scenarioId, string testCaseFlowId, string? excludeId = null, CancellationToken cancellationToken = default);
/// <summary>
/// 根据场景ID获取包含完整信息的测试用例列表
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>包含完整信息的测试用例列表</returns>
Task<IEnumerable<ScenarioTestCase>> GetWithDetailsByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据场景ID删除所有测试用例
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>删除的记录数</returns>
Task<int> DeleteByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据场景ID获取测试用例数量
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例数量</returns>
Task<int> GetCountByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default);
/// <summary>
/// 批量检查场景中已存在的测试用例流程ID
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="testCaseFlowIds">要检查的测试用例流程ID列表</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>已存在的测试用例流程ID集合</returns>
Task<HashSet<string>> GetExistingTestCaseFlowIdsAsync(string scenarioId, IEnumerable<string> testCaseFlowIds, CancellationToken cancellationToken = default);
}
}

62
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
{
/// <summary>
/// 测试场景仓储接口
/// </summary>
public interface ITestScenarioRepository : IBaseRepository<TestScenario>
{
/// <summary>
/// 根据场景编码获取测试场景
/// </summary>
/// <param name="scenarioCode">场景编码</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试场景</returns>
Task<TestScenario?> GetByScenarioCodeAsync(string scenarioCode, CancellationToken cancellationToken = default);
/// <summary>
/// 根据场景类型获取测试场景列表
/// </summary>
/// <param name="type">场景类型</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试场景列表</returns>
Task<IEnumerable<TestScenario>> GetByTypeAsync(ScenarioType type, CancellationToken cancellationToken = default);
/// <summary>
/// 获取启用的测试场景列表
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>启用的测试场景列表</returns>
Task<IEnumerable<TestScenario>> GetEnabledAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 检查场景编码是否存在
/// </summary>
/// <param name="scenarioCode">场景编码</param>
/// <param name="excludeId">排除的ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否存在</returns>
Task<bool> ExistsByScenarioCodeAsync(string scenarioCode, string? excludeId = null, CancellationToken cancellationToken = default);
/// <summary>
/// 根据场景ID获取包含测试用例的完整场景信息
/// </summary>
/// <param name="scenarioId">场景ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>包含测试用例的场景</returns>
Task<TestScenario?> GetWithTestCasesAsync(string scenarioId, CancellationToken cancellationToken = default);
/// <summary>
/// 获取所有包含测试用例的场景列表
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>包含测试用例的场景列表</returns>
Task<IEnumerable<TestScenario>> GetAllWithTestCasesAsync(CancellationToken cancellationToken = default);
}
}

82
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
{
/// <summary>
/// 场景测试用例实体配置
/// </summary>
public class ScenarioTestCaseConfiguration : IEntityTypeConfiguration<ScenarioTestCase>
{
public void Configure(EntityTypeBuilder<ScenarioTestCase> 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);
}
}
}

74
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
{
/// <summary>
/// 测试场景实体配置
/// </summary>
public class TestScenarioConfiguration : IEntityTypeConfiguration<TestScenario>
{
public void Configure(EntityTypeBuilder<TestScenario> 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<string>();
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);
}
}
}

10
src/X1.Infrastructure/Context/AppDbContext.cs

@ -129,6 +129,16 @@ public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
/// </summary>
public DbSet<ImsiRegistrationRecord> ImsiRegistrationRecords { get; set; } = null!;
/// <summary>
/// 测试场景集合
/// </summary>
public DbSet<TestScenario> TestScenarios { get; set; } = null!;
/// <summary>
/// 场景测试用例集合
/// </summary>
public DbSet<ScenarioTestCase> ScenarioTestCases { get; set; } = null!;
/// <summary>
/// 初始化数据库上下文
/// </summary>

2
src/X1.Infrastructure/DependencyInjection.cs

@ -241,6 +241,8 @@ public static class DependencyInjection
services.AddScoped<ITestCaseEdgeRepository, TestCaseEdgeRepository>();
services.AddScoped<ITestCaseNodeRepository, TestCaseNodeRepository>();
services.AddScoped<IImsiRegistrationRecordRepository, ImsiRegistrationRecordRepository>();
services.AddScoped<ITestScenarioRepository, TestScenarioRepository>();
services.AddScoped<IScenarioTestCaseRepository, ScenarioTestCaseRepository>();
return services;
}

135
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
{
/// <summary>
/// 场景测试用例仓储实现
/// </summary>
public class ScenarioTestCaseRepository : BaseRepository<ScenarioTestCase>, IScenarioTestCaseRepository
{
private readonly ILogger<ScenarioTestCaseRepository> _logger;
public ScenarioTestCaseRepository(
ICommandRepository<ScenarioTestCase> commandRepository,
IQueryRepository<ScenarioTestCase> queryRepository,
ILogger<ScenarioTestCaseRepository> logger)
: base(commandRepository, queryRepository, logger)
{
_logger = logger;
}
/// <summary>
/// 根据场景ID获取测试用例列表
/// </summary>
public async Task<IEnumerable<ScenarioTestCase>> GetByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default)
{
var testCases = await QueryRepository.FindAsync(x => x.ScenarioId == scenarioId, cancellationToken:cancellationToken);
return testCases.OrderBy(x => x.ExecutionOrder);
}
/// <summary>
/// 根据测试用例流程ID获取场景测试用例列表
/// </summary>
public async Task<IEnumerable<ScenarioTestCase>> GetByTestCaseFlowIdAsync(string testCaseFlowId, CancellationToken cancellationToken = default)
{
var testCases = await QueryRepository.FindAsync(x => x.TestCaseFlowId == testCaseFlowId, cancellationToken: cancellationToken);
return testCases.OrderBy(x => x.ExecutionOrder);
}
/// <summary>
/// 根据场景ID获取启用的测试用例列表
/// </summary>
public async Task<IEnumerable<ScenarioTestCase>> 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);
}
/// <summary>
/// 根据场景ID和测试用例流程ID获取场景测试用例
/// </summary>
public async Task<ScenarioTestCase?> GetByScenarioAndTestCaseFlowAsync(string scenarioId, string testCaseFlowId, CancellationToken cancellationToken = default)
{
return await QueryRepository.FirstOrDefaultAsync(x => x.ScenarioId == scenarioId && x.TestCaseFlowId == testCaseFlowId, cancellationToken: cancellationToken);
}
/// <summary>
/// 检查场景中是否已存在指定的测试用例
/// </summary>
public async Task<bool> 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);
}
}
/// <summary>
/// 根据场景ID获取包含完整信息的测试用例列表
/// </summary>
public async Task<IEnumerable<ScenarioTestCase>> 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);
}
/// <summary>
/// 根据场景ID删除所有测试用例
/// </summary>
public async Task<int> 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;
}
/// <summary>
/// 根据场景ID获取测试用例数量
/// </summary>
public async Task<int> GetCountByScenarioIdAsync(string scenarioId, CancellationToken cancellationToken = default)
{
return await QueryRepository.CountAsync(x => x.ScenarioId == scenarioId, cancellationToken: cancellationToken);
}
/// <summary>
/// 批量检查场景中已存在的测试用例流程ID
/// </summary>
public async Task<HashSet<string>> GetExistingTestCaseFlowIdsAsync(string scenarioId, IEnumerable<string> testCaseFlowIds, CancellationToken cancellationToken = default)
{
var testCaseFlowIdList = testCaseFlowIds.ToList();
if (!testCaseFlowIdList.Any())
{
return new HashSet<string>();
}
var existingTestCases = await QueryRepository.FindAsync(
x => x.ScenarioId == scenarioId && testCaseFlowIdList.Contains(x.TestCaseFlowId),
cancellationToken: cancellationToken);
return existingTestCases.Select(x => x.TestCaseFlowId).ToHashSet();
}
}
}

93
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
{
/// <summary>
/// 测试场景仓储实现
/// </summary>
public class TestScenarioRepository : BaseRepository<TestScenario>, ITestScenarioRepository
{
private readonly ILogger<TestScenarioRepository> _logger;
public TestScenarioRepository(
ICommandRepository<TestScenario> commandRepository,
IQueryRepository<TestScenario> queryRepository,
ILogger<TestScenarioRepository> logger)
: base(commandRepository, queryRepository, logger)
{
_logger = logger;
}
/// <summary>
/// 根据场景编码获取测试场景
/// </summary>
public async Task<TestScenario?> GetByScenarioCodeAsync(string scenarioCode, CancellationToken cancellationToken = default)
{
return await QueryRepository.FirstOrDefaultAsync(x => x.ScenarioCode == scenarioCode, cancellationToken: cancellationToken);
}
/// <summary>
/// 根据场景类型获取测试场景列表
/// </summary>
public async Task<IEnumerable<TestScenario>> GetByTypeAsync(ScenarioType type, CancellationToken cancellationToken = default)
{
var scenarios = await QueryRepository.FindAsync(x => x.Type == type, cancellationToken: cancellationToken);
return scenarios.OrderBy(x => x.ScenarioCode);
}
/// <summary>
/// 获取启用的测试场景列表
/// </summary>
public async Task<IEnumerable<TestScenario>> GetEnabledAsync(CancellationToken cancellationToken = default)
{
var scenarios = await QueryRepository.FindAsync(x => x.IsEnabled, cancellationToken: cancellationToken);
return scenarios.OrderBy(x => x.ScenarioCode);
}
/// <summary>
/// 检查场景编码是否存在
/// </summary>
public async Task<bool> 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);
}
}
/// <summary>
/// 根据场景ID获取包含测试用例的完整场景信息
/// </summary>
public async Task<TestScenario?> GetWithTestCasesAsync(string scenarioId, CancellationToken cancellationToken = default)
{
return await QueryRepository.GetByIdAsync(scenarioId,
query => query.Include(x => x.ScenarioTestCases).ThenInclude(stc => stc.TestCaseFlow),
cancellationToken);
}
/// <summary>
/// 获取所有包含测试用例的场景列表
/// </summary>
public async Task<IEnumerable<TestScenario>> 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);
}
}
}

24
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;
}
/// <summary>
/// 获取测试流程类型列表
/// </summary>
/// <returns>测试流程类型数据</returns>
[HttpGet("form-type-step")]
public async Task<OperationResult<GetFormTypeStepTypeMappingResponse>> 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<string>()));
return result;
}
_logger.LogInformation("成功获取测试流程类型列表,测试流程类型数量: {TestFlowTypeCount}",
result.Data?.TestFlowTypes.Count);
return result;
}
}

346
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<TestScenario>"未包含"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<T>` 接口中没有 `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
#### 修改文件:

Loading…
Cancel
Save