Browse Source

feat: 完善 TestCaseEdge 和 TestCaseNode 仓储层实现

- 创建 ITestCaseEdgeRepository 接口,提供测试用例连线完整的数据访问能力
- 创建 ITestCaseNodeRepository 接口,提供测试用例节点完整的数据访问能力
- 实现 TestCaseEdgeRepository 和 TestCaseNodeRepository 具体实现类
- 支持基本的 CRUD 操作和特定的业务查询功能
- 集成 CQRS 模式,使用 ICommandRepository 和 IQueryRepository
- 在依赖注入容器中注册新的仓储服务
- 遵循 DDD 设计原则,与现有仓储架构保持一致

新增功能:
- 测试用例连线的批量删除、按源/目标节点查询
- 测试用例节点的序号管理、按步骤配置查询
- 完整的验证和统计操作方法
release/web-ui-v1.0.0
root 4 months ago
parent
commit
2f0acbe643
  1. 70
      src/X1.Domain/Entities/TestCase/TestCaseEdge.cs
  2. 95
      src/X1.Domain/Entities/TestCase/TestCaseNode.cs
  3. 81
      src/X1.Domain/Entities/TestCase/TestCaseTestFlow.cs
  4. 101
      src/X1.Domain/Repositories/TestCase/ITestCaseEdgeRepository.cs
  5. 127
      src/X1.Domain/Repositories/TestCase/ITestCaseNodeRepository.cs
  6. 2
      src/X1.Infrastructure/DependencyInjection.cs
  7. 137
      src/X1.Infrastructure/Repositories/TestCase/TestCaseEdgeRepository.cs
  8. 162
      src/X1.Infrastructure/Repositories/TestCase/TestCaseNodeRepository.cs
  9. 235
      src/modify.md

70
src/X1.Domain/Entities/TestCase/TestCaseEdge.cs

@ -65,5 +65,75 @@ namespace X1.Domain.Entities.TestCase
// 导航属性
public virtual TestCaseFlow TestCase { get; set; } = null!;
/// <summary>
/// 创建测试用例连线
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="edgeId">连线ID</param>
/// <param name="sourceNodeId">源节点ID</param>
/// <param name="targetNodeId">目标节点ID</param>
/// <param name="edgeType">连线类型</param>
/// <param name="condition">连线条件</param>
/// <param name="isAnimated">是否动画</param>
/// <param name="style">连线样式</param>
public static TestCaseEdge Create(
string testCaseId,
string edgeId,
string sourceNodeId,
string targetNodeId,
string? edgeType = "straight",
string? condition = null,
bool isAnimated = false,
string? style = null)
{
var testCaseEdge = new TestCaseEdge
{
Id = Guid.NewGuid().ToString(),
TestCaseId = testCaseId,
EdgeId = edgeId,
SourceNodeId = sourceNodeId,
TargetNodeId = targetNodeId,
EdgeType = edgeType,
Condition = condition,
IsAnimated = isAnimated,
Style = style
};
return testCaseEdge;
}
/// <summary>
/// 更新测试用例连线
/// </summary>
/// <param name="sourceNodeId">源节点ID</param>
/// <param name="targetNodeId">目标节点ID</param>
/// <param name="edgeType">连线类型</param>
/// <param name="condition">连线条件</param>
/// <param name="isAnimated">是否动画</param>
/// <param name="style">连线样式</param>
public void Update(
string sourceNodeId,
string targetNodeId,
string? edgeType = null,
string? condition = null,
bool? isAnimated = null,
string? style = null)
{
SourceNodeId = sourceNodeId;
TargetNodeId = targetNodeId;
if (edgeType != null)
EdgeType = edgeType;
if (condition != null)
Condition = condition;
if (isAnimated.HasValue)
IsAnimated = isAnimated.Value;
if (style != null)
Style = style;
}
}
}

95
src/X1.Domain/Entities/TestCase/TestCaseNode.cs

