1372 lines
49 KiB
C#
1372 lines
49 KiB
C#
using FsCheck;
|
|
using FsCheck.Xunit;
|
|
using HoneyBox.Admin.Business.Models;
|
|
using HoneyBox.Admin.Business.Models.FloatBall;
|
|
using HoneyBox.Admin.Business.Models.WelfareHouse;
|
|
using HoneyBox.Admin.Business.Services;
|
|
using HoneyBox.Model.Data;
|
|
using HoneyBox.Model.Entities;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
using Microsoft.Extensions.Logging;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace HoneyBox.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// 内容与辅助模块前端属性测试
|
|
/// Feature: content-auxiliary-frontend
|
|
/// </summary>
|
|
public class ContentAuxiliaryFrontendPropertyTests
|
|
{
|
|
private readonly Mock<ILogger<FloatBallService>> _mockFloatBallLogger = new();
|
|
private readonly Mock<ILogger<WelfareHouseService>> _mockWelfareHouseLogger = new();
|
|
|
|
#region Property 1: 分页参数正确传递
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 1: 分页参数正确传递**
|
|
/// For any pagination request to FloatBall list, the returned list should have at most pageSize items,
|
|
/// and the page and pageSize in response should match the request.
|
|
/// **Validates: Requirements 3.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallPagination_ShouldReturnCorrectPageSize(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 30) + 10; // 10 to 39 items
|
|
var pageSize = (seed.Get % 10) + 1; // 1 to 10 per page
|
|
var page = (seed.Get % 5) + 1; // page 1 to 5
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Create test float balls
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.FloatBallConfigs.Add(CreateTestFloatBall($"FloatBall{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new FloatBallListRequest { Page = page, PageSize = pageSize };
|
|
var result = service.GetFloatBallsAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify pagination parameters are correctly passed
|
|
return result.Total == itemCount &&
|
|
result.List.Count <= pageSize &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 1: 分页参数正确传递**
|
|
/// For any pagination request to WelfareHouse list, the returned list should have at most pageSize items,
|
|
/// and the page and pageSize in response should match the request.
|
|
/// **Validates: Requirements 7.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHousePagination_ShouldReturnCorrectPageSize(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 30) + 10; // 10 to 39 items
|
|
var pageSize = (seed.Get % 10) + 1; // 1 to 10 per page
|
|
var page = (seed.Get % 5) + 1; // page 1 to 5
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
// Create test welfare house entries
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.WelfareHouses.Add(CreateTestWelfareHouse($"WelfareHouse{i}", i));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new WelfareHouseListRequest { Page = page, PageSize = pageSize };
|
|
var result = service.GetWelfareHousesAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify pagination parameters are correctly passed
|
|
return result.Total == itemCount &&
|
|
result.List.Count <= pageSize &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 1: 分页参数正确传递**
|
|
/// The total count should remain consistent regardless of which page is requested for FloatBall.
|
|
/// **Validates: Requirements 3.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallPagination_TotalShouldBeConsistentAcrossPages(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 20) + 15; // 15 to 34 items
|
|
var pageSize = 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Create test float balls
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.FloatBallConfigs.Add(CreateTestFloatBall($"FloatBall{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
// Get multiple pages
|
|
var page1 = service.GetFloatBallsAsync(new FloatBallListRequest { Page = 1, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page2 = service.GetFloatBallsAsync(new FloatBallListRequest { Page = 2, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page3 = service.GetFloatBallsAsync(new FloatBallListRequest { Page = 3, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
|
|
// Total should be consistent across all pages
|
|
return page1.Total == page2.Total &&
|
|
page2.Total == page3.Total &&
|
|
page1.Total == itemCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 1: 分页参数正确传递**
|
|
/// The total count should remain consistent regardless of which page is requested for WelfareHouse.
|
|
/// **Validates: Requirements 7.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHousePagination_TotalShouldBeConsistentAcrossPages(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 20) + 15; // 15 to 34 items
|
|
var pageSize = 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
// Create test welfare house entries
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.WelfareHouses.Add(CreateTestWelfareHouse($"WelfareHouse{i}", i));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
// Get multiple pages
|
|
var page1 = service.GetWelfareHousesAsync(new WelfareHouseListRequest { Page = 1, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page2 = service.GetWelfareHousesAsync(new WelfareHouseListRequest { Page = 2, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page3 = service.GetWelfareHousesAsync(new WelfareHouseListRequest { Page = 3, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
|
|
// Total should be consistent across all pages
|
|
return page1.Total == page2.Total &&
|
|
page2.Total == page3.Total &&
|
|
page1.Total == itemCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 1: 分页参数正确传递**
|
|
/// Different pages should return different items (no overlap) for FloatBall.
|
|
/// **Validates: Requirements 3.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallPagination_DifferentPagesShouldNotOverlap(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 15) + 20; // 20 to 34 items
|
|
var pageSize = 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Create test float balls
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.FloatBallConfigs.Add(CreateTestFloatBall($"FloatBall{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
// Get first two pages
|
|
var page1 = service.GetFloatBallsAsync(new FloatBallListRequest { Page = 1, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page2 = service.GetFloatBallsAsync(new FloatBallListRequest { Page = 2, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
|
|
// IDs should not overlap between pages
|
|
var page1Ids = page1.List.Select(f => f.Id).ToHashSet();
|
|
var page2Ids = page2.List.Select(f => f.Id).ToHashSet();
|
|
|
|
return !page1Ids.Overlaps(page2Ids);
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 1: 分页参数正确传递**
|
|
/// Different pages should return different items (no overlap) for WelfareHouse.
|
|
/// **Validates: Requirements 7.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHousePagination_DifferentPagesShouldNotOverlap(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 15) + 20; // 20 to 34 items
|
|
var pageSize = 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
// Create test welfare house entries
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.WelfareHouses.Add(CreateTestWelfareHouse($"WelfareHouse{i}", i));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
// Get first two pages
|
|
var page1 = service.GetWelfareHousesAsync(new WelfareHouseListRequest { Page = 1, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page2 = service.GetWelfareHousesAsync(new WelfareHouseListRequest { Page = 2, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
|
|
// IDs should not overlap between pages
|
|
var page1Ids = page1.List.Select(w => w.Id).ToHashSet();
|
|
var page2Ids = page2.List.Select(w => w.Id).ToHashSet();
|
|
|
|
return !page1Ids.Overlaps(page2Ids);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private HoneyBoxDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
|
|
.Options;
|
|
|
|
return new HoneyBoxDbContext(options);
|
|
}
|
|
|
|
private FloatBallConfig CreateTestFloatBall(string title)
|
|
{
|
|
return new FloatBallConfig
|
|
{
|
|
Title = title,
|
|
Type = 1,
|
|
Image = "http://test.com/floatball.jpg",
|
|
LinkUrl = string.Empty,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1,
|
|
CreatedAt = DateTime.Now,
|
|
UpdatedAt = DateTime.Now
|
|
};
|
|
}
|
|
|
|
private WelfareHouse CreateTestWelfareHouse(string name, int sort)
|
|
{
|
|
return new WelfareHouse
|
|
{
|
|
Name = name,
|
|
Image = "http://test.com/welfare.jpg",
|
|
Url = "/welfare/test",
|
|
Sort = sort,
|
|
Status = 1,
|
|
CreateTime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
|
|
UpdateTime = (int)DateTimeOffset.Now.ToUnixTimeSeconds()
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 内容与辅助模块前端属性测试 - 第二部分
|
|
/// Feature: content-auxiliary-frontend
|
|
/// </summary>
|
|
public class ContentAuxiliaryFrontendPropertyTests_Part2
|
|
{
|
|
private readonly Mock<ILogger<FloatBallService>> _mockFloatBallLogger = new();
|
|
private readonly Mock<ILogger<WelfareHouseService>> _mockWelfareHouseLogger = new();
|
|
|
|
#region Property 2: 表单必填字段验证
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When FloatBall type is invalid (not 1 or 2), the system should reject the creation.
|
|
/// **Validates: Requirements 4.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallCreate_WithInvalidType_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Use invalid type values
|
|
var invalidTypes = new[] { 0, 3, 4, 5, -1, 100 };
|
|
var invalidType = invalidTypes[seed.Get % invalidTypes.Length];
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = "Test FloatBall",
|
|
Type = invalidType,
|
|
Image = "http://test.com/img.jpg",
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("类型必须为1(展示图片)或2(跳转页面)");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When FloatBall image is empty, the system should reject the creation.
|
|
/// **Validates: Requirements 4.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallCreate_WithEmptyImage_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var emptyImages = new[] { "", " ", null };
|
|
var emptyImage = emptyImages[seed.Get % emptyImages.Length];
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = "Test FloatBall",
|
|
Type = 1,
|
|
Image = emptyImage ?? string.Empty,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("悬浮球图片不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When FloatBall position X is empty, the system should reject the creation.
|
|
/// **Validates: Requirements 4.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallCreate_WithEmptyPositionX_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var emptyValues = new[] { "", " " };
|
|
var emptyValue = emptyValues[seed.Get % emptyValues.Length];
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = "Test FloatBall",
|
|
Type = 1,
|
|
Image = "http://test.com/img.jpg",
|
|
PositionX = emptyValue,
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("X轴位置不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When FloatBall position Y is empty, the system should reject the creation.
|
|
/// **Validates: Requirements 4.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallCreate_WithEmptyPositionY_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var emptyValues = new[] { "", " " };
|
|
var emptyValue = emptyValues[seed.Get % emptyValues.Length];
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = "Test FloatBall",
|
|
Type = 1,
|
|
Image = "http://test.com/img.jpg",
|
|
PositionX = "10",
|
|
PositionY = emptyValue,
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("Y轴位置不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When FloatBall width is empty, the system should reject the creation.
|
|
/// **Validates: Requirements 4.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallCreate_WithEmptyWidth_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var emptyValues = new[] { "", " " };
|
|
var emptyValue = emptyValues[seed.Get % emptyValues.Length];
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = "Test FloatBall",
|
|
Type = 1,
|
|
Image = "http://test.com/img.jpg",
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = emptyValue,
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("宽度不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When FloatBall height is empty, the system should reject the creation.
|
|
/// **Validates: Requirements 4.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallCreate_WithEmptyHeight_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var emptyValues = new[] { "", " " };
|
|
var emptyValue = emptyValues[seed.Get % emptyValues.Length];
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = "Test FloatBall",
|
|
Type = 1,
|
|
Image = "http://test.com/img.jpg",
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = emptyValue,
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("高度不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When FloatBall effect is invalid (not 0 or 1), the system should reject the creation.
|
|
/// **Validates: Requirements 4.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallCreate_WithInvalidEffect_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Use invalid effect values
|
|
var invalidEffects = new[] { 2, 3, -1, 100 };
|
|
var invalidEffect = invalidEffects[seed.Get % invalidEffects.Length];
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = "Test FloatBall",
|
|
Type = 1,
|
|
Image = "http://test.com/img.jpg",
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = invalidEffect,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("特效必须为0(无)或1(缩放动画)");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When WelfareHouse name is empty, the system should reject the creation.
|
|
/// **Validates: Requirements 8.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHouseCreate_WithEmptyName_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
var emptyNames = new[] { "", " " };
|
|
var emptyName = emptyNames[seed.Get % emptyNames.Length];
|
|
|
|
var request = new WelfareHouseCreateRequest
|
|
{
|
|
Name = emptyName,
|
|
Image = "http://test.com/img.jpg",
|
|
Url = "/welfare/test",
|
|
Sort = 1,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateWelfareHouseAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("名称不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When WelfareHouse image is empty, the system should reject the creation.
|
|
/// **Validates: Requirements 8.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHouseCreate_WithEmptyImage_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
var emptyImages = new[] { "", " " };
|
|
var emptyImage = emptyImages[seed.Get % emptyImages.Length];
|
|
|
|
var request = new WelfareHouseCreateRequest
|
|
{
|
|
Name = "Test WelfareHouse",
|
|
Image = emptyImage,
|
|
Url = "/welfare/test",
|
|
Sort = 1,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateWelfareHouseAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("图片不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When WelfareHouse URL is empty, the system should reject the creation.
|
|
/// **Validates: Requirements 8.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHouseCreate_WithEmptyUrl_ShouldFail(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
var emptyUrls = new[] { "", " " };
|
|
var emptyUrl = emptyUrls[seed.Get % emptyUrls.Length];
|
|
|
|
var request = new WelfareHouseCreateRequest
|
|
{
|
|
Name = "Test WelfareHouse",
|
|
Image = "http://test.com/img.jpg",
|
|
Url = emptyUrl,
|
|
Sort = 1,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
service.CreateWelfareHouseAsync(request).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
return ex.Message.Contains("跳转链接不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When all required fields are valid, FloatBall creation should succeed.
|
|
/// **Validates: Requirements 4.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallCreate_WithValidData_ShouldSucceed(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var validTypes = new[] { 1, 2 };
|
|
var validEffects = new[] { 0, 1 };
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = validTypes[seed.Get % validTypes.Length],
|
|
Image = "http://test.com/img.jpg",
|
|
PositionX = (seed.Get % 100).ToString(),
|
|
PositionY = (seed.Get % 100).ToString(),
|
|
Width = ((seed.Get % 50) + 20).ToString(),
|
|
Height = ((seed.Get % 50) + 20).ToString(),
|
|
Effect = validEffects[seed.Get % validEffects.Length],
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
var id = service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
var created = dbContext.FloatBallConfigs.Find(id);
|
|
return created != null &&
|
|
created.Type == request.Type &&
|
|
created.Image == request.Image &&
|
|
created.PositionX == request.PositionX &&
|
|
created.PositionY == request.PositionY &&
|
|
created.Width == request.Width &&
|
|
created.Height == request.Height &&
|
|
created.Effect == request.Effect;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 2: 表单必填字段验证**
|
|
/// When all required fields are valid, WelfareHouse creation should succeed.
|
|
/// **Validates: Requirements 8.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHouseCreate_WithValidData_ShouldSucceed(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
var request = new WelfareHouseCreateRequest
|
|
{
|
|
Name = $"Test WelfareHouse {seed.Get}",
|
|
Image = "http://test.com/img.jpg",
|
|
Url = $"/welfare/test{seed.Get}",
|
|
Sort = seed.Get % 100,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
var id = service.CreateWelfareHouseAsync(request).GetAwaiter().GetResult();
|
|
var created = dbContext.WelfareHouses.Find(id);
|
|
return created != null &&
|
|
created.Name == request.Name &&
|
|
created.Image == request.Image &&
|
|
created.Url == request.Url &&
|
|
created.Sort == request.Sort;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private HoneyBoxDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
|
|
.Options;
|
|
|
|
return new HoneyBoxDbContext(options);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 内容与辅助模块前端属性测试 - 第三部分
|
|
/// Feature: content-auxiliary-frontend
|
|
/// </summary>
|
|
public class ContentAuxiliaryFrontendPropertyTests_Part3
|
|
{
|
|
private readonly Mock<ILogger<FloatBallService>> _mockFloatBallLogger = new();
|
|
|
|
#region Property 3: 条件显示字段正确切换
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 3: 条件显示字段正确切换**
|
|
/// When FloatBall type is 1 (展示图片), the LinkUrl field should be optional and can be empty.
|
|
/// **Validates: Requirements 4.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallType1_LinkUrlShouldBeOptional(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Type 1 = 展示图片, LinkUrl should be optional
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = 1, // 展示图片
|
|
Image = "http://test.com/img.jpg",
|
|
LinkUrl = null, // Empty link URL
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
var id = service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
var created = dbContext.FloatBallConfigs.Find(id);
|
|
// For type 1, creation should succeed even without LinkUrl
|
|
return created != null && created.Type == 1;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 3: 条件显示字段正确切换**
|
|
/// When FloatBall type is 2 (跳转页面), the LinkUrl field can be provided for navigation.
|
|
/// **Validates: Requirements 4.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallType2_LinkUrlShouldBeUsed(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var linkUrl = $"/page/test{seed.Get}";
|
|
|
|
// Type 2 = 跳转页面, LinkUrl should be used
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = 2, // 跳转页面
|
|
Image = "http://test.com/img.jpg",
|
|
LinkUrl = linkUrl,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
try
|
|
{
|
|
var id = service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
var created = dbContext.FloatBallConfigs.Find(id);
|
|
// For type 2, LinkUrl should be stored correctly
|
|
return created != null &&
|
|
created.Type == 2 &&
|
|
created.LinkUrl == linkUrl;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 3: 条件显示字段正确切换**
|
|
/// When FloatBall type changes from 1 to 2, the LinkUrl should be updatable.
|
|
/// **Validates: Requirements 4.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallTypeChange_LinkUrlShouldBeUpdatable(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Create with type 1 (no link)
|
|
var createRequest = new FloatBallCreateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = 1,
|
|
Image = "http://test.com/img.jpg",
|
|
LinkUrl = null,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
var id = service.CreateFloatBallAsync(createRequest).GetAwaiter().GetResult();
|
|
|
|
// Update to type 2 with link
|
|
var newLinkUrl = $"/page/updated{seed.Get}";
|
|
var updateRequest = new FloatBallUpdateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = 2, // Change to 跳转页面
|
|
Image = "http://test.com/img.jpg",
|
|
LinkUrl = newLinkUrl,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
var result = service.UpdateFloatBallAsync(id, updateRequest).GetAwaiter().GetResult();
|
|
if (!result) return false;
|
|
|
|
var updated = dbContext.FloatBallConfigs.Find(id);
|
|
return updated != null &&
|
|
updated.Type == 2 &&
|
|
updated.LinkUrl == newLinkUrl;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 3: 条件显示字段正确切换**
|
|
/// When FloatBall type changes from 2 to 1, the LinkUrl should be preserved but not used.
|
|
/// **Validates: Requirements 4.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallTypeChange_FromType2ToType1_ShouldPreserveLinkUrl(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var originalLinkUrl = $"/page/original{seed.Get}";
|
|
|
|
// Create with type 2 (with link)
|
|
var createRequest = new FloatBallCreateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = 2,
|
|
Image = "http://test.com/img.jpg",
|
|
LinkUrl = originalLinkUrl,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
var id = service.CreateFloatBallAsync(createRequest).GetAwaiter().GetResult();
|
|
|
|
// Update to type 1 (展示图片)
|
|
var updateRequest = new FloatBallUpdateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = 1, // Change to 展示图片
|
|
Image = "http://test.com/img.jpg",
|
|
LinkUrl = originalLinkUrl, // Keep the link URL
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
var result = service.UpdateFloatBallAsync(id, updateRequest).GetAwaiter().GetResult();
|
|
if (!result) return false;
|
|
|
|
var updated = dbContext.FloatBallConfigs.Find(id);
|
|
// Type should be 1, and LinkUrl should be preserved (even if not used)
|
|
return updated != null &&
|
|
updated.Type == 1 &&
|
|
updated.LinkUrl == originalLinkUrl;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 3: 条件显示字段正确切换**
|
|
/// For any FloatBall, the type field should correctly determine the behavior.
|
|
/// Type 1 = 展示图片 (show image), Type 2 = 跳转页面 (jump to page).
|
|
/// **Validates: Requirements 4.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallType_ShouldDetermineBehavior(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var validTypes = new[] { 1, 2 };
|
|
var selectedType = validTypes[seed.Get % validTypes.Length];
|
|
var linkUrl = selectedType == 2 ? $"/page/test{seed.Get}" : null;
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = selectedType,
|
|
Image = "http://test.com/img.jpg",
|
|
LinkUrl = linkUrl,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
var id = service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
var created = dbContext.FloatBallConfigs.Find(id);
|
|
|
|
// Verify type is correctly stored
|
|
return created != null && created.Type == selectedType;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 3: 条件显示字段正确切换**
|
|
/// The response should correctly reflect the type and LinkUrl relationship.
|
|
/// **Validates: Requirements 4.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallResponse_ShouldReflectTypeAndLinkUrl(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var validTypes = new[] { 1, 2 };
|
|
var selectedType = validTypes[seed.Get % validTypes.Length];
|
|
var linkUrl = selectedType == 2 ? $"/page/test{seed.Get}" : string.Empty;
|
|
|
|
var request = new FloatBallCreateRequest
|
|
{
|
|
Title = $"Test FloatBall {seed.Get}",
|
|
Type = selectedType,
|
|
Image = "http://test.com/img.jpg",
|
|
LinkUrl = linkUrl,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1
|
|
};
|
|
|
|
var id = service.CreateFloatBallAsync(request).GetAwaiter().GetResult();
|
|
var response = service.GetFloatBallByIdAsync(id).GetAwaiter().GetResult();
|
|
|
|
// Verify response correctly reflects type and LinkUrl
|
|
return response != null &&
|
|
response.Type == selectedType &&
|
|
response.LinkUrl == (linkUrl ?? string.Empty);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private HoneyBoxDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
|
|
.Options;
|
|
|
|
return new HoneyBoxDbContext(options);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 内容与辅助模块前端属性测试 - 第四部分
|
|
/// Feature: content-auxiliary-frontend
|
|
/// </summary>
|
|
public class ContentAuxiliaryFrontendPropertyTests_Part4
|
|
{
|
|
private readonly Mock<ILogger<FloatBallService>> _mockFloatBallLogger = new();
|
|
private readonly Mock<ILogger<WelfareHouseService>> _mockWelfareHouseLogger = new();
|
|
|
|
#region Property 4: API响应格式一致性
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 4: API响应格式一致性**
|
|
/// For any FloatBall list API response, the response format should conform to the unified
|
|
/// PagedResult structure with correct pagination parameters.
|
|
/// **Validates: Requirements 11.4, 11.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallApiResponse_ShouldHaveConsistentPagedStructure(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 20) + 5;
|
|
var page = (seed.Get % 3) + 1;
|
|
var pageSize = (seed.Get % 10) + 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Create test float balls
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.FloatBallConfigs.Add(CreateTestFloatBall($"FloatBall{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new FloatBallListRequest { Page = page, PageSize = pageSize };
|
|
var result = service.GetFloatBallsAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify PagedResult structure
|
|
return result != null &&
|
|
result.List != null &&
|
|
result.Total >= 0 &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize &&
|
|
result.TotalPages == (int)Math.Ceiling((double)result.Total / result.PageSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 4: API响应格式一致性**
|
|
/// For any WelfareHouse list API response, the response format should conform to the unified
|
|
/// PagedResult structure with correct pagination parameters.
|
|
/// **Validates: Requirements 11.4, 11.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHouseApiResponse_ShouldHaveConsistentPagedStructure(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 20) + 5;
|
|
var page = (seed.Get % 3) + 1;
|
|
var pageSize = (seed.Get % 10) + 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
// Create test welfare house entries
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.WelfareHouses.Add(CreateTestWelfareHouse($"WelfareHouse{i}", i));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new WelfareHouseListRequest { Page = page, PageSize = pageSize };
|
|
var result = service.GetWelfareHousesAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify PagedResult structure
|
|
return result != null &&
|
|
result.List != null &&
|
|
result.Total >= 0 &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize &&
|
|
result.TotalPages == (int)Math.Ceiling((double)result.Total / result.PageSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 4: API响应格式一致性**
|
|
/// For any FloatBall detail API response, all required fields should be present.
|
|
/// **Validates: Requirements 11.4, 11.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallDetailResponse_ShouldHaveAllRequiredFields(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
var floatBall = CreateTestFloatBall($"FloatBall{seed.Get}");
|
|
dbContext.FloatBallConfigs.Add(floatBall);
|
|
dbContext.SaveChanges();
|
|
|
|
var response = service.GetFloatBallByIdAsync(floatBall.Id).GetAwaiter().GetResult();
|
|
|
|
// Verify all required fields are present
|
|
return response != null &&
|
|
response.Id > 0 &&
|
|
!string.IsNullOrEmpty(response.Image) &&
|
|
!string.IsNullOrEmpty(response.PositionX) &&
|
|
!string.IsNullOrEmpty(response.PositionY) &&
|
|
!string.IsNullOrEmpty(response.Width) &&
|
|
!string.IsNullOrEmpty(response.Height) &&
|
|
response.Type >= 1 && response.Type <= 2 &&
|
|
response.Effect >= 0 && response.Effect <= 1 &&
|
|
response.Status >= 0 && response.Status <= 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 4: API响应格式一致性**
|
|
/// For any WelfareHouse detail API response, all required fields should be present.
|
|
/// **Validates: Requirements 11.4, 11.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHouseDetailResponse_ShouldHaveAllRequiredFields(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
var welfareHouse = CreateTestWelfareHouse($"WelfareHouse{seed.Get}", seed.Get % 100);
|
|
dbContext.WelfareHouses.Add(welfareHouse);
|
|
dbContext.SaveChanges();
|
|
|
|
var response = service.GetWelfareHouseByIdAsync(welfareHouse.Id).GetAwaiter().GetResult();
|
|
|
|
// Verify all required fields are present
|
|
return response != null &&
|
|
response.Id > 0 &&
|
|
!string.IsNullOrEmpty(response.Name) &&
|
|
!string.IsNullOrEmpty(response.Image) &&
|
|
!string.IsNullOrEmpty(response.Url) &&
|
|
response.Sort >= 0 &&
|
|
response.Status >= 0 && response.Status <= 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 4: API响应格式一致性**
|
|
/// For any FloatBall list item, all required fields should be present.
|
|
/// **Validates: Requirements 11.4, 11.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool FloatBallListItem_ShouldHaveAllRequiredFields(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 10) + 1;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Create test float balls
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.FloatBallConfigs.Add(CreateTestFloatBall($"FloatBall{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new FloatBallListRequest { Page = 1, PageSize = 100 };
|
|
var result = service.GetFloatBallsAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify all items have required fields
|
|
return result.List.All(item =>
|
|
item.Id > 0 &&
|
|
!string.IsNullOrEmpty(item.Image) &&
|
|
!string.IsNullOrEmpty(item.PositionX) &&
|
|
!string.IsNullOrEmpty(item.PositionY) &&
|
|
!string.IsNullOrEmpty(item.Width) &&
|
|
!string.IsNullOrEmpty(item.Height) &&
|
|
item.Type >= 1 && item.Type <= 2 &&
|
|
item.Effect >= 0 && item.Effect <= 1 &&
|
|
item.Status >= 0 && item.Status <= 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 4: API响应格式一致性**
|
|
/// For any WelfareHouse list item, all required fields should be present.
|
|
/// **Validates: Requirements 11.4, 11.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool WelfareHouseListItem_ShouldHaveAllRequiredFields(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 10) + 1;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
// Create test welfare house entries
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.WelfareHouses.Add(CreateTestWelfareHouse($"WelfareHouse{i}", i));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new WelfareHouseListRequest { Page = 1, PageSize = 100 };
|
|
var result = service.GetWelfareHousesAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify all items have required fields
|
|
return result.List.All(item =>
|
|
item.Id > 0 &&
|
|
!string.IsNullOrEmpty(item.Name) &&
|
|
!string.IsNullOrEmpty(item.Image) &&
|
|
!string.IsNullOrEmpty(item.Url) &&
|
|
item.Sort >= 0 &&
|
|
item.Status >= 0 && item.Status <= 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 4: API响应格式一致性**
|
|
/// PagedResult should correctly calculate HasNextPage and HasPreviousPage.
|
|
/// **Validates: Requirements 11.4, 11.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool PagedResult_ShouldCorrectlyCalculateNavigationFlags(PositiveInt seed)
|
|
{
|
|
var itemCount = (seed.Get % 30) + 15; // 15 to 44 items
|
|
var pageSize = 5;
|
|
var totalPages = (int)Math.Ceiling((double)itemCount / pageSize);
|
|
var page = (seed.Get % totalPages) + 1; // Valid page number
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
|
|
// Create test float balls
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
dbContext.FloatBallConfigs.Add(CreateTestFloatBall($"FloatBall{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new FloatBallListRequest { Page = page, PageSize = pageSize };
|
|
var result = service.GetFloatBallsAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify navigation flags
|
|
var expectedHasNextPage = page < result.TotalPages;
|
|
var expectedHasPreviousPage = page > 1;
|
|
|
|
return result.HasNextPage == expectedHasNextPage &&
|
|
result.HasPreviousPage == expectedHasPreviousPage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: content-auxiliary-frontend, Property 4: API响应格式一致性**
|
|
/// Empty result should return valid PagedResult with empty list.
|
|
/// **Validates: Requirements 11.4, 11.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool EmptyResult_ShouldReturnValidPagedResult(PositiveInt seed)
|
|
{
|
|
var page = (seed.Get % 5) + 1;
|
|
var pageSize = (seed.Get % 10) + 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var floatBallService = new FloatBallService(dbContext, _mockFloatBallLogger.Object);
|
|
var welfareHouseService = new WelfareHouseService(dbContext, _mockWelfareHouseLogger.Object);
|
|
|
|
// Don't add any data - test empty result
|
|
|
|
var floatBallResult = floatBallService.GetFloatBallsAsync(new FloatBallListRequest { Page = page, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var welfareHouseResult = welfareHouseService.GetWelfareHousesAsync(new WelfareHouseListRequest { Page = page, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
|
|
// Verify empty results have valid structure
|
|
return floatBallResult != null &&
|
|
floatBallResult.List != null &&
|
|
floatBallResult.List.Count == 0 &&
|
|
floatBallResult.Total == 0 &&
|
|
floatBallResult.Page == page &&
|
|
floatBallResult.PageSize == pageSize &&
|
|
welfareHouseResult != null &&
|
|
welfareHouseResult.List != null &&
|
|
welfareHouseResult.List.Count == 0 &&
|
|
welfareHouseResult.Total == 0 &&
|
|
welfareHouseResult.Page == page &&
|
|
welfareHouseResult.PageSize == pageSize;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private HoneyBoxDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
|
|
.Options;
|
|
|
|
return new HoneyBoxDbContext(options);
|
|
}
|
|
|
|
private FloatBallConfig CreateTestFloatBall(string title)
|
|
{
|
|
return new FloatBallConfig
|
|
{
|
|
Title = title,
|
|
Type = 1,
|
|
Image = "http://test.com/floatball.jpg",
|
|
LinkUrl = string.Empty,
|
|
PositionX = "10",
|
|
PositionY = "20",
|
|
Width = "50",
|
|
Height = "50",
|
|
Effect = 0,
|
|
Status = 1,
|
|
CreatedAt = DateTime.Now,
|
|
UpdatedAt = DateTime.Now
|
|
};
|
|
}
|
|
|
|
private WelfareHouse CreateTestWelfareHouse(string name, int sort)
|
|
{
|
|
return new WelfareHouse
|
|
{
|
|
Name = name,
|
|
Image = "http://test.com/welfare.jpg",
|
|
Url = "/welfare/test",
|
|
Sort = sort,
|
|
Status = 1,
|
|
CreateTime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
|
|
UpdateTime = (int)DateTimeOffset.Now.ToUnixTimeSeconds()
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
}
|