using FsCheck; using FsCheck.Xunit; using Infrastructure; using Microsoft.Extensions.Configuration; using Xunit; using ZR.Model.Business.Dto; using ZR.Service.Business; namespace ZR.Tests { /// /// 导出分页属性测试 /// **Feature: work-camera-2.0, Property 4: Export Pagination Consistency** /// **Validates: Requirements 4.1, 4.2, 4.4** /// public class ExportPaginationPropertyTests : IClassFixture { public ExportPaginationPropertyTests(TestFixture fixture) { // 确保配置已初始化 } /// /// Property 4: Export Pagination Consistency - Page Size Capping /// /// *For any* export query with page size greater than 50, /// the system SHALL cap the page size to 50. /// /// **Validates: Requirements 4.1, 4.2, 4.4** /// [Property(MaxTest = 100)] public Property PageSize_ShouldBeCappedAt50() { // Generate page sizes including values above 50 var pageSizeGen = Gen.Choose(1, 200); return Prop.ForAll( pageSizeGen.ToArbitrary(), requestedPageSize => { var query = new WorkRecordExportQueryDto { PageNum = 1, PageSize = requestedPageSize }; // Simulate the capping logic from WorkRecordExportService var effectivePageSize = requestedPageSize > WorkRecordExportService.MaxPageSize ? WorkRecordExportService.MaxPageSize : requestedPageSize; // Verify: Effective page size should never exceed 50 if (effectivePageSize > 50) return false.Label($"Effective page size {effectivePageSize} exceeds maximum 50"); // Verify: If requested <= 50, effective should equal requested if (requestedPageSize <= 50 && effectivePageSize != requestedPageSize) return false.Label($"Page size {requestedPageSize} should not be modified when <= 50"); // Verify: If requested > 50, effective should be 50 if (requestedPageSize > 50 && effectivePageSize != 50) return false.Label($"Page size should be capped to 50 when requested {requestedPageSize}"); return true.Label("Page size capping works correctly"); }); } /// /// Property 4: Export Pagination Consistency - Page Number Validation /// /// *For any* export query, page number should be at least 1. /// /// **Validates: Requirements 4.1, 4.2, 4.4** /// [Property(MaxTest = 100)] public Property PageNumber_ShouldBeAtLeast1() { // Generate page numbers including invalid values var pageNumGen = Gen.Choose(-10, 100); return Prop.ForAll( pageNumGen.ToArbitrary(), requestedPageNum => { var query = new WorkRecordExportQueryDto { PageNum = requestedPageNum, PageSize = 50 }; // Simulate the validation logic var effectivePageNum = requestedPageNum <= 0 ? 1 : requestedPageNum; // Verify: Effective page number should be at least 1 if (effectivePageNum < 1) return false.Label($"Effective page number {effectivePageNum} should be at least 1"); // Verify: If requested > 0, effective should equal requested if (requestedPageNum > 0 && effectivePageNum != requestedPageNum) return false.Label($"Page number {requestedPageNum} should not be modified when > 0"); // Verify: If requested <= 0, effective should be 1 if (requestedPageNum <= 0 && effectivePageNum != 1) return false.Label($"Page number should default to 1 when requested {requestedPageNum}"); return true.Label("Page number validation works correctly"); }); } /// /// Property 4: Export Pagination Consistency - Pagination Calculation /// /// *For any* total record count N and page size P (capped at 50), /// the total number of pages should be ceil(N/P). /// /// **Validates: Requirements 4.1, 4.2, 4.4** /// [Property(MaxTest = 100)] public Property TotalPages_ShouldBeCalculatedCorrectly() { // Generate total counts and page sizes var paramsGen = from totalCount in Gen.Choose(0, 1000) from pageSize in Gen.Choose(1, 100) select new { totalCount, pageSize }; return Prop.ForAll( paramsGen.ToArbitrary(), p => { // Cap page size at 50 var effectivePageSize = Math.Min(p.pageSize, WorkRecordExportService.MaxPageSize); // Calculate expected total pages var expectedTotalPages = p.totalCount == 0 ? 0 : (int)Math.Ceiling((double)p.totalCount / effectivePageSize); // Verify: Total pages calculation if (p.totalCount == 0 && expectedTotalPages != 0) return false.Label("Total pages should be 0 when no records"); if (p.totalCount > 0 && expectedTotalPages < 1) return false.Label("Total pages should be at least 1 when records exist"); // Verify: All records can be retrieved by iterating through pages var totalRecordsFromPages = 0; for (int page = 1; page <= expectedTotalPages; page++) { var recordsInPage = page < expectedTotalPages ? effectivePageSize : p.totalCount - (expectedTotalPages - 1) * effectivePageSize; totalRecordsFromPages += recordsInPage; } if (totalRecordsFromPages != p.totalCount) return false.Label($"Iterating through {expectedTotalPages} pages should yield {p.totalCount} records, got {totalRecordsFromPages}"); return true.Label("Pagination calculation is correct"); }); } /// /// Property 4: Export Pagination Consistency - No Duplicate Records /// /// *For any* pagination scenario, records on different pages should not overlap. /// This is verified by checking that skip values are correctly calculated. /// /// **Validates: Requirements 4.1, 4.2, 4.4** /// [Property(MaxTest = 100)] public Property PaginationSkip_ShouldNotCauseDuplicates() { // Generate pagination parameters var paramsGen = from pageNum in Gen.Choose(1, 50) from pageSize in Gen.Choose(1, 100) select new { pageNum, pageSize }; return Prop.ForAll( paramsGen.ToArbitrary(), p => { // Cap page size at 50 var effectivePageSize = Math.Min(p.pageSize, WorkRecordExportService.MaxPageSize); // Calculate skip value for current page var skip = (p.pageNum - 1) * effectivePageSize; // Calculate skip value for next page var nextPageSkip = p.pageNum * effectivePageSize; // Verify: Skip values should not overlap if (skip < 0) return false.Label($"Skip value {skip} should not be negative"); // Verify: Next page skip should be exactly pageSize more than current if (nextPageSkip - skip != effectivePageSize) return false.Label($"Gap between pages should be {effectivePageSize}, got {nextPageSkip - skip}"); // Verify: Records from page N end where page N+1 begins var pageEndIndex = skip + effectivePageSize - 1; var nextPageStartIndex = nextPageSkip; if (nextPageStartIndex != pageEndIndex + 1) return false.Label($"Page {p.pageNum} ends at {pageEndIndex}, page {p.pageNum + 1} should start at {pageEndIndex + 1}"); return true.Label("Pagination skip values are correct"); }); } /// /// Property 4: Export Pagination Consistency - Query DTO Default Values /// /// *For any* WorkRecordExportQueryDto, default values should be: /// - PageNum = 1 /// - PageSize = 50 /// /// **Validates: Requirements 4.1, 4.2, 4.4** /// [Fact] public void QueryDto_ShouldHaveCorrectDefaults() { var query = new WorkRecordExportQueryDto(); Assert.Equal(1, query.PageNum); Assert.Equal(50, query.PageSize); } /// /// Property 4: Export Pagination Consistency - MaxPageSize Constant /// /// The MaxPageSize constant should be 50 as per requirements. /// /// **Validates: Requirements 4.1, 4.2, 4.4** /// [Fact] public void MaxPageSize_ShouldBe50() { Assert.Equal(50, WorkRecordExportService.MaxPageSize); } } }