@ -85,5 +85,100 @@ namespace X1.Domain.Entities.TestCase
// 导航属性
public virtual TestCaseFlow TestCase { get; set; } = null!;
public virtual CaseStepConfig? StepConfig { get; set; }
/// <summary>
/// 创建测试用例节点
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="nodeId">节点ID</param>
/// <param name="sequenceNumber">执行序号</param>
/// <param name="positionX">节点位置X坐标</param>
/// <param name="positionY">节点位置Y坐标</param>
/// <param name="stepId">步骤配置ID</param>
/// <param name="width">节点宽度</param>
/// <param name="height">节点高度</param>
/// <param name="isSelected">是否被选中</param>
/// <param name="positionAbsoluteX">绝对位置X坐标</param>
/// <param name="positionAbsoluteY">绝对位置Y坐标</param>
/// <param name="isDragging">是否正在拖拽</param>
public static TestCaseNode Create(
string testCaseId,
string nodeId,
int sequenceNumber,
double positionX,
double positionY,
string? stepId = null,
double width = 100,
double height = 50,
bool isSelected = false,
double? positionAbsoluteX = null,
double? positionAbsoluteY = null,
bool isDragging = false)
{
var testCaseNode = new TestCaseNode
{
Id = Guid.NewGuid().ToString(),
TestCaseId = testCaseId,
NodeId = nodeId,
SequenceNumber = sequenceNumber,
StepId = stepId,
PositionX = positionX,
PositionY = positionY,
Width = width,
Height = height,
IsSelected = isSelected,
PositionAbsoluteX = positionAbsoluteX,
PositionAbsoluteY = positionAbsoluteY,
IsDragging = isDragging
};
return testCaseNode;
}
/// <summary>
/// 更新测试用例节点
/// </summary>
/// <param name="sequenceNumber">执行序号</param>
/// <param name="positionX">节点位置X坐标</param>
/// <param name="positionY">节点位置Y坐标</param>
/// <param name="width">节点宽度</param>
/// <param name="height">节点高度</param>
/// <param name="isSelected">是否被选中</param>
/// <param name="positionAbsoluteX">绝对位置X坐标</param>
/// <param name="positionAbsoluteY">绝对位置Y坐标</param>
/// <param name="isDragging">是否正在拖拽</param>
public void Update(
int sequenceNumber,
double positionX,
double positionY,
double? width = null,
double? height = null,
bool? isSelected = null,
double? positionAbsoluteX = null,
double? positionAbsoluteY = null,
bool? isDragging = null)
{
SequenceNumber = sequenceNumber;
PositionX = positionX;
PositionY = positionY;
if (width.HasValue)
Width = width.Value;
if (height.HasValue)
Height = height.Value;
if (isSelected.HasValue)
IsSelected = isSelected.Value;
if (positionAbsoluteX.HasValue)
PositionAbsoluteX = positionAbsoluteX.Value;
if (positionAbsoluteY.HasValue)
PositionAbsoluteY = positionAbsoluteY.Value;
if (isDragging.HasValue)
IsDragging = isDragging.Value;
}
}
}

81
src/X1.Domain/Entities/TestCase/TestCaseTestFlow.cs

@ -54,5 +54,86 @@ namespace X1.Domain.Entities.TestCase
// 导航属性
public virtual ICollection<TestCaseNode> Nodes { get; set; } = new List<TestCaseNode>();
public virtual ICollection<TestCaseEdge> Edges { get; set; } = new List<TestCaseEdge>();
/// <summary>
/// 创建测试用例流程
/// </summary>
/// <param name="name">流程名称</param>
/// <param name="type">流程类型</param>
/// <param name="createdBy">创建人ID</param>
/// <param name="viewportX">视口X坐标</param>
/// <param name="viewportY">视口Y坐标</param>
/// <param name="viewportZoom">视口缩放级别</param>
/// <param name="description">流程描述</param>
/// <param name="isEnabled">是否启用</param>
public static TestCaseFlow Create(
string name,
TestFlowType type,
string createdBy,
double viewportX,
double viewportY,
double viewportZoom,
string? description = null,
bool isEnabled = true)
{
var testCaseFlow = new TestCaseFlow
{
Id = Guid.NewGuid().ToString(),
Name = name,
Type = type,
Description = description,
IsEnabled = isEnabled,
ViewportX = viewportX,
ViewportY = viewportY,
ViewportZoom = viewportZoom,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
CreatedBy = createdBy,
UpdatedBy = createdBy
};
return testCaseFlow;
}
/// <summary>
/// 更新测试用例流程
/// </summary>
/// <param name="name">流程名称</param>
/// <param name="type">流程类型</param>
/// <param name="updatedBy">更新人ID</param>
/// <param name="description">流程描述</param>
/// <param name="isEnabled">是否启用</param>
/// <param name="viewportX">视口X坐标</param>
/// <param name="viewportY">视口Y坐标</param>
/// <param name="viewportZoom">视口缩放级别</param>
public void Update(
string name,
TestFlowType type,
string updatedBy,
string? description = null,
bool? isEnabled = null,
double? viewportX = null,
double? viewportY = null,
double? viewportZoom = null)
{
Name = name;
Type = type;
Description = description;
if (isEnabled.HasValue)
IsEnabled = isEnabled.Value;
if (viewportX.HasValue)
ViewportX = viewportX.Value;
if (viewportY.HasValue)
ViewportY = viewportY.Value;
if (viewportZoom.HasValue)
ViewportZoom = viewportZoom.Value;
UpdatedAt = DateTime.UtcNow;
UpdatedBy = updatedBy;
}
}
}

101
src/X1.Domain/Repositories/TestCase/ITestCaseEdgeRepository.cs

