427 lines
14 KiB
C#
427 lines
14 KiB
C#
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
using MiAssessment.Admin.Business.Data;
|
||
using MiAssessment.Admin.Business.Entities;
|
||
using MiAssessment.Admin.Business.Models;
|
||
using MiAssessment.Admin.Business.Models.AssessmentRecord;
|
||
using MiAssessment.Admin.Business.Models.Common;
|
||
using MiAssessment.Admin.Business.Services;
|
||
using Moq;
|
||
using Xunit;
|
||
|
||
namespace MiAssessment.Tests.Admin;
|
||
|
||
/// <summary>
|
||
/// 测评记录服务单元测试
|
||
/// 验证测评记录服务的边界条件和错误处理
|
||
/// </summary>
|
||
public class AssessmentRecordServiceTests
|
||
{
|
||
private readonly Mock<ILogger<AssessmentRecordService>> _mockLogger = new();
|
||
|
||
#region 测试记录不存在返回错误 (Requirements 2.4)
|
||
|
||
/// <summary>
|
||
/// 测试 GetRecordDetailAsync 当记录 ID 不存在时返回 null
|
||
/// **Validates: Requirements 2.4**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetRecordDetailAsync_WithNonExistentId_ReturnsNull()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
var nonExistentId = 99999L;
|
||
|
||
// Act
|
||
var result = await service.GetRecordDetailAsync(nonExistentId);
|
||
|
||
// Assert
|
||
Assert.Null(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试 GetRecordDetailAsync 当记录已软删除时返回 null
|
||
/// **Validates: Requirements 2.4**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetRecordDetailAsync_WithSoftDeletedRecord_ReturnsNull()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var user = CreateTestUser(dbContext, 1);
|
||
var order = CreateTestOrder(dbContext, user.Id, 1);
|
||
var assessmentType = CreateTestAssessmentType(dbContext, 1);
|
||
|
||
// 创建一个已软删除的记录
|
||
var record = new AssessmentRecord
|
||
{
|
||
UserId = user.Id,
|
||
OrderId = order.Id,
|
||
AssessmentTypeId = assessmentType.Id,
|
||
Name = "测试人",
|
||
Phone = "13900000001",
|
||
Gender = 1,
|
||
Age = 15,
|
||
EducationStage = 2,
|
||
Province = "北京市",
|
||
City = "北京市",
|
||
District = "朝阳区",
|
||
Status = 4,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = true // 已软删除
|
||
};
|
||
dbContext.AssessmentRecords.Add(record);
|
||
await dbContext.SaveChangesAsync();
|
||
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
|
||
// Act
|
||
var result = await service.GetRecordDetailAsync(record.Id);
|
||
|
||
// Assert
|
||
Assert.Null(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试 GetRecordReportAsync 当记录 ID 不存在时返回 null
|
||
/// **Validates: Requirements 2.4**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetRecordReportAsync_WithNonExistentId_ReturnsNull()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
var nonExistentId = 99999L;
|
||
|
||
// Act
|
||
var result = await service.GetRecordReportAsync(nonExistentId);
|
||
|
||
// Assert
|
||
Assert.Null(result);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 测试未完成记录请求报告返回错误 (Requirements 3.3)
|
||
|
||
/// <summary>
|
||
/// 测试 GetRecordReportAsync 当记录状态为 1(待测评)时返回 null
|
||
/// **Validates: Requirements 3.3**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetRecordReportAsync_WithStatus1_ReturnsNull()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var record = await CreateTestRecordWithStatus(dbContext, 1, 1); // Status = 1 (待测评)
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
|
||
// Act
|
||
var result = await service.GetRecordReportAsync(record.Id);
|
||
|
||
// Assert
|
||
Assert.Null(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试 GetRecordReportAsync 当记录状态为 2(测评中)时返回 null
|
||
/// **Validates: Requirements 3.3**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetRecordReportAsync_WithStatus2_ReturnsNull()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var record = await CreateTestRecordWithStatus(dbContext, 2, 2); // Status = 2 (测评中)
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
|
||
// Act
|
||
var result = await service.GetRecordReportAsync(record.Id);
|
||
|
||
// Assert
|
||
Assert.Null(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试 GetRecordReportAsync 当记录状态为 3(生成中)时返回 null
|
||
/// **Validates: Requirements 3.3**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetRecordReportAsync_WithStatus3_ReturnsNull()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var record = await CreateTestRecordWithStatus(dbContext, 3, 3); // Status = 3 (生成中)
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
|
||
// Act
|
||
var result = await service.GetRecordReportAsync(record.Id);
|
||
|
||
// Assert
|
||
Assert.Null(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试 GetRecordReportAsync 当记录状态为 4(已完成)时返回报告
|
||
/// **Validates: Requirements 3.3**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetRecordReportAsync_WithStatus4_ReturnsReport()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var record = await CreateTestRecordWithStatus(dbContext, 4, 4); // Status = 4 (已完成)
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
|
||
// Act
|
||
var result = await service.GetRecordReportAsync(record.Id);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(record.Id, result.Id);
|
||
Assert.Equal(4, result.Status);
|
||
Assert.Equal("已完成", result.StatusName);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 测试导出超限返回错误 (Requirements 4.3)
|
||
|
||
/// <summary>
|
||
/// 测试 ExportRecordsAsync 当结果集超过 10000 条记录时抛出 BusinessException
|
||
/// **Validates: Requirements 4.3**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task ExportRecordsAsync_WithExceedingLimit_ThrowsBusinessException()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
|
||
// 创建超过 10000 条记录
|
||
await CreateBulkTestRecords(dbContext, 10001);
|
||
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
var request = new AssessmentRecordQueryRequest { Page = 1, PageSize = 100 };
|
||
|
||
// Act & Assert
|
||
var exception = await Assert.ThrowsAsync<BusinessException>(
|
||
() => service.ExportRecordsAsync(request));
|
||
|
||
Assert.Equal(ErrorCodes.ExportDataTooLarge, exception.Code);
|
||
Assert.Equal("导出数据量过大,请缩小查询范围", exception.Message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试 ExportRecordsAsync 当结果集正好为 10000 条记录时不抛出异常
|
||
/// **Validates: Requirements 4.3**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task ExportRecordsAsync_WithExactLimit_DoesNotThrow()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
|
||
// 创建正好 10000 条记录
|
||
await CreateBulkTestRecords(dbContext, 10000);
|
||
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
var request = new AssessmentRecordQueryRequest { Page = 1, PageSize = 100 };
|
||
|
||
// Act
|
||
var result = await service.ExportRecordsAsync(request);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.True(result.Length > 0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试 ExportRecordsAsync 当结果集少于 10000 条记录时正常导出
|
||
/// **Validates: Requirements 4.3**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task ExportRecordsAsync_WithinLimit_ReturnsExcelBytes()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
|
||
// 创建少量记录
|
||
await CreateBulkTestRecords(dbContext, 100);
|
||
|
||
var service = new AssessmentRecordService(dbContext, _mockLogger.Object);
|
||
var request = new AssessmentRecordQueryRequest { Page = 1, PageSize = 100 };
|
||
|
||
// Act
|
||
var result = await service.ExportRecordsAsync(request);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.True(result.Length > 0);
|
||
// 验证是有效的 Excel 文件(XLSX 文件以 PK 开头,因为它是 ZIP 格式)
|
||
Assert.Equal(0x50, result[0]); // 'P'
|
||
Assert.Equal(0x4B, result[1]); // 'K'
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 创建内存数据库上下文
|
||
/// </summary>
|
||
private AdminBusinessDbContext CreateDbContext()
|
||
{
|
||
var options = new DbContextOptionsBuilder<AdminBusinessDbContext>()
|
||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||
.Options;
|
||
|
||
return new AdminBusinessDbContext(options);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建测试用户
|
||
/// </summary>
|
||
private User CreateTestUser(AdminBusinessDbContext dbContext, int seed)
|
||
{
|
||
var user = new User
|
||
{
|
||
Uid = $"{seed % 1000000:D6}",
|
||
OpenId = $"openid_{seed}_{Guid.NewGuid():N}",
|
||
Phone = $"138{seed % 100000000:D8}",
|
||
Nickname = $"User_{seed}",
|
||
UserLevel = 1,
|
||
Status = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Users.Add(user);
|
||
dbContext.SaveChanges();
|
||
return user;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建测试订单
|
||
/// </summary>
|
||
private Order CreateTestOrder(AdminBusinessDbContext dbContext, long userId, int seed)
|
||
{
|
||
var order = new Order
|
||
{
|
||
OrderNo = $"ORD{DateTime.Now:yyyyMMddHHmmss}{seed % 10000:D4}_{Guid.NewGuid():N}",
|
||
UserId = userId,
|
||
OrderType = 1,
|
||
ProductId = 1,
|
||
ProductName = $"测试商品_{seed}",
|
||
Amount = 100m,
|
||
PayAmount = 100m,
|
||
PayType = 1,
|
||
Status = 2,
|
||
PayTime = DateTime.Now,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Orders.Add(order);
|
||
dbContext.SaveChanges();
|
||
return order;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建测试测评类型
|
||
/// </summary>
|
||
private AssessmentType CreateTestAssessmentType(AdminBusinessDbContext dbContext, int seed)
|
||
{
|
||
var assessmentType = new AssessmentType
|
||
{
|
||
Name = $"测评类型_{seed}",
|
||
Code = $"TYPE_{seed}_{Guid.NewGuid():N}",
|
||
Price = 99.00m,
|
||
QuestionCount = 80,
|
||
Sort = seed,
|
||
Status = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.AssessmentTypes.Add(assessmentType);
|
||
dbContext.SaveChanges();
|
||
return assessmentType;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建指定状态的测评记录
|
||
/// </summary>
|
||
private async Task<AssessmentRecord> CreateTestRecordWithStatus(AdminBusinessDbContext dbContext, int seed, int status)
|
||
{
|
||
var user = CreateTestUser(dbContext, seed);
|
||
var order = CreateTestOrder(dbContext, user.Id, seed);
|
||
var assessmentType = CreateTestAssessmentType(dbContext, seed);
|
||
|
||
var record = new AssessmentRecord
|
||
{
|
||
UserId = user.Id,
|
||
OrderId = order.Id,
|
||
AssessmentTypeId = assessmentType.Id,
|
||
Name = $"测试人_{seed}",
|
||
Phone = $"139{seed % 100000000:D8}",
|
||
Gender = 1,
|
||
Age = 15,
|
||
EducationStage = 2,
|
||
Province = "北京市",
|
||
City = "北京市",
|
||
District = "朝阳区",
|
||
Status = status,
|
||
StartTime = status >= 2 ? DateTime.Now.AddMinutes(-30) : null,
|
||
SubmitTime = status >= 3 ? DateTime.Now.AddMinutes(-10) : null,
|
||
CompleteTime = status == 4 ? DateTime.Now : null,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.AssessmentRecords.Add(record);
|
||
await dbContext.SaveChangesAsync();
|
||
return record;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量创建测试记录(用于导出测试)
|
||
/// </summary>
|
||
private async Task CreateBulkTestRecords(AdminBusinessDbContext dbContext, int count)
|
||
{
|
||
var user = CreateTestUser(dbContext, 1);
|
||
var order = CreateTestOrder(dbContext, user.Id, 1);
|
||
var assessmentType = CreateTestAssessmentType(dbContext, 1);
|
||
|
||
var records = new List<AssessmentRecord>();
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
records.Add(new AssessmentRecord
|
||
{
|
||
UserId = user.Id,
|
||
OrderId = order.Id,
|
||
AssessmentTypeId = assessmentType.Id,
|
||
Name = $"测试人_{i}",
|
||
Phone = $"139{i % 100000000:D8}",
|
||
Gender = (i % 2) + 1,
|
||
Age = 10 + (i % 20),
|
||
EducationStage = (i % 6) + 1,
|
||
Province = "北京市",
|
||
City = "北京市",
|
||
District = "朝阳区",
|
||
Status = (i % 4) + 1,
|
||
CreateTime = DateTime.Now.AddMinutes(-i),
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
});
|
||
}
|
||
|
||
dbContext.AssessmentRecords.AddRange(records);
|
||
await dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
#endregion
|
||
}
|