286 lines
9.3 KiB
C#
286 lines
9.3 KiB
C#
using FsCheck;
|
|
using FsCheck.Xunit;
|
|
using HoneyBox.Admin.Business.Models;
|
|
using System.Text.Json;
|
|
using Xunit;
|
|
|
|
namespace HoneyBox.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// 响应格式属性测试
|
|
/// Property 16: API Response Format Consistency - API响应格式一致性
|
|
/// Property 17: Paginated Response Format - 分页响应格式一致性
|
|
/// </summary>
|
|
public class ResponseFormatPropertyTests
|
|
{
|
|
/// <summary>
|
|
/// Property 16: ApiResponse 成功响应格式一致性
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public Property Property16_ApiResponse_Success_HasConsistentFormat()
|
|
{
|
|
return Prop.ForAll(
|
|
Arb.From<string>().Filter(s => !string.IsNullOrEmpty(s)),
|
|
message =>
|
|
{
|
|
var response = ApiResponse.Success(message);
|
|
|
|
return response.Code == 0 &&
|
|
response.Message == message &&
|
|
response.Data == null;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 16: ApiResponse 带数据的成功响应格式一致性
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public Property Property16_ApiResponse_SuccessWithData_HasConsistentFormat()
|
|
{
|
|
return Prop.ForAll(
|
|
Arb.From<int>(),
|
|
Arb.From<string>().Filter(s => !string.IsNullOrEmpty(s)),
|
|
(data, message) =>
|
|
{
|
|
var response = ApiResponse.Success(data, message);
|
|
|
|
return response.Code == 0 &&
|
|
response.Message == message &&
|
|
response.Data != null &&
|
|
(int)response.Data == data;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 16: ApiResponse 错误响应格式一致性
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public Property Property16_ApiResponse_Error_HasConsistentFormat()
|
|
{
|
|
return Prop.ForAll(
|
|
Arb.From<int>().Filter(c => c != 0),
|
|
Arb.From<string>().Filter(s => !string.IsNullOrEmpty(s)),
|
|
(code, message) =>
|
|
{
|
|
var response = ApiResponse.Error(code, message);
|
|
|
|
return response.Code == code &&
|
|
response.Message == message &&
|
|
response.Data == null;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 16: ApiResponse<T> 泛型成功响应格式一致性
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public Property Property16_GenericApiResponse_Success_HasConsistentFormat()
|
|
{
|
|
return Prop.ForAll(
|
|
Arb.From<int>(),
|
|
data =>
|
|
{
|
|
var response = ApiResponse<int>.Success(data);
|
|
|
|
return response.Code == 0 &&
|
|
response.Message == "success" &&
|
|
response.Data == data;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 16: ApiResponse<T> 泛型错误响应格式一致性
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public Property Property16_GenericApiResponse_Error_HasConsistentFormat()
|
|
{
|
|
return Prop.ForAll(
|
|
Arb.From<int>().Filter(c => c != 0),
|
|
Arb.From<string>().Filter(s => !string.IsNullOrEmpty(s)),
|
|
(code, message) =>
|
|
{
|
|
var response = ApiResponse<int>.Error(code, message);
|
|
|
|
return response.Code == code &&
|
|
response.Message == message &&
|
|
EqualityComparer<int>.Default.Equals(response.Data, default);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 17: PagedResult 分页响应格式一致性
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public Property Property17_PagedResult_HasConsistentFormat()
|
|
{
|
|
return Prop.ForAll(
|
|
Arb.From<int>().Filter(p => p > 0),
|
|
Arb.From<int>().Filter(ps => ps > 0 && ps <= 100),
|
|
Arb.From<int>().Filter(t => t >= 0),
|
|
(page, pageSize, total) =>
|
|
{
|
|
var items = Enumerable.Range(1, Math.Min(pageSize, total)).ToList();
|
|
var result = new PagedResult<int>
|
|
{
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
Total = total,
|
|
Items = items
|
|
};
|
|
|
|
// 验证分页结果格式
|
|
return result.Page == page &&
|
|
result.PageSize == pageSize &&
|
|
result.Total == total &&
|
|
result.Items != null &&
|
|
result.Items.Count <= pageSize;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 17: PagedResult 总页数计算正确性
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public Property Property17_PagedResult_TotalPages_CalculatedCorrectly()
|
|
{
|
|
return Prop.ForAll(
|
|
Arb.From<int>().Filter(ps => ps > 0 && ps <= 100),
|
|
Arb.From<int>().Filter(t => t >= 0 && t <= 10000),
|
|
(pageSize, total) =>
|
|
{
|
|
var result = new PagedResult<int>
|
|
{
|
|
Page = 1,
|
|
PageSize = pageSize,
|
|
Total = total,
|
|
Items = new List<int>()
|
|
};
|
|
|
|
var expectedTotalPages = (int)Math.Ceiling((double)total / pageSize);
|
|
return result.TotalPages == expectedTotalPages;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 17: PagedResult 空结果处理
|
|
/// </summary>
|
|
[Fact]
|
|
public void Property17_PagedResult_EmptyResult_HasCorrectFormat()
|
|
{
|
|
var result = new PagedResult<int>
|
|
{
|
|
Page = 1,
|
|
PageSize = 20,
|
|
Total = 0,
|
|
Items = new List<int>()
|
|
};
|
|
|
|
Assert.Equal(1, result.Page);
|
|
Assert.Equal(20, result.PageSize);
|
|
Assert.Equal(0, result.Total);
|
|
Assert.Equal(0, result.TotalPages);
|
|
Assert.Empty(result.Items);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 16: BusinessErrorCodes 错误码唯一性
|
|
/// </summary>
|
|
[Fact]
|
|
public void Property16_BusinessErrorCodes_AreUnique()
|
|
{
|
|
var errorCodes = new[]
|
|
{
|
|
BusinessErrorCodes.ValidationError,
|
|
BusinessErrorCodes.NotFound,
|
|
BusinessErrorCodes.Unauthorized,
|
|
BusinessErrorCodes.Forbidden,
|
|
BusinessErrorCodes.InternalError,
|
|
BusinessErrorCodes.BusinessError
|
|
};
|
|
|
|
var uniqueCodes = errorCodes.Distinct().ToList();
|
|
Assert.Equal(errorCodes.Length, uniqueCodes.Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 16: BusinessErrorCodes 错误码非零
|
|
/// </summary>
|
|
[Fact]
|
|
public void Property16_BusinessErrorCodes_AreNonZero()
|
|
{
|
|
var errorCodes = new[]
|
|
{
|
|
BusinessErrorCodes.ValidationError,
|
|
BusinessErrorCodes.NotFound,
|
|
BusinessErrorCodes.Unauthorized,
|
|
BusinessErrorCodes.Forbidden,
|
|
BusinessErrorCodes.InternalError,
|
|
BusinessErrorCodes.BusinessError
|
|
};
|
|
|
|
foreach (var code in errorCodes)
|
|
{
|
|
Assert.NotEqual(0, code);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 16: ApiResponse JSON 序列化格式一致性
|
|
/// </summary>
|
|
[Fact]
|
|
public void Property16_ApiResponse_JsonSerialization_HasExpectedFormat()
|
|
{
|
|
var response = ApiResponse.Success("test data", "操作成功");
|
|
var json = JsonSerializer.Serialize(response, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
});
|
|
|
|
var doc = JsonDocument.Parse(json);
|
|
var root = doc.RootElement;
|
|
|
|
Assert.True(root.TryGetProperty("code", out var codeElement));
|
|
Assert.True(root.TryGetProperty("message", out var messageElement));
|
|
Assert.True(root.TryGetProperty("data", out var dataElement));
|
|
|
|
Assert.Equal(0, codeElement.GetInt32());
|
|
Assert.Equal("操作成功", messageElement.GetString());
|
|
Assert.Equal("test data", dataElement.GetString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 17: PagedResult JSON 序列化格式一致性
|
|
/// </summary>
|
|
[Fact]
|
|
public void Property17_PagedResult_JsonSerialization_HasExpectedFormat()
|
|
{
|
|
var result = new PagedResult<string>
|
|
{
|
|
Page = 1,
|
|
PageSize = 10,
|
|
Total = 100,
|
|
Items = new List<string> { "item1", "item2" }
|
|
};
|
|
|
|
var json = JsonSerializer.Serialize(result, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
});
|
|
|
|
var doc = JsonDocument.Parse(json);
|
|
var root = doc.RootElement;
|
|
|
|
Assert.True(root.TryGetProperty("page", out var pageElement));
|
|
Assert.True(root.TryGetProperty("pageSize", out var pageSizeElement));
|
|
Assert.True(root.TryGetProperty("total", out var totalElement));
|
|
Assert.True(root.TryGetProperty("totalPages", out var totalPagesElement));
|
|
Assert.True(root.TryGetProperty("items", out var itemsElement));
|
|
|
|
Assert.Equal(1, pageElement.GetInt32());
|
|
Assert.Equal(10, pageSizeElement.GetInt32());
|
|
Assert.Equal(100, totalElement.GetInt32());
|
|
Assert.Equal(10, totalPagesElement.GetInt32());
|
|
Assert.Equal(2, itemsElement.GetArrayLength());
|
|
}
|
|
}
|