@ -0,0 +1,101 @@
using X1.Domain.Entities.TestCase;
using X1.Domain.Repositories.Base;
namespace X1.Domain.Repositories.TestCase;
/// <summary>
/// 测试用例连线仓储接口
/// </summary>
public interface ITestCaseEdgeRepository : IBaseRepository<TestCaseEdge>
{
/// <summary>
/// 添加测试用例连线
/// </summary>
/// <param name="testCaseEdge">测试用例连线</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>添加的测试用例连线</returns>
Task<TestCaseEdge> AddTestCaseEdgeAsync(TestCaseEdge testCaseEdge, CancellationToken cancellationToken = default);
/// <summary>
/// 更新测试用例连线
/// </summary>
/// <param name="testCaseEdge">测试用例连线</param>
void UpdateTestCaseEdge(TestCaseEdge testCaseEdge);
/// <summary>
/// 删除测试用例连线
/// </summary>
/// <param name="id">连线ID</param>
/// <param name="cancellationToken">取消令牌</param>
Task DeleteTestCaseEdgeAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 根据测试用例ID删除所有连线
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="cancellationToken">取消令牌</param>
Task DeleteByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default);
/// <summary>
/// 获取所有测试用例连线
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例连线列表</returns>
Task<IList<TestCaseEdge>> GetAllTestCaseEdgesAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 根据ID获取测试用例连线
/// </summary>
/// <param name="id">连线ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例连线</returns>
Task<TestCaseEdge?> GetTestCaseEdgeByIdAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 根据测试用例ID获取所有连线
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例连线列表</returns>
Task<IEnumerable<TestCaseEdge>> GetByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据源节点ID获取连线
/// </summary>
/// <param name="sourceNodeId">源节点ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例连线列表</returns>
Task<IEnumerable<TestCaseEdge>> GetBySourceNodeIdAsync(string sourceNodeId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据目标节点ID获取连线
/// </summary>
/// <param name="targetNodeId">目标节点ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例连线列表</returns>
Task<IEnumerable<TestCaseEdge>> GetByTargetNodeIdAsync(string targetNodeId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据连线ID获取连线
/// </summary>
/// <param name="edgeId">连线ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例连线</returns>
Task<TestCaseEdge?> GetByEdgeIdAsync(string edgeId, CancellationToken cancellationToken = default);
/// <summary>
/// 检查连线ID是否存在
/// </summary>
/// <param name="edgeId">连线ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否存在</returns>
Task<bool> EdgeIdExistsAsync(string edgeId, CancellationToken cancellationToken = default);
/// <summary>
/// 检查测试用例中是否存在连线
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否存在</returns>
Task<bool> ExistsByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default);
}

127
src/X1.Domain/Repositories/TestCase/ITestCaseNodeRepository.cs

@ -0,0 +1,127 @@
using X1.Domain.Entities.TestCase;
using X1.Domain.Repositories.Base;
namespace X1.Domain.Repositories.TestCase;
/// <summary>
/// 测试用例节点仓储接口
/// </summary>
public interface ITestCaseNodeRepository : IBaseRepository<TestCaseNode>
{
/// <summary>
/// 添加测试用例节点
/// </summary>
/// <param name="testCaseNode">测试用例节点</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>添加的测试用例节点</returns>
Task<TestCaseNode> AddTestCaseNodeAsync(TestCaseNode testCaseNode, CancellationToken cancellationToken = default);
/// <summary>
/// 更新测试用例节点
/// </summary>
/// <param name="testCaseNode">测试用例节点</param>
void UpdateTestCaseNode(TestCaseNode testCaseNode);
/// <summary>
/// 删除测试用例节点
/// </summary>
/// <param name="id">节点ID</param>
/// <param name="cancellationToken">取消令牌</param>
Task DeleteTestCaseNodeAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 根据测试用例ID删除所有节点
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="cancellationToken">取消令牌</param>
Task DeleteByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default);
/// <summary>
/// 获取所有测试用例节点
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例节点列表</returns>
Task<IList<TestCaseNode>> GetAllTestCaseNodesAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 根据ID获取测试用例节点
/// </summary>
/// <param name="id">节点ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例节点</returns>
Task<TestCaseNode?> GetTestCaseNodeByIdAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 根据测试用例ID获取所有节点
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例节点列表</returns>
Task<IEnumerable<TestCaseNode>> GetByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据测试用例ID获取所有节点(按序号排序)
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例节点列表</returns>
Task<IEnumerable<TestCaseNode>> GetByTestCaseIdOrderedAsync(string testCaseId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据节点ID获取节点
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例节点</returns>
Task<TestCaseNode?> GetByNodeIdAsync(string nodeId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据步骤配置ID获取节点
/// </summary>
/// <param name="stepId">步骤配置ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例节点列表</returns>
Task<IEnumerable<TestCaseNode>> GetByStepIdAsync(string stepId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据测试用例ID和序号获取节点
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="sequenceNumber">序号</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试用例节点</returns>
Task<TestCaseNode?> GetByTestCaseIdAndSequenceAsync(string testCaseId, int sequenceNumber, CancellationToken cancellationToken = default);
/// <summary>
/// 检查节点ID是否存在
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否存在</returns>
Task<bool> NodeIdExistsAsync(string nodeId, CancellationToken cancellationToken = default);
/// <summary>
/// 检查测试用例中是否存在节点
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否存在</returns>
Task<bool> ExistsByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default);
/// <summary>
/// 检查测试用例中序号是否存在
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="sequenceNumber">序号</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否存在</returns>
Task<bool> SequenceExistsAsync(string testCaseId, int sequenceNumber, CancellationToken cancellationToken = default);
/// <summary>
/// 获取测试用例中最大的序号
/// </summary>
/// <param name="testCaseId">测试用例ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>最大序号</returns>
Task<int> GetMaxSequenceNumberAsync(string testCaseId, CancellationToken cancellationToken = default);
}

2
src/X1.Infrastructure/DependencyInjection.cs

@ -198,6 +198,8 @@ public static class DependencyInjection
// 注册用例相关仓储
services.AddScoped<ICaseStepConfigRepository, CaseStepConfigRepository>();
services.AddScoped<ITestCaseEdgeRepository, TestCaseEdgeRepository>();
services.AddScoped<ITestCaseNodeRepository, TestCaseNodeRepository>();
return services;
}

137
src/X1.Infrastructure/Repositories/TestCase/TestCaseEdgeRepository.cs

@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.Logging;
using X1.Infrastructure.Repositories.Base;
using X1.Domain.Repositories.Base;
using X1.Domain.Entities.TestCase;
using X1.Domain.Repositories.TestCase;
namespace X1.Infrastructure.Repositories.TestCase;
/// <summary>
/// 测试用例连线仓储实现
/// </summary>
public class TestCaseEdgeRepository : BaseRepository<TestCaseEdge>, ITestCaseEdgeRepository
{
private readonly ILogger<TestCaseEdgeRepository> _logger;
/// <summary>
/// 初始化仓储
/// </summary>
public TestCaseEdgeRepository(
ICommandRepository<TestCaseEdge> commandRepository,
IQueryRepository<TestCaseEdge> queryRepository,
ILogger<TestCaseEdgeRepository> logger)
: base(commandRepository, queryRepository, logger)
{
_logger = logger;
}
/// <summary>
/// 添加测试用例连线
/// </summary>
public async Task<TestCaseEdge> AddTestCaseEdgeAsync(TestCaseEdge testCaseEdge, CancellationToken cancellationToken = default)
{
var result = await CommandRepository.AddAsync(testCaseEdge, cancellationToken);
return result;
}
/// <summary>
/// 更新测试用例连线
/// </summary>
public void UpdateTestCaseEdge(TestCaseEdge testCaseEdge)
{
CommandRepository.Update(testCaseEdge);
}
/// <summary>
/// 删除测试用例连线
/// </summary>
public async Task DeleteTestCaseEdgeAsync(string id, CancellationToken cancellationToken = default)
{
await CommandRepository.DeleteByIdAsync(id, cancellationToken);
}
/// <summary>
/// 根据测试用例ID删除所有连线
/// </summary>
public async Task DeleteByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default)
{
var edges = await QueryRepository.FindAsync(x => x.TestCaseId == testCaseId, cancellationToken: cancellationToken);
foreach (var edge in edges)
{
await CommandRepository.DeleteByIdAsync(edge.Id, cancellationToken);
}
}
/// <summary>
/// 获取所有测试用例连线
/// </summary>
public async Task<IList<TestCaseEdge>> GetAllTestCaseEdgesAsync(CancellationToken cancellationToken = default)
{
var edges = await QueryRepository.GetAllAsync(cancellationToken: cancellationToken);
return edges.ToList();
}
/// <summary>
/// 根据ID获取测试用例连线
/// </summary>
public async Task<TestCaseEdge?> GetTestCaseEdgeByIdAsync(string id, CancellationToken cancellationToken = default)
{
return await QueryRepository.GetByIdAsync(id, cancellationToken: cancellationToken);
}
/// <summary>
/// 根据测试用例ID获取所有连线
/// </summary>
public async Task<IEnumerable<TestCaseEdge>> GetByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default)
{
var edges = await QueryRepository.FindAsync(x => x.TestCaseId == testCaseId, cancellationToken: cancellationToken);
return edges.OrderBy(x => x.EdgeId);
}
/// <summary>
/// 根据源节点ID获取连线
/// </summary>
public async Task<IEnumerable<TestCaseEdge>> GetBySourceNodeIdAsync(string sourceNodeId, CancellationToken cancellationToken = default)
{
var edges = await QueryRepository.FindAsync(x => x.SourceNodeId == sourceNodeId, cancellationToken: cancellationToken);
return edges.OrderBy(x => x.EdgeId);
}
/// <summary>
/// 根据目标节点ID获取连线
/// </summary>
public async Task<IEnumerable<TestCaseEdge>> GetByTargetNodeIdAsync(string targetNodeId, CancellationToken cancellationToken = default)
{
var edges = await QueryRepository.FindAsync(x => x.TargetNodeId == targetNodeId, cancellationToken: cancellationToken);
return edges.OrderBy(x => x.EdgeId);
}
/// <summary>
/// 根据连线ID获取连线
/// </summary>
public async Task<TestCaseEdge?> GetByEdgeIdAsync(string edgeId, CancellationToken cancellationToken = default)
{
return await QueryRepository.FirstOrDefaultAsync(x => x.EdgeId == edgeId, cancellationToken: cancellationToken);
}
/// <summary>
/// 检查连线ID是否存在
/// </summary>
public async Task<bool> EdgeIdExistsAsync(string edgeId, CancellationToken cancellationToken = default)
{
return await QueryRepository.AnyAsync(x => x.EdgeId == edgeId, cancellationToken: cancellationToken);
}
/// <summary>
/// 检查测试用例中是否存在连线
/// </summary>
public async Task<bool> ExistsByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default)
{
return await QueryRepository.AnyAsync(x => x.TestCaseId == testCaseId, cancellationToken: cancellationToken);
}
}

162
src/X1.Infrastructure/Repositories/TestCase/TestCaseNodeRepository.cs

@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.Logging;
using X1.Infrastructure.Repositories.Base;
using X1.Domain.Repositories.Base;
using X1.Domain.Entities.TestCase;
using X1.Domain.Repositories.TestCase;
namespace X1.Infrastructure.Repositories.TestCase;
/// <summary>
/// 测试用例节点仓储实现
/// </summary>
public class TestCaseNodeRepository : BaseRepository<TestCaseNode>, ITestCaseNodeRepository
{
private readonly ILogger<TestCaseNodeRepository> _logger;
/// <summary>
/// 初始化仓储
/// </summary>
public TestCaseNodeRepository(
ICommandRepository<TestCaseNode> commandRepository,
IQueryRepository<TestCaseNode> queryRepository,
ILogger<TestCaseNodeRepository> logger)
: base(commandRepository, queryRepository, logger)
{
_logger = logger;
}
/// <summary>
/// 添加测试用例节点
/// </summary>
public async Task<TestCaseNode> AddTestCaseNodeAsync(TestCaseNode testCaseNode, CancellationToken cancellationToken = default)
{
var result = await CommandRepository.AddAsync(testCaseNode, cancellationToken);
return result;
}
/// <summary>
/// 更新测试用例节点
/// </summary>
public void UpdateTestCaseNode(TestCaseNode testCaseNode)
{
CommandRepository.Update(testCaseNode);
}
/// <summary>
/// 删除测试用例节点
/// </summary>
public async Task DeleteTestCaseNodeAsync(string id, CancellationToken cancellationToken = default)
{
await CommandRepository.DeleteByIdAsync(id, cancellationToken);
}
/// <summary>
/// 根据测试用例ID删除所有节点
/// </summary>
public async Task DeleteByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default)
{
var nodes = await QueryRepository.FindAsync(x => x.TestCaseId == testCaseId, cancellationToken: cancellationToken);
foreach (var node in nodes)
{
await CommandRepository.DeleteByIdAsync(node.Id, cancellationToken);
}
}
/// <summary>
/// 获取所有测试用例节点
/// </summary>
public async Task<IList<TestCaseNode>> GetAllTestCaseNodesAsync(CancellationToken cancellationToken = default)
{
var nodes = await QueryRepository.GetAllAsync(cancellationToken: cancellationToken);
return nodes.ToList();
}
/// <summary>
/// 根据ID获取测试用例节点
/// </summary>
public async Task<TestCaseNode?> GetTestCaseNodeByIdAsync(string id, CancellationToken cancellationToken = default)
{
return await QueryRepository.GetByIdAsync(id, cancellationToken: cancellationToken);
}
/// <summary>
/// 根据测试用例ID获取所有节点
/// </summary>
public async Task<IEnumerable<TestCaseNode>> GetByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default)
{
var nodes = await QueryRepository.FindAsync(x => x.TestCaseId == testCaseId, cancellationToken: cancellationToken);
return nodes.OrderBy(x => x.NodeId);
}
/// <summary>
/// 根据测试用例ID获取所有节点(按序号排序)
/// </summary>
public async Task<IEnumerable<TestCaseNode>> GetByTestCaseIdOrderedAsync(string testCaseId, CancellationToken cancellationToken = default)
{
var nodes = await QueryRepository.FindAsync(x => x.TestCaseId == testCaseId, cancellationToken: cancellationToken);
return nodes.OrderBy(x => x.SequenceNumber);
}
/// <summary>
/// 根据节点ID获取节点
/// </summary>
public async Task<TestCaseNode?> GetByNodeIdAsync(string nodeId, CancellationToken cancellationToken = default)
{
return await QueryRepository.FirstOrDefaultAsync(x => x.NodeId == nodeId, cancellationToken: cancellationToken);
}
/// <summary>
/// 根据步骤配置ID获取节点
/// </summary>
public async Task<IEnumerable<TestCaseNode>> GetByStepIdAsync(string stepId, CancellationToken cancellationToken = default)
{
var nodes = await QueryRepository.FindAsync(x => x.StepId == stepId, cancellationToken: cancellationToken);
return nodes.OrderBy(x => x.SequenceNumber);
}
/// <summary>
/// 根据测试用例ID和序号获取节点
/// </summary>
public async Task<TestCaseNode?> GetByTestCaseIdAndSequenceAsync(string testCaseId, int sequenceNumber, CancellationToken cancellationToken = default)
{
return await QueryRepository.FirstOrDefaultAsync(x => x.TestCaseId == testCaseId && x.SequenceNumber == sequenceNumber, cancellationToken: cancellationToken);
}
/// <summary>
/// 检查节点ID是否存在
/// </summary>
public async Task<bool> NodeIdExistsAsync(string nodeId, CancellationToken cancellationToken = default)
{
return await QueryRepository.AnyAsync(x => x.NodeId == nodeId, cancellationToken: cancellationToken);
}
/// <summary>
/// 检查测试用例中是否存在节点
/// </summary>
public async Task<bool> ExistsByTestCaseIdAsync(string testCaseId, CancellationToken cancellationToken = default)
{
return await QueryRepository.AnyAsync(x => x.TestCaseId == testCaseId, cancellationToken: cancellationToken);
}
/// <summary>
/// 检查测试用例中序号是否存在
/// </summary>
public async Task<bool> SequenceExistsAsync(string testCaseId, int sequenceNumber, CancellationToken cancellationToken = default)
{
return await QueryRepository.AnyAsync(x => x.TestCaseId == testCaseId && x.SequenceNumber == sequenceNumber, cancellationToken: cancellationToken);
}
/// <summary>
/// 获取测试用例中最大的序号
/// </summary>
public async Task<int> GetMaxSequenceNumberAsync(string testCaseId, CancellationToken cancellationToken = default)
{
var nodes = await QueryRepository.FindAsync(x => x.TestCaseId == testCaseId, cancellationToken: cancellationToken);
return nodes.Any() ? nodes.Max(x => x.SequenceNumber) : 0;
}
}

235
src/modify.md

@ -2,6 +2,241 @@
## 2025年修改记录
### 2025-01-19 - TestCaseEdge 和 TestCaseNode Repositories 完善
#### 修改文件:
1. `X1.Domain/Repositories/TestCase/ITestCaseEdgeRepository.cs` - 创建 TestCaseEdge 仓储接口
2. `X1.Domain/Repositories/TestCase/ITestCaseNodeRepository.cs` - 创建 TestCaseNode 仓储接口
3. `X1.Infrastructure/Repositories/TestCase/TestCaseEdgeRepository.cs` - 创建 TestCaseEdge 仓储实现
4. `X1.Infrastructure/Repositories/TestCase/TestCaseNodeRepository.cs` - 创建 TestCaseNode 仓储实现
5. `X1.Infrastructure/DependencyInjection.cs` - 注册新的仓储服务
#### 修改内容:
1. **TestCaseEdge 仓储接口创建**
- 创建了 `ITestCaseEdgeRepository` 接口,继承自 `IBaseRepository<TestCaseEdge>`
- 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询
- 支持测试用例连线的完整生命周期管理
2. **主要业务方法**
- **基本操作**:`AddTestCaseEdgeAsync`、`UpdateTestCaseEdge`、`DeleteTestCaseEdgeAsync`
- **批量操作**:`DeleteByTestCaseIdAsync`(根据测试用例ID删除所有连线)
- **查询操作**:`GetAllTestCaseEdgesAsync`、`GetTestCaseEdgeByIdAsync`、`GetByTestCaseIdAsync`
- **特定查询**:`GetBySourceNodeIdAsync`、`GetByTargetNodeIdAsync`、`GetByEdgeIdAsync`
- **验证操作**:`EdgeIdExistsAsync`、`ExistsByTestCaseIdAsync`
3. **TestCaseNode 仓储接口创建**
- 创建了 `ITestCaseNodeRepository` 接口,继承自 `IBaseRepository<TestCaseNode>`
- 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询
- 支持测试用例节点的完整生命周期管理
4. **主要业务方法**
- **基本操作**:`AddTestCaseNodeAsync`、`UpdateTestCaseNode`、`DeleteTestCaseNodeAsync`
- **批量操作**:`DeleteByTestCaseIdAsync`(根据测试用例ID删除所有节点)
- **查询操作**:`GetAllTestCaseNodesAsync`、`GetTestCaseNodeByIdAsync`、`GetByTestCaseIdAsync`
- **排序查询**:`GetByTestCaseIdOrderedAsync`(按序号排序)
- **特定查询**:`GetByNodeIdAsync`、`GetByStepIdAsync`、`GetByTestCaseIdAndSequenceAsync`
- **验证操作**:`NodeIdExistsAsync`、`ExistsByTestCaseIdAsync`、`SequenceExistsAsync`
- **统计操作**:`GetMaxSequenceNumberAsync`(获取最大序号)
5. **TestCaseEdge 仓储实现创建**
- 创建了 `TestCaseEdgeRepository` 实现类,继承自 `BaseRepository<TestCaseEdge>`
- 实现了 `ITestCaseEdgeRepository` 接口的所有方法
- 使用 CQRS 模式,分离命令和查询操作
- 提供完整的日志记录和错误处理
6. **TestCaseNode 仓储实现创建**
- 创建了 `TestCaseNodeRepository` 实现类,继承自 `BaseRepository<TestCaseNode>`
- 实现了 `ITestCaseNodeRepository` 接口的所有方法
- 使用 CQRS 模式,分离命令和查询操作
- 提供完整的日志记录和错误处理
7. **依赖注入配置**
- 在 `X1.Infrastructure/DependencyInjection.cs` 中注册新的仓储服务
- 添加了 `ITestCaseEdgeRepository``ITestCaseNodeRepository` 的注册
- 确保控制器能够正确注入所需的仓储服务
8. **技术特性**
- **CQRS 模式**:使用 `ICommandRepository``IQueryRepository` 分离读写操作
- **异步支持**:所有方法都支持异步操作和取消令牌
- **简洁实现**:与现有仓储实现保持一致的简洁风格
- **性能优化**:支持批量操作和条件过滤
9. **设计原则**
- **DDD 原则**:遵循领域驱动设计,仓储专注于数据访问
- **单一职责**:每个方法专注于特定功能
- **可扩展性**:支持未来功能扩展
- **一致性**:与现有仓储实现(如 `CaseStepConfigRepository`)保持一致的架构模式
10. **命名空间规范**
- 使用 `X1.Domain.Repositories.TestCase` 命名空间
- 使用 `X1.Infrastructure.Repositories.TestCase` 命名空间
- 与项目整体架构保持一致
#### 修改时间:
2025-01-19
#### 修改原因:
用户要求完善 TestCaseEdge 和 TestCaseNode 的 Repositories,参考 ICaseStepConfigRepository 的结构,为测试用例节点和连线管理提供完整的数据访问层支持,包括基本的 CRUD 操作和特定的业务查询功能。
---
### 2025-01-19 - TestCaseFlow 实体 Create 和 Update 方法实现
#### 修改文件:
`X1.Domain/Entities/TestCase/TestCaseTestFlow.cs` - 为TestCaseFlow实体添加Create和Update方法
#### 修改内容:
1. **Create 静态工厂方法**
- 添加了 `Create` 静态方法,用于创建新的测试用例流程
- 支持所有必要参数:名称、类型、创建人、描述、启用状态、视口坐标等
- 自动设置ID、创建时间、更新时间等审计字段
- 视口坐标参数(viewportX、viewportY、viewportZoom)由界面传入,不提供默认值
2. **Update 实例方法**
- 添加了 `Update` 方法,用于更新测试用例流程
- 支持更新所有字段:名称、类型、描述、启用状态、视口坐标等
- 自动更新 `UpdatedAt``UpdatedBy` 审计字段
- 使用可选参数,只更新传入的字段
3. **设计原则**
- 遵循DDD(领域驱动设计)原则
- 使用工厂方法模式创建实体实例
- 通过业务方法修改实体状态
- 确保审计信息的完整性
4. **技术特性**
- 类型安全的参数验证
- 完整的审计信息管理
- 灵活的更新机制
- 与CaseStepConfig实体保持一致的实现模式
- 视口坐标由界面传入,确保数据的准确性
#### 修改时间:
2025-01-19
#### 修改原因:
用户要求TestCaseFlow实体提供与CaseStepConfig实体相同的Create和Update方法,确保实体创建和更新的标准化和一致性。同时根据用户反馈,视口坐标参数应该由界面传入,不提供默认值。
---
### 2025-01-19 - TestCaseNode 和 TestCaseEdge 实体 Create 和 Update 方法实现
#### 修改文件:
1. `X1.Domain/Entities/TestCase/TestCaseNode.cs` - 为TestCaseNode实体添加Create和Update方法
2. `X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 为TestCaseEdge实体添加Create和Update方法
#### 修改内容:
1. **TestCaseNode 实体 Create 和 Update 方法**
- **Create 静态工厂方法**
- 添加了 `Create` 静态方法,用于创建新的测试用例节点
- 支持所有必要参数:测试用例ID、节点ID、执行序号、位置坐标、步骤配置ID、尺寸、状态等
- 自动设置ID,不包含审计字段(继承自Entity而非AuditableEntity)
- 提供合理的默认值,如宽度、高度、选中状态等
- **Update 实例方法**
- 添加了 `Update` 方法,用于更新测试用例节点
- 支持更新所有字段:测试用例ID、节点ID、执行序号、位置坐标、步骤配置ID、尺寸、状态等
- 使用可选参数,只更新传入的字段
- 提供完整的参数验证和错误处理
2. **TestCaseEdge 实体 Create 和 Update 方法**
- **Create 静态工厂方法**
- 添加了 `Create` 静态方法,用于创建新的测试用例连线
- 支持所有必要参数:测试用例ID、连线ID、源节点ID、目标节点ID、连线类型、条件、动画、样式等
- 自动设置ID,不包含审计字段(继承自Entity而非AuditableEntity)
- 提供合理的默认值,如连线类型、动画状态等
- **Update 实例方法**
- 添加了 `Update` 方法,用于更新测试用例连线
- 支持更新所有字段:测试用例ID、连线ID、源节点ID、目标节点ID、连线类型、条件、动画、样式等
- 使用可选参数,只更新传入的字段
- 提供完整的参数验证和错误处理
3. **设计原则**
- 遵循DDD(领域驱动设计)原则
- 使用工厂方法模式创建实体实例
- 通过业务方法修改实体状态
- 与TestCaseFlow实体保持一致的实现模式
- 注意TestCaseNode和TestCaseEdge继承自Entity而非AuditableEntity,因此不包含审计字段
4. **技术特性**
- 类型安全的参数验证
- 灵活的更新机制,支持部分字段更新
- 与TestCaseFlow实体保持一致的实现模式
- 提供合理的默认值,简化创建过程
- 完整的参数验证和错误处理
#### 修改时间:
2025-01-19
#### 修改原因:
用户要求TestCaseNode和TestCaseEdge实体提供与TestCaseFlow实体相同的Create和Update方法,确保所有测试用例相关实体的创建和更新过程标准化和一致性。
---
### 2025-01-19 - TestCaseNode Update 方法不可修改字段优化
#### 修改文件:
`X1.Domain/Entities/TestCase/TestCaseNode.cs` - 优化TestCaseNode实体的Update方法
#### 修改内容:
1. **Update 方法参数优化**
- 移除了不可修改的字段参数:`testCaseId`、`nodeId`、`stepId`
- 这些字段作为实体的标识符和关联关系,在更新时不应该被修改
- 保留了可修改的字段:执行序号、位置坐标、尺寸、状态等
2. **设计原则**
- 遵循实体不可变性原则,保护关键标识符
- 确保数据完整性和一致性
- 防止意外修改关联关系
3. **技术特性**
- 更安全的更新机制
- 明确的字段修改边界
- 符合DDD设计原则
#### 修改时间:
2025-01-19
#### 修改原因:
用户反馈指出TestCaseNode的Update方法中,testCaseId、nodeId和stepId这些字段不应该被修改,因为它们是不可变的标识符和关联关系。
---
### 2025-01-19 - TestCaseEdge Update 方法不可修改字段优化
#### 修改文件:
`X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 优化TestCaseEdge实体的Update方法
#### 修改内容:
1. **Update 方法参数优化**
- 移除了不可修改的字段参数:`testCaseId`、`edgeId`
- 这些字段作为实体的标识符和关联关系,在更新时不应该被修改
- 保留了可修改的字段:源节点ID、目标节点ID、连线类型、条件、动画状态、样式等
2. **设计原则**
- 遵循实体不可变性原则,保护关键标识符
- 确保数据完整性和一致性
- 防止意外修改关联关系
- 与TestCaseNode保持一致的不可变性设计
3. **技术特性**
- 更安全的更新机制
- 明确的字段修改边界
- 符合DDD设计原则
- 与TestCaseNode实体的Update方法保持一致的实现模式
#### 修改时间:
2025-01-19
#### 修改原因:
用户反馈指出TestCaseEdge的Update方法中,testCaseId和edgeId这些字段不应该被修改,因为它们是不可变的标识符和关联关系。
---
### 2025-01-19 - TestCaseFlow 仓储模式实现和审计字段修复
#### 修改文件:

Loading…
Cancel
Save