From 3b3a29cb91a49fa0596b8b73c21d52b6d495a96a Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Fri, 30 Jan 2026 00:53:58 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=20tests=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GetAnnouncementByIdQueryHandlerTests.cs | 54 -- ...etTenantsAnnouncementsQueryHandlerTests.cs | 122 ---- ...CreateAnnouncementCommandValidatorTests.cs | 110 ---- ...ublishAnnouncementCommandValidatorTests.cs | 62 -- ...RevokeAnnouncementCommandValidatorTests.cs | 62 -- ...UpdateAnnouncementCommandValidatorTests.cs | 88 --- .../TakeoutSaaS.Application.Tests.csproj | 35 - .../TestUtilities/AnnouncementTestData.cs | 72 --- .../App/Dictionary/DictionaryApiTests.cs | 608 ------------------ .../Tenants/AnnouncementRegressionTests.cs | 98 --- .../App/Tenants/AnnouncementWorkflowTests.cs | 218 ------- .../TenantAnnouncementRepositoryScopeTests.cs | 58 -- .../PermissionAuthorizationHandlerTests.cs | 80 --- .../Fixtures/DictionarySqliteTestDatabase.cs | 58 -- .../Fixtures/SqliteTestDatabase.cs | 58 -- .../Fixtures/TestCurrentUserAccessor.cs | 10 - .../Fixtures/TestDictionaryHybridCache.cs | 46 -- .../Fixtures/TestIdGenerator.cs | 20 - .../Fixtures/TestTenantProvider.cs | 10 - .../AnnouncementQueryPerformanceTests.cs | 70 -- .../TakeoutSaaS.Integration.Tests.csproj | 39 -- 21 files changed, 1978 deletions(-) delete mode 100644 tests/TakeoutSaaS.Application.Tests/App/Tenants/Handlers/GetAnnouncementByIdQueryHandlerTests.cs delete mode 100644 tests/TakeoutSaaS.Application.Tests/App/Tenants/Handlers/GetTenantsAnnouncementsQueryHandlerTests.cs delete mode 100644 tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/CreateAnnouncementCommandValidatorTests.cs delete mode 100644 tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/PublishAnnouncementCommandValidatorTests.cs delete mode 100644 tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/RevokeAnnouncementCommandValidatorTests.cs delete mode 100644 tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/UpdateAnnouncementCommandValidatorTests.cs delete mode 100644 tests/TakeoutSaaS.Application.Tests/TakeoutSaaS.Application.Tests.csproj delete mode 100644 tests/TakeoutSaaS.Application.Tests/TestUtilities/AnnouncementTestData.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/App/Dictionary/DictionaryApiTests.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/App/Tenants/AnnouncementRegressionTests.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/App/Tenants/AnnouncementWorkflowTests.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/App/Tenants/TenantAnnouncementRepositoryScopeTests.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/Authorization/PermissionAuthorizationHandlerTests.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/Fixtures/DictionarySqliteTestDatabase.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/Fixtures/SqliteTestDatabase.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/Fixtures/TestCurrentUserAccessor.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/Fixtures/TestDictionaryHybridCache.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/Fixtures/TestIdGenerator.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/Fixtures/TestTenantProvider.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/Performance/AnnouncementQueryPerformanceTests.cs delete mode 100644 tests/TakeoutSaaS.Integration.Tests/TakeoutSaaS.Integration.Tests.csproj diff --git a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Handlers/GetAnnouncementByIdQueryHandlerTests.cs b/tests/TakeoutSaaS.Application.Tests/App/Tenants/Handlers/GetAnnouncementByIdQueryHandlerTests.cs deleted file mode 100644 index a6cdfe8..0000000 --- a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Handlers/GetAnnouncementByIdQueryHandlerTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using FluentAssertions; -using Moq; -using TakeoutSaaS.Application.App.Tenants.Handlers; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Application.Tests.TestUtilities; -using TakeoutSaaS.Domain.Tenants.Entities; -using TakeoutSaaS.Domain.Tenants.Repositories; - -namespace TakeoutSaaS.Application.Tests.App.Tenants.Handlers; - -/// -/// 单元测试。 -/// -public sealed class GetAnnouncementByIdQueryHandlerTests -{ - [Fact] - public async Task GivenAnnouncementMissing_WhenHandle_ThenReturnsNull() - { - // 1. 准备 - var announcementRepository = new Mock(); - announcementRepository - .Setup(x => x.FindByIdAsync(99, 500, It.IsAny())) - .ReturnsAsync((TenantAnnouncement?)null); - - // 2. (空行后) 执行 - var handler = new GetAnnouncementByIdQueryHandler(announcementRepository.Object); - var result = await handler.Handle(new GetAnnouncementByIdQuery { TenantId = 99, AnnouncementId = 500 }, CancellationToken.None); - - // 3. (空行后) 断言 - result.Should().BeNull(); - } - - [Fact] - public async Task GivenAnnouncementExists_WhenHandle_ThenReturnsDtoWithoutReadState() - { - // 1. 准备 - var announcement = AnnouncementTestData.CreateAnnouncement(10, 200, 1, DateTime.UtcNow); - var announcementRepository = new Mock(); - announcementRepository - .Setup(x => x.FindByIdAsync(200, 10, It.IsAny())) - .ReturnsAsync(announcement); - - // 2. (空行后) 执行 - var handler = new GetAnnouncementByIdQueryHandler(announcementRepository.Object); - var result = await handler.Handle(new GetAnnouncementByIdQuery { TenantId = 200, AnnouncementId = 10 }, CancellationToken.None); - - // 3. (空行后) 断言 - result.Should().NotBeNull(); - result!.Id.Should().Be(announcement.Id); - result.TenantId.Should().Be(announcement.TenantId); - result.IsRead.Should().BeFalse(); - result.ReadAt.Should().BeNull(); - } -} diff --git a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Handlers/GetTenantsAnnouncementsQueryHandlerTests.cs b/tests/TakeoutSaaS.Application.Tests/App/Tenants/Handlers/GetTenantsAnnouncementsQueryHandlerTests.cs deleted file mode 100644 index 7fbf0b1..0000000 --- a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Handlers/GetTenantsAnnouncementsQueryHandlerTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -using FluentAssertions; -using Moq; -using TakeoutSaaS.Application.App.Tenants.Handlers; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Application.Tests.TestUtilities; -using TakeoutSaaS.Domain.Tenants.Repositories; - -namespace TakeoutSaaS.Application.Tests.App.Tenants.Handlers; - -/// -/// 单元测试。 -/// -public sealed class GetTenantsAnnouncementsQueryHandlerTests -{ - [Fact] - public async Task GivenQuery_WhenHandle_ThenOrdersAndPaginates() - { - // 1. 准备 - var announcements = new List - { - AnnouncementTestData.CreateAnnouncement(1, 42, priority: 1, effectiveFrom: DateTime.UtcNow.AddDays(-1)), - AnnouncementTestData.CreateAnnouncement(2, 42, priority: 2, effectiveFrom: DateTime.UtcNow.AddDays(-3)), - AnnouncementTestData.CreateAnnouncement(3, 42, priority: 2, effectiveFrom: DateTime.UtcNow.AddDays(-1)), - AnnouncementTestData.CreateAnnouncement(4, 42, priority: 0, effectiveFrom: DateTime.UtcNow) - }; - - // 2. (空行后) 模拟数据库端排序:按 priority DESC, effectiveFrom DESC - var sortedAnnouncements = announcements - .OrderByDescending(x => x.Priority) - .ThenByDescending(x => x.EffectiveFrom) - .ToList(); - var announcementRepository = new Mock(); - announcementRepository - .Setup(x => x.SearchAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(sortedAnnouncements); - - // 3. (空行后) 执行 - var handler = new GetTenantsAnnouncementsQueryHandler(announcementRepository.Object); - var query = new GetTenantsAnnouncementsQuery - { - TenantId = 42, - Page = 2, - PageSize = 2 - }; - var result = await handler.Handle(query, CancellationToken.None); - - // 4. (空行后) 断言 - announcementRepository.Verify(x => x.SearchAsync( - query.TenantId, - query.Keyword, - query.Status, - query.AnnouncementType, - query.IsActive, - query.EffectiveFrom, - query.EffectiveTo, - null, - true, - 12, // estimatedLimit = page * size * 3 = 2 * 2 * 3 = 12 - It.IsAny()), Times.Once); - - result.TotalCount.Should().Be(4); - result.Items.Select(x => x.Id).Should().Equal(1, 4); - result.Page.Should().Be(2); - result.PageSize.Should().Be(2); - } - - [Fact] - public async Task GivenOnlyEffective_WhenHandle_ThenFiltersScheduledPublish() - { - // 1. 准备 - var announcement1 = AnnouncementTestData.CreateAnnouncement(1, 42, priority: 1, effectiveFrom: DateTime.UtcNow.AddDays(-1)); - announcement1.ScheduledPublishAt = DateTime.UtcNow.AddMinutes(-10); - var announcement2 = AnnouncementTestData.CreateAnnouncement(2, 42, priority: 1, effectiveFrom: DateTime.UtcNow.AddDays(-1)); - announcement2.ScheduledPublishAt = DateTime.UtcNow.AddMinutes(30); - var announcements = new List - { - announcement1, - announcement2 - }; - var announcementRepository = new Mock(); - announcementRepository - .Setup(x => x.SearchAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(announcements); - - // 2. (空行后) 执行 - var handler = new GetTenantsAnnouncementsQueryHandler(announcementRepository.Object); - var query = new GetTenantsAnnouncementsQuery - { - TenantId = 42, - OnlyEffective = true, - Page = 1, - PageSize = 10 - }; - var result = await handler.Handle(query, CancellationToken.None); - - // 3. (空行后) 断言 - result.Items.Should().ContainSingle(); - result.Items[0].Id.Should().Be(1); - } -} diff --git a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/CreateAnnouncementCommandValidatorTests.cs b/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/CreateAnnouncementCommandValidatorTests.cs deleted file mode 100644 index 9abd854..0000000 --- a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/CreateAnnouncementCommandValidatorTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -using FluentValidation.TestHelper; -using TakeoutSaaS.Application.App.Tenants.Validators; -using TakeoutSaaS.Application.Tests.TestUtilities; -using TakeoutSaaS.Domain.Tenants.Enums; - -namespace TakeoutSaaS.Application.Tests.App.Tenants.Validators; - -public sealed class CreateAnnouncementCommandValidatorTests -{ - private readonly CreateAnnouncementCommandValidator _validator = new(); - - [Fact] - public void GivenEmptyTitle_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidCreateCommand() with { Title = "" }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Title); - } - - [Fact] - public void GivenTitleTooLong_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidCreateCommand() with { Title = new string('A', 129) }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Title); - } - - [Fact] - public void GivenEmptyContent_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidCreateCommand() with { Content = "" }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Content); - } - - [Fact] - public void GivenEmptyTargetType_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidCreateCommand() with { TargetType = "" }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.TargetType); - } - - [Fact] - public void GivenTenantIdZeroAndNotPlatform_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidCreateCommand() with - { - TenantId = 0, - PublisherScope = PublisherScope.Tenant - }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x); - } - - [Fact] - public void GivenEffectiveToBeforeFrom_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidCreateCommand() with - { - EffectiveFrom = DateTime.UtcNow, - EffectiveTo = DateTime.UtcNow.AddMinutes(-5) - }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.EffectiveTo); - } - - [Fact] - public void GivenValidCommand_WhenValidate_ThenShouldNotHaveErrors() - { - // Arrange - var command = AnnouncementTestData.CreateValidCreateCommand(); - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } -} diff --git a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/PublishAnnouncementCommandValidatorTests.cs b/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/PublishAnnouncementCommandValidatorTests.cs deleted file mode 100644 index e325755..0000000 --- a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/PublishAnnouncementCommandValidatorTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using FluentValidation.TestHelper; -using TakeoutSaaS.Application.App.Tenants.Validators; -using TakeoutSaaS.Application.Tests.TestUtilities; - -namespace TakeoutSaaS.Application.Tests.App.Tenants.Validators; - -public sealed class PublishAnnouncementCommandValidatorTests -{ - private readonly PublishAnnouncementCommandValidator _validator = new(); - - [Fact] - public void GivenAnnouncementIdZero_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidPublishCommand() with { AnnouncementId = 0 }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.AnnouncementId); - } - - [Fact] - public void GivenNullRowVersion_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidPublishCommand() with { RowVersion = null! }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.RowVersion); - } - - [Fact] - public void GivenEmptyRowVersion_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidPublishCommand() with { RowVersion = Array.Empty() }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.RowVersion); - } - - [Fact] - public void GivenValidCommand_WhenValidate_ThenShouldNotHaveErrors() - { - // Arrange - var command = AnnouncementTestData.CreateValidPublishCommand(); - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } -} diff --git a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/RevokeAnnouncementCommandValidatorTests.cs b/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/RevokeAnnouncementCommandValidatorTests.cs deleted file mode 100644 index 694c8a5..0000000 --- a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/RevokeAnnouncementCommandValidatorTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using FluentValidation.TestHelper; -using TakeoutSaaS.Application.App.Tenants.Validators; -using TakeoutSaaS.Application.Tests.TestUtilities; - -namespace TakeoutSaaS.Application.Tests.App.Tenants.Validators; - -public sealed class RevokeAnnouncementCommandValidatorTests -{ - private readonly RevokeAnnouncementCommandValidator _validator = new(); - - [Fact] - public void GivenAnnouncementIdZero_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidRevokeCommand() with { AnnouncementId = 0 }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.AnnouncementId); - } - - [Fact] - public void GivenNullRowVersion_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidRevokeCommand() with { RowVersion = null! }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.RowVersion); - } - - [Fact] - public void GivenEmptyRowVersion_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidRevokeCommand() with { RowVersion = Array.Empty() }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.RowVersion); - } - - [Fact] - public void GivenValidCommand_WhenValidate_ThenShouldNotHaveErrors() - { - // Arrange - var command = AnnouncementTestData.CreateValidRevokeCommand(); - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } -} diff --git a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/UpdateAnnouncementCommandValidatorTests.cs b/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/UpdateAnnouncementCommandValidatorTests.cs deleted file mode 100644 index bed8efe..0000000 --- a/tests/TakeoutSaaS.Application.Tests/App/Tenants/Validators/UpdateAnnouncementCommandValidatorTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -using FluentValidation.TestHelper; -using TakeoutSaaS.Application.App.Tenants.Validators; -using TakeoutSaaS.Application.Tests.TestUtilities; - -namespace TakeoutSaaS.Application.Tests.App.Tenants.Validators; - -public sealed class UpdateAnnouncementCommandValidatorTests -{ - private readonly UpdateAnnouncementCommandValidator _validator = new(); - - [Fact] - public void GivenEmptyTitle_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidUpdateCommand() with { Title = "" }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Title); - } - - [Fact] - public void GivenTitleTooLong_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidUpdateCommand() with { Title = new string('A', 129) }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Title); - } - - [Fact] - public void GivenEmptyContent_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidUpdateCommand() with { Content = "" }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Content); - } - - [Fact] - public void GivenNullRowVersion_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidUpdateCommand() with { RowVersion = null! }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.RowVersion); - } - - [Fact] - public void GivenEmptyRowVersion_WhenValidate_ThenShouldHaveError() - { - // Arrange - var command = AnnouncementTestData.CreateValidUpdateCommand() with { RowVersion = Array.Empty() }; - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.RowVersion); - } - - [Fact] - public void GivenValidCommand_WhenValidate_ThenShouldNotHaveErrors() - { - // Arrange - var command = AnnouncementTestData.CreateValidUpdateCommand(); - - // Act - var result = _validator.TestValidate(command); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } -} diff --git a/tests/TakeoutSaaS.Application.Tests/TakeoutSaaS.Application.Tests.csproj b/tests/TakeoutSaaS.Application.Tests/TakeoutSaaS.Application.Tests.csproj deleted file mode 100644 index 0e333a1..0000000 --- a/tests/TakeoutSaaS.Application.Tests/TakeoutSaaS.Application.Tests.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - net10.0 - enable - enable - false - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - diff --git a/tests/TakeoutSaaS.Application.Tests/TestUtilities/AnnouncementTestData.cs b/tests/TakeoutSaaS.Application.Tests/TestUtilities/AnnouncementTestData.cs deleted file mode 100644 index 6ac7f18..0000000 --- a/tests/TakeoutSaaS.Application.Tests/TestUtilities/AnnouncementTestData.cs +++ /dev/null @@ -1,72 +0,0 @@ -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Domain.Tenants.Entities; -using TakeoutSaaS.Domain.Tenants.Enums; - -namespace TakeoutSaaS.Application.Tests.TestUtilities; - -public static class AnnouncementTestData -{ - public static CreateTenantAnnouncementCommand CreateValidCreateCommand() - => new() - { - TenantId = 100, - Title = "公告标题", - Content = "公告内容", - AnnouncementType = TenantAnnouncementType.System, - Priority = 1, - EffectiveFrom = DateTime.UtcNow.AddHours(-1), - EffectiveTo = DateTime.UtcNow.AddHours(2), - PublisherScope = PublisherScope.Tenant, - TargetType = "ALL_TENANTS", - TargetParameters = null - }; - - public static UpdateTenantAnnouncementCommand CreateValidUpdateCommand() - => new() - { - TenantId = 100, - AnnouncementId = 9001, - Title = "更新公告", - Content = "更新内容", - TargetType = "ALL_TENANTS", - TargetParameters = null, - RowVersion = new byte[] { 1, 2, 3 } - }; - - public static PublishAnnouncementCommand CreateValidPublishCommand() - => new() - { - AnnouncementId = 9001, - RowVersion = new byte[] { 1, 2, 3 } - }; - - public static RevokeAnnouncementCommand CreateValidRevokeCommand() - => new() - { - AnnouncementId = 9001, - RowVersion = new byte[] { 1, 2, 3 } - }; - - public static TenantAnnouncement CreateAnnouncement( - long id, - long tenantId, - int priority, - DateTime effectiveFrom, - AnnouncementStatus status = AnnouncementStatus.Draft) - => new() - { - Id = id, - TenantId = tenantId, - Title = $"公告-{id}", - Content = "内容", - AnnouncementType = TenantAnnouncementType.System, - Priority = priority, - EffectiveFrom = effectiveFrom, - EffectiveTo = null, - PublisherScope = PublisherScope.Tenant, - Status = status, - TargetType = string.Empty, - TargetParameters = null, - RowVersion = new byte[] { 1, 1, 1 } - }; -} diff --git a/tests/TakeoutSaaS.Integration.Tests/App/Dictionary/DictionaryApiTests.cs b/tests/TakeoutSaaS.Integration.Tests/App/Dictionary/DictionaryApiTests.cs deleted file mode 100644 index dde9318..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/App/Dictionary/DictionaryApiTests.cs +++ /dev/null @@ -1,608 +0,0 @@ -using System.Text; -using System.Text.Json; -using FluentAssertions; -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging.Abstractions; -using TakeoutSaaS.Application.Dictionary.Contracts; -using TakeoutSaaS.Application.Dictionary.Services; -using TakeoutSaaS.Domain.Dictionary.Entities; -using TakeoutSaaS.Domain.Dictionary.Enums; -using TakeoutSaaS.Domain.Dictionary.ValueObjects; -using TakeoutSaaS.Infrastructure.Dictionary.ImportExport; -using TakeoutSaaS.Infrastructure.Dictionary.Repositories; -using TakeoutSaaS.Integration.Tests.Fixtures; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Exceptions; - -namespace TakeoutSaaS.Integration.Tests.App.Dictionary; - -public sealed class DictionaryApiTests -{ - [Fact] - public async Task Test_CreateDictionaryGroup_ReturnsCreated() - { - using var database = new DictionarySqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 0, userId: 11); - var tenantProvider = new TestTenantProvider(0); - var cache = new TestDictionaryHybridCache(); - - var service = BuildCommandService(context, tenantProvider, cache); - var result = await service.CreateGroupAsync(new CreateDictionaryGroupRequest - { - Code = "ORDER_STATUS", - Name = "Order Status", - Scope = DictionaryScope.System, - AllowOverride = true, - Description = "Order lifecycle" - }); - - result.Id.Should().NotBe(0); - result.Code.Should().Be("order_status"); - result.Scope.Should().Be(DictionaryScope.System); - - using var verifyContext = database.CreateContext(tenantId: 0); - var stored = await verifyContext.DictionaryGroups - .IgnoreQueryFilters() - .FirstOrDefaultAsync(group => group.Id == result.Id); - - stored.Should().NotBeNull(); - stored!.TenantId.Should().Be(0); - stored.Code.Value.Should().Be("order_status"); - } - - [Fact] - public async Task Test_GetDictionaryGroups_ReturnsPaged() - { - using var database = new DictionarySqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 0, userId: 11); - - context.DictionaryGroups.AddRange( - CreateSystemGroup(101, "order_status"), - CreateSystemGroup(102, "payment_method")); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var tenantProvider = new TestTenantProvider(0); - var cache = new TestDictionaryHybridCache(); - var service = BuildQueryService(context, tenantProvider, cache); - - var page = await service.GetGroupsAsync(new DictionaryGroupQuery - { - Scope = DictionaryScope.System, - Page = 1, - PageSize = 1 - }); - - page.TotalCount.Should().Be(2); - page.Items.Should().HaveCount(1); - } - - [Fact] - public async Task Test_UpdateDictionaryGroup_WithValidRowVersion_ReturnsOk() - { - using var database = new DictionarySqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 0, userId: 11); - var tenantProvider = new TestTenantProvider(0); - var cache = new TestDictionaryHybridCache(); - var service = BuildCommandService(context, tenantProvider, cache); - - var created = await service.CreateGroupAsync(new CreateDictionaryGroupRequest - { - Code = "PAYMENT_METHOD", - Name = "Payment Method", - Scope = DictionaryScope.System, - AllowOverride = true - }); - - var updated = await service.UpdateGroupAsync(created.Id, new UpdateDictionaryGroupRequest - { - Name = "Payment Method Updated", - Description = "Updated", - AllowOverride = false, - IsEnabled = false, - RowVersion = created.RowVersion - }); - - updated.Name.Should().Be("Payment Method Updated"); - updated.AllowOverride.Should().BeFalse(); - updated.IsEnabled.Should().BeFalse(); - } - - [Fact] - public async Task Test_UpdateDictionaryGroup_WithStaleRowVersion_ReturnsConflict() - { - using var database = new DictionarySqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 0, userId: 11); - var tenantProvider = new TestTenantProvider(0); - var cache = new TestDictionaryHybridCache(); - var service = BuildCommandService(context, tenantProvider, cache); - - var created = await service.CreateGroupAsync(new CreateDictionaryGroupRequest - { - Code = "SHIPPING_METHOD", - Name = "Shipping Method", - Scope = DictionaryScope.System, - AllowOverride = true - }); - - var request = new UpdateDictionaryGroupRequest - { - Name = "Shipping Method Updated", - AllowOverride = true, - IsEnabled = true, - RowVersion = new byte[] { 9 } - }; - - Func act = async () => await service.UpdateGroupAsync(created.Id, request); - - var exception = await act.Should().ThrowAsync(); - exception.Which.ErrorCode.Should().Be(ErrorCodes.Conflict); - } - - [Fact] - public async Task Test_DeleteDictionaryGroup_SoftDeletesGroup() - { - using var database = new DictionarySqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 0, userId: 11); - - var group = CreateSystemGroup(201, "user_role"); - var item = CreateSystemItem(210, group.Id, "ADMIN", 10); - context.DictionaryGroups.Add(group); - context.DictionaryItems.Add(item); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var tenantProvider = new TestTenantProvider(0); - var cache = new TestDictionaryHybridCache(); - var service = BuildCommandService(context, tenantProvider, cache); - - var result = await service.DeleteGroupAsync(group.Id); - result.Should().BeTrue(); - - using var verifyContext = database.CreateContext(tenantId: 0); - var deletedGroup = await verifyContext.DictionaryGroups - .IgnoreQueryFilters() - .FirstAsync(x => x.Id == group.Id); - deletedGroup.DeletedAt.Should().NotBeNull(); - - var deletedItem = await verifyContext.DictionaryItems - .IgnoreQueryFilters() - .FirstAsync(x => x.Id == item.Id); - deletedItem.DeletedAt.Should().NotBeNull(); - } - - [Fact] - public async Task Test_EnableOverride_CreatesOverrideConfig() - { - using var database = new DictionarySqliteTestDatabase(); - - using (var systemContext = database.CreateContext(tenantId: 0, userId: 11)) - { - systemContext.DictionaryGroups.Add(CreateSystemGroup(301, "order_status", allowOverride: true)); - await systemContext.SaveChangesAsync(); - } - - using var tenantContext = database.CreateContext(tenantId: 100, userId: 21); - var cache = new TestDictionaryHybridCache(); - var service = BuildOverrideService(tenantContext, cache); - - var result = await service.EnableOverrideAsync(100, "ORDER_STATUS"); - - result.OverrideEnabled.Should().BeTrue(); - result.SystemDictionaryGroupCode.Should().Be("order_status"); - - var stored = await tenantContext.TenantDictionaryOverrides - .IgnoreQueryFilters() - .FirstOrDefaultAsync(x => x.TenantId == 100 && x.SystemDictionaryGroupId == 301); - stored.Should().NotBeNull(); - stored!.OverrideEnabled.Should().BeTrue(); - } - - [Fact] - public async Task Test_DisableOverride_ClearsCustomization() - { - using var database = new DictionarySqliteTestDatabase(); - - using (var systemContext = database.CreateContext(tenantId: 0, userId: 11)) - { - systemContext.DictionaryGroups.Add(CreateSystemGroup(401, "payment_method", allowOverride: true)); - await systemContext.SaveChangesAsync(); - } - - using var tenantContext = database.CreateContext(tenantId: 100, userId: 21); - var cache = new TestDictionaryHybridCache(); - var service = BuildOverrideService(tenantContext, cache); - - await service.EnableOverrideAsync(100, "payment_method"); - var disabled = await service.DisableOverrideAsync(100, "payment_method"); - - disabled.Should().BeTrue(); - - var stored = await tenantContext.TenantDictionaryOverrides - .IgnoreQueryFilters() - .FirstAsync(x => x.TenantId == 100 && x.SystemDictionaryGroupId == 401); - stored.OverrideEnabled.Should().BeFalse(); - } - - [Fact] - public async Task Test_UpdateHiddenItems_FiltersSystemItems() - { - using var database = new DictionarySqliteTestDatabase(); - - using (var systemContext = database.CreateContext(tenantId: 0, userId: 11)) - { - var group = CreateSystemGroup(501, "shipping_method", allowOverride: true); - systemContext.DictionaryGroups.Add(group); - systemContext.DictionaryItems.AddRange( - CreateSystemItem(510, group.Id, "PLATFORM", 10), - CreateSystemItem(511, group.Id, "MERCHANT", 20)); - await systemContext.SaveChangesAsync(); - } - - using var tenantContext = database.CreateContext(tenantId: 200, userId: 22); - var cache = new TestDictionaryHybridCache(); - var overrideService = BuildOverrideService(tenantContext, cache); - var mergeService = BuildMergeService(tenantContext); - - await overrideService.UpdateHiddenItemsAsync(200, "shipping_method", new[] { 511L }); - var merged = await mergeService.MergeItemsAsync(200, 501); - - merged.Should().Contain(item => item.Id == 510); - merged.Should().NotContain(item => item.Id == 511); - } - - [Fact] - public async Task Test_GetMergedDictionary_ReturnsMergedResult() - { - using var database = new DictionarySqliteTestDatabase(); - - using (var systemContext = database.CreateContext(tenantId: 0, userId: 11)) - { - var group = CreateSystemGroup(601, "order_status", allowOverride: true); - systemContext.DictionaryGroups.Add(group); - systemContext.DictionaryItems.AddRange( - CreateSystemItem(610, group.Id, "PENDING", 10), - CreateSystemItem(611, group.Id, "ACCEPTED", 20)); - await systemContext.SaveChangesAsync(); - } - - using (var tenantContext = database.CreateContext(tenantId: 300, userId: 33)) - { - var tenantGroup = CreateTenantGroup(701, 300, "order_status"); - tenantContext.DictionaryGroups.Add(tenantGroup); - tenantContext.DictionaryItems.Add(CreateTenantItem(720, tenantGroup.Id, "CUSTOM", 15)); - await tenantContext.SaveChangesAsync(); - } - - using var queryContext = database.CreateContext(tenantId: 300, userId: 33); - var cache = new TestDictionaryHybridCache(); - var overrideService = BuildOverrideService(queryContext, cache); - await overrideService.EnableOverrideAsync(300, "order_status"); - await overrideService.UpdateHiddenItemsAsync(300, "order_status", new[] { 611L }); - await overrideService.UpdateCustomSortOrderAsync(300, "order_status", new Dictionary - { - [720L] = 1, - [610L] = 2 - }); - - var queryService = BuildQueryService(queryContext, new TestTenantProvider(300), cache); - var merged = await queryService.GetMergedDictionaryAsync("order_status"); - - merged.Should().Contain(item => item.Id == 610); - merged.Should().Contain(item => item.Id == 720 && item.Source == "tenant"); - merged.Should().NotContain(item => item.Id == 611); - merged.First().Id.Should().Be(720); - } - - [Fact] - public async Task Test_ExportCsv_GeneratesValidFile() - { - using var database = new DictionarySqliteTestDatabase(); - - using (var systemContext = database.CreateContext(tenantId: 0, userId: 11)) - { - var group = CreateSystemGroup(801, "payment_method", allowOverride: true); - systemContext.DictionaryGroups.Add(group); - systemContext.DictionaryItems.Add(CreateSystemItem(810, group.Id, "ALIPAY", 10)); - await systemContext.SaveChangesAsync(); - } - - using var exportContext = database.CreateContext(tenantId: 0, userId: 11); - var cache = new TestDictionaryHybridCache(); - var service = BuildImportExportService(exportContext, new TestTenantProvider(0), new TestCurrentUserAccessor(11), cache); - - await using var stream = new MemoryStream(); - await service.ExportToCsvAsync(801, stream); - var csv = Encoding.UTF8.GetString(stream.ToArray()); - - csv.Should().Contain("code,key,value,sortOrder,isEnabled,description,source"); - csv.Should().Contain("payment_method"); - csv.Should().Contain("ALIPAY"); - } - - [Fact] - public async Task Test_ImportCsv_WithSkipMode_SkipsDuplicates() - { - using var database = new DictionarySqliteTestDatabase(); - - using (var systemContext = database.CreateContext(tenantId: 0, userId: 11)) - { - var group = CreateSystemGroup(901, "order_status", allowOverride: true); - systemContext.DictionaryGroups.Add(group); - systemContext.DictionaryItems.Add(CreateSystemItem(910, group.Id, "PENDING", 10)); - await systemContext.SaveChangesAsync(); - } - - using var importContext = database.CreateContext(tenantId: 0, userId: 11); - var cache = new TestDictionaryHybridCache(); - var service = BuildImportExportService(importContext, new TestTenantProvider(0), new TestCurrentUserAccessor(11), cache); - - var csv = BuildCsv( - new[] { "code", "key", "value", "sortOrder", "isEnabled", "description", "source" }, - new[] - { - new[] { "order_status", "PENDING", BuildValueJson("待接单", "Pending"), "10", "true", "重复项", "system" }, - new[] { "order_status", "COMPLETED", BuildValueJson("已完成", "Completed"), "20", "true", "新增项", "system" } - }); - - var result = await service.ImportFromCsvAsync(new DictionaryImportRequest - { - GroupId = 901, - FileName = "import.csv", - FileSize = Encoding.UTF8.GetByteCount(csv), - ConflictMode = ConflictResolutionMode.Skip, - FileStream = new MemoryStream(Encoding.UTF8.GetBytes(csv)) - }); - - result.SkipCount.Should().Be(1); - result.SuccessCount.Should().Be(1); - } - - [Fact] - public async Task Test_ImportCsv_WithOverwriteMode_UpdatesExisting() - { - using var database = new DictionarySqliteTestDatabase(); - - using (var systemContext = database.CreateContext(tenantId: 0, userId: 11)) - { - var group = CreateSystemGroup(1001, "payment_method", allowOverride: true); - systemContext.DictionaryGroups.Add(group); - systemContext.DictionaryItems.Add(CreateSystemItem(1010, group.Id, "ALIPAY", 10)); - await systemContext.SaveChangesAsync(); - } - - using var importContext = database.CreateContext(tenantId: 0, userId: 11); - var cache = new TestDictionaryHybridCache(); - var service = BuildImportExportService(importContext, new TestTenantProvider(0), new TestCurrentUserAccessor(11), cache); - - var csv = BuildCsv( - new[] { "code", "key", "value", "sortOrder", "isEnabled", "description", "source" }, - new[] - { - new[] { "payment_method", "ALIPAY", BuildValueJson("支付宝新版", "Alipay New"), "15", "true", "覆盖", "system" } - }); - - await service.ImportFromCsvAsync(new DictionaryImportRequest - { - GroupId = 1001, - FileName = "import.csv", - FileSize = Encoding.UTF8.GetByteCount(csv), - ConflictMode = ConflictResolutionMode.Overwrite, - FileStream = new MemoryStream(Encoding.UTF8.GetBytes(csv)) - }); - - var updated = await importContext.DictionaryItems - .IgnoreQueryFilters() - .FirstAsync(item => item.GroupId == 1001 && item.Key == "ALIPAY"); - - updated.Value.Should().Contain("支付宝新版"); - } - - [Fact] - public async Task Test_ImportCsv_WithInvalidData_ReturnsErrors() - { - using var database = new DictionarySqliteTestDatabase(); - - using (var systemContext = database.CreateContext(tenantId: 0, userId: 11)) - { - systemContext.DictionaryGroups.Add(CreateSystemGroup(1101, "user_role", allowOverride: false)); - await systemContext.SaveChangesAsync(); - } - - using var importContext = database.CreateContext(tenantId: 0, userId: 11); - var cache = new TestDictionaryHybridCache(); - var service = BuildImportExportService(importContext, new TestTenantProvider(0), new TestCurrentUserAccessor(11), cache); - - var csv = BuildCsv( - new[] { "code", "key", "value", "sortOrder", "isEnabled", "description", "source" }, - new[] - { - new[] { "user_role", "ADMIN", "invalid-json", "10", "true", "bad", "system" } - }); - - var result = await service.ImportFromCsvAsync(new DictionaryImportRequest - { - GroupId = 1101, - FileName = "import.csv", - FileSize = Encoding.UTF8.GetByteCount(csv), - ConflictMode = ConflictResolutionMode.Skip, - FileStream = new MemoryStream(Encoding.UTF8.GetBytes(csv)) - }); - - result.ErrorCount.Should().Be(1); - result.SuccessCount.Should().Be(0); - } - - private static DictionaryCommandService BuildCommandService( - TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext context, - TestTenantProvider tenantProvider, - TestDictionaryHybridCache cache) - { - return new DictionaryCommandService( - new DictionaryGroupRepository(context), - new DictionaryItemRepository(context), - cache, - tenantProvider, - new HttpContextAccessor { HttpContext = new DefaultHttpContext() }, - NullLogger.Instance); - } - - private static DictionaryQueryService BuildQueryService( - TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext context, - TestTenantProvider tenantProvider, - TestDictionaryHybridCache cache) - { - var groupRepository = new DictionaryGroupRepository(context); - var itemRepository = new DictionaryItemRepository(context); - var overrideRepository = new TenantDictionaryOverrideRepository(context); - var mergeService = new DictionaryMergeService(groupRepository, itemRepository, overrideRepository); - - return new DictionaryQueryService(groupRepository, itemRepository, mergeService, cache, tenantProvider); - } - - private static DictionaryOverrideService BuildOverrideService( - TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext context, - TestDictionaryHybridCache cache) - { - return new DictionaryOverrideService( - new DictionaryGroupRepository(context), - new DictionaryItemRepository(context), - new TenantDictionaryOverrideRepository(context), - cache); - } - - private static DictionaryMergeService BuildMergeService( - TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext context) - { - var groupRepository = new DictionaryGroupRepository(context); - var itemRepository = new DictionaryItemRepository(context); - var overrideRepository = new TenantDictionaryOverrideRepository(context); - return new DictionaryMergeService(groupRepository, itemRepository, overrideRepository); - } - - private static DictionaryImportExportService BuildImportExportService( - TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext context, - TestTenantProvider tenantProvider, - TestCurrentUserAccessor currentUser, - TestDictionaryHybridCache cache) - { - return new DictionaryImportExportService( - new CsvDictionaryParser(), - new JsonDictionaryParser(), - new DictionaryGroupRepository(context), - new DictionaryItemRepository(context), - new DictionaryImportLogRepository(context), - cache, - tenantProvider, - currentUser, - new HttpContextAccessor { HttpContext = new DefaultHttpContext() }, - NullLogger.Instance); - } - - private static DictionaryGroup CreateSystemGroup(long id, string code, bool allowOverride = true) - { - return new DictionaryGroup - { - Id = id, - TenantId = 0, - Code = new DictionaryCode(code), - Name = code, - Scope = DictionaryScope.System, - AllowOverride = allowOverride, - Description = "Test group", - IsEnabled = true, - RowVersion = new byte[] { 1 } - }; - } - - private static DictionaryGroup CreateTenantGroup(long id, long tenantId, string code) - { - return new DictionaryGroup - { - Id = id, - TenantId = tenantId, - Code = new DictionaryCode(code), - Name = code, - Scope = DictionaryScope.Business, - AllowOverride = false, - Description = "Tenant group", - IsEnabled = true, - RowVersion = new byte[] { 1 } - }; - } - - private static DictionaryItem CreateSystemItem(long id, long groupId, string key, int sortOrder) - { - return new DictionaryItem - { - Id = id, - TenantId = 0, - GroupId = groupId, - Key = key, - Value = BuildValueJson("测试", "Test"), - IsDefault = false, - IsEnabled = true, - SortOrder = sortOrder, - Description = "System item", - RowVersion = new byte[] { 1 } - }; - } - - private static DictionaryItem CreateTenantItem(long id, long groupId, string key, int sortOrder) - { - return new DictionaryItem - { - Id = id, - TenantId = 300, - GroupId = groupId, - Key = key, - Value = BuildValueJson("租户值", "Tenant Value"), - IsDefault = false, - IsEnabled = true, - SortOrder = sortOrder, - Description = "Tenant item", - RowVersion = new byte[] { 1 } - }; - } - - private static string BuildValueJson(string zh, string en) - { - var value = new I18nValue(new Dictionary - { - ["zh-CN"] = zh, - ["en"] = en - }); - return value.ToJson(); - } - - private static string BuildCsv(string[] headers, IEnumerable rows) - { - var builder = new StringBuilder(); - builder.AppendLine(string.Join(",", headers)); - foreach (var row in rows) - { - builder.AppendLine(string.Join(",", row.Select(EscapeCsvField))); - } - - return builder.ToString(); - } - - private static string EscapeCsvField(string value) - { - if (value.Contains('"', StringComparison.Ordinal)) - { - value = value.Replace("\"", "\"\""); - } - - if (value.Contains(',', StringComparison.Ordinal) || - value.Contains('\n', StringComparison.Ordinal) || - value.Contains('\r', StringComparison.Ordinal) || - value.Contains('"', StringComparison.Ordinal)) - { - return $"\"{value}\""; - } - - return value; - } -} diff --git a/tests/TakeoutSaaS.Integration.Tests/App/Tenants/AnnouncementRegressionTests.cs b/tests/TakeoutSaaS.Integration.Tests/App/Tenants/AnnouncementRegressionTests.cs deleted file mode 100644 index f370a4b..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/App/Tenants/AnnouncementRegressionTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -using FluentAssertions; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Handlers; -using TakeoutSaaS.Domain.Tenants.Entities; -using TakeoutSaaS.Domain.Tenants.Enums; -using TakeoutSaaS.Infrastructure.App.Repositories; -using TakeoutSaaS.Integration.Tests.Fixtures; - -namespace TakeoutSaaS.Integration.Tests.App.Tenants; - -public sealed class AnnouncementRegressionTests -{ - [Fact] - public async Task GivenPublishedAnnouncement_WhenSearchByIsActive_ThenReturns() - { - // Arrange - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 600, userId: 12); - - var legacy = CreateAnnouncement(tenantId: 600, id: 9100); - legacy.Status = AnnouncementStatus.Published; - - context.TenantAnnouncements.Add(legacy); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var repository = new EfTenantAnnouncementRepository(context); - - // Act - var results = await repository.SearchAsync( - tenantId: 600, - keyword: null, - status: null, - type: null, - isActive: true, - effectiveFrom: null, - effectiveTo: null, - effectiveAt: null, - cancellationToken: CancellationToken.None); - - // Assert - results.Should().ContainSingle(x => x.Id == legacy.Id); - } - - [Fact] - public async Task GivenDraftAnnouncement_WhenUpdate_ThenUpdatesFieldsAndKeepsInactive() - { - // Arrange - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 700, userId: 12); - - var announcement = CreateAnnouncement(tenantId: 700, id: 9101); - announcement.Status = AnnouncementStatus.Draft; - context.TenantAnnouncements.Add(announcement); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var repository = new EfTenantAnnouncementRepository(context); - var handler = new UpdateTenantAnnouncementCommandHandler(repository); - - var command = new UpdateTenantAnnouncementCommand - { - TenantId = 700, - AnnouncementId = announcement.Id, - Title = "更新后的标题", - Content = "更新后的内容", - TargetType = "ALL_TENANTS", - RowVersion = announcement.RowVersion - }; - - // Act - var result = await handler.Handle(command, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result!.Title.Should().Be("更新后的标题"); - result.Content.Should().Be("更新后的内容"); - result.IsActive.Should().BeFalse(); - } - - private static TenantAnnouncement CreateAnnouncement(long tenantId, long id) - => new() - { - Id = id, - TenantId = tenantId, - Title = "旧公告", - Content = "内容", - AnnouncementType = TenantAnnouncementType.System, - Priority = 1, - EffectiveFrom = DateTime.UtcNow.AddMinutes(-5), - EffectiveTo = null, - PublisherScope = PublisherScope.Tenant, - Status = AnnouncementStatus.Draft, - TargetType = "ALL_TENANTS", - TargetParameters = null, - RowVersion = new byte[] { 1 } - }; -} diff --git a/tests/TakeoutSaaS.Integration.Tests/App/Tenants/AnnouncementWorkflowTests.cs b/tests/TakeoutSaaS.Integration.Tests/App/Tenants/AnnouncementWorkflowTests.cs deleted file mode 100644 index 706c9c1..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/App/Tenants/AnnouncementWorkflowTests.cs +++ /dev/null @@ -1,218 +0,0 @@ -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Moq; -using TakeoutSaaS.Application.App.Tenants.Commands; -using TakeoutSaaS.Application.App.Tenants.Handlers; -using TakeoutSaaS.Application.Messaging.Abstractions; -using TakeoutSaaS.Domain.Tenants.Entities; -using TakeoutSaaS.Domain.Tenants.Enums; -using TakeoutSaaS.Infrastructure.App.Repositories; -using TakeoutSaaS.Integration.Tests.Fixtures; -using TakeoutSaaS.Shared.Abstractions.Constants; -using TakeoutSaaS.Shared.Abstractions.Exceptions; - -namespace TakeoutSaaS.Integration.Tests.App.Tenants; - -/// -/// 公告工作流集成测试。 -/// -public sealed class AnnouncementWorkflowTests -{ - [Fact] - public async Task GivenDraftAnnouncement_WhenPublish_ThenStatusIsPublishedAndActive() - { - // 1. 准备 - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 100, userId: 11); - - var announcement = CreateDraftAnnouncement(tenantId: 100, id: 9001); - context.TenantAnnouncements.Add(announcement); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var repository = new EfTenantAnnouncementRepository(context); - var eventPublisher = new Mock(); - var handler = new PublishAnnouncementCommandHandler(repository, eventPublisher.Object); - - // 2. (空行后) 执行 - var result = await handler.Handle(new PublishAnnouncementCommand - { - TenantId = 100, - AnnouncementId = announcement.Id, - RowVersion = announcement.RowVersion - }, CancellationToken.None); - - // 3. (空行后) 断言 - result.Should().NotBeNull(); - result!.Status.Should().Be(AnnouncementStatus.Published); - result.IsActive.Should().BeTrue(); - - using var verifyContext = database.CreateContext(tenantId: 100); - var persisted = await verifyContext.TenantAnnouncements.FirstAsync(x => x.Id == announcement.Id); - persisted.Status.Should().Be(AnnouncementStatus.Published); - persisted.PublishedAt.Should().NotBeNull(); - } - - [Fact] - public async Task GivenPublishedAnnouncement_WhenRevoke_ThenStatusIsRevokedAndInactive() - { - // 1. 准备 - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 200, userId: 11); - - var announcement = CreateDraftAnnouncement(tenantId: 200, id: 9002); - announcement.Status = AnnouncementStatus.Published; - context.TenantAnnouncements.Add(announcement); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var repository = new EfTenantAnnouncementRepository(context); - var eventPublisher = new Mock(); - var handler = new RevokeAnnouncementCommandHandler(repository, eventPublisher.Object); - - // 2. (空行后) 执行 - var result = await handler.Handle(new RevokeAnnouncementCommand - { - TenantId = 200, - AnnouncementId = announcement.Id, - RowVersion = announcement.RowVersion - }, CancellationToken.None); - - // 3. (空行后) 断言 - result.Should().NotBeNull(); - result!.Status.Should().Be(AnnouncementStatus.Revoked); - result.IsActive.Should().BeFalse(); - - using var verifyContext = database.CreateContext(tenantId: 200); - var persisted = await verifyContext.TenantAnnouncements.FirstAsync(x => x.Id == announcement.Id); - persisted.Status.Should().Be(AnnouncementStatus.Revoked); - persisted.RevokedAt.Should().NotBeNull(); - } - - [Fact] - public async Task GivenRevokedAnnouncement_WhenPublish_ThenRepublishAndClearRevokedAt() - { - // 1. 准备 - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 300, userId: 11); - - var announcement = CreateDraftAnnouncement(tenantId: 300, id: 9003); - announcement.Status = AnnouncementStatus.Revoked; - announcement.RevokedAt = DateTime.UtcNow.AddMinutes(-5); - context.TenantAnnouncements.Add(announcement); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var repository = new EfTenantAnnouncementRepository(context); - var eventPublisher = new Mock(); - var handler = new PublishAnnouncementCommandHandler(repository, eventPublisher.Object); - - // 2. (空行后) 执行 - var result = await handler.Handle(new PublishAnnouncementCommand - { - TenantId = 300, - AnnouncementId = announcement.Id, - RowVersion = announcement.RowVersion - }, CancellationToken.None); - - // 3. (空行后) 断言 - result.Should().NotBeNull(); - result!.Status.Should().Be(AnnouncementStatus.Published); - result.IsActive.Should().BeTrue(); - - using var verifyContext = database.CreateContext(tenantId: 300); - var persisted = await verifyContext.TenantAnnouncements.FirstAsync(x => x.Id == announcement.Id); - persisted.Status.Should().Be(AnnouncementStatus.Published); - persisted.RevokedAt.Should().BeNull(); - } - - [Fact] - public async Task GivenPublishedAnnouncement_WhenUpdate_ThenThrowsBusinessException() - { - // Arrange - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 400, userId: 11); - - var announcement = CreateDraftAnnouncement(tenantId: 400, id: 9004); - announcement.Status = AnnouncementStatus.Published; - context.TenantAnnouncements.Add(announcement); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var repository = new EfTenantAnnouncementRepository(context); - var handler = new UpdateTenantAnnouncementCommandHandler(repository); - - var command = new UpdateTenantAnnouncementCommand - { - TenantId = 400, - AnnouncementId = announcement.Id, - Title = "更新标题", - Content = "更新内容", - TargetType = "ALL_TENANTS", - RowVersion = announcement.RowVersion - }; - - // Act - Func act = async () => await handler.Handle(command, CancellationToken.None); - - // Assert - var exception = await act.Should().ThrowAsync(); - exception.Which.ErrorCode.Should().Be(ErrorCodes.Conflict); - } - - [Fact] - public async Task GivenStaleRowVersion_WhenUpdate_ThenReturnsConflict() - { - // Arrange - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 500, userId: 11); - - var announcement = CreateDraftAnnouncement(tenantId: 500, id: 9005); - announcement.RowVersion = new byte[] { 1 }; - context.TenantAnnouncements.Add(announcement); - await context.SaveChangesAsync(); - - await context.Database.ExecuteSqlRawAsync( - "UPDATE tenant_announcements SET \"RowVersion\" = {0} WHERE \"Id\" = {1}", - new byte[] { 9 }, announcement.Id); - context.ChangeTracker.Clear(); - - var repository = new EfTenantAnnouncementRepository(context); - var handler = new UpdateTenantAnnouncementCommandHandler(repository); - - var command = new UpdateTenantAnnouncementCommand - { - TenantId = 500, - AnnouncementId = announcement.Id, - Title = "并发更新", - Content = "内容", - TargetType = "ALL_TENANTS", - RowVersion = new byte[] { 1 } - }; - - // Act - Func act = async () => await handler.Handle(command, CancellationToken.None); - - // Assert - var exception = await act.Should().ThrowAsync(); - exception.Which.ErrorCode.Should().Be(ErrorCodes.Conflict); - } - - private static TenantAnnouncement CreateDraftAnnouncement(long tenantId, long id) - => new() - { - Id = id, - TenantId = tenantId, - Title = "公告", - Content = "内容", - AnnouncementType = TenantAnnouncementType.System, - Priority = 1, - EffectiveFrom = DateTime.UtcNow.AddMinutes(-10), - EffectiveTo = DateTime.UtcNow.AddMinutes(30), - PublisherScope = PublisherScope.Tenant, - Status = AnnouncementStatus.Draft, - TargetType = "ALL_TENANTS", - TargetParameters = null, - RowVersion = new byte[] { 1 } - }; -} diff --git a/tests/TakeoutSaaS.Integration.Tests/App/Tenants/TenantAnnouncementRepositoryScopeTests.cs b/tests/TakeoutSaaS.Integration.Tests/App/Tenants/TenantAnnouncementRepositoryScopeTests.cs deleted file mode 100644 index cb7697f..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/App/Tenants/TenantAnnouncementRepositoryScopeTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using FluentAssertions; -using TakeoutSaaS.Domain.Tenants.Entities; -using TakeoutSaaS.Domain.Tenants.Enums; -using TakeoutSaaS.Infrastructure.App.Repositories; -using TakeoutSaaS.Integration.Tests.Fixtures; - -namespace TakeoutSaaS.Integration.Tests.App.Tenants; - -public sealed class TenantAnnouncementRepositoryScopeTests -{ - [Fact] - public async Task GivenTenantAndPlatformAnnouncements_WhenSearchAsync_ThenReturnsBoth() - { - // Arrange - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 800); - - var tenantAnnouncement = CreateAnnouncement(tenantId: 800, id: 9200); - var platformAnnouncement = CreateAnnouncement(tenantId: 0, id: 9201); - - context.TenantAnnouncements.AddRange(tenantAnnouncement, platformAnnouncement); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - - var repository = new EfTenantAnnouncementRepository(context); - - // Act - var results = await repository.SearchAsync( - tenantId: 800, - keyword: null, - status: null, - type: null, - isActive: null, - effectiveFrom: null, - effectiveTo: null, - effectiveAt: null, - cancellationToken: CancellationToken.None); - - // Assert - results.Select(x => x.Id).Should().Contain(new[] { tenantAnnouncement.Id, platformAnnouncement.Id }); - } - - private static TenantAnnouncement CreateAnnouncement(long tenantId, long id) - => new() - { - Id = id, - TenantId = tenantId, - Title = "公告", - Content = "内容", - AnnouncementType = TenantAnnouncementType.System, - Priority = 1, - EffectiveFrom = DateTime.UtcNow.AddMinutes(-5), - PublisherScope = PublisherScope.Tenant, - Status = AnnouncementStatus.Draft, - TargetType = "ALL_TENANTS", - RowVersion = new byte[] { 1 } - }; -} diff --git a/tests/TakeoutSaaS.Integration.Tests/Authorization/PermissionAuthorizationHandlerTests.cs b/tests/TakeoutSaaS.Integration.Tests/Authorization/PermissionAuthorizationHandlerTests.cs deleted file mode 100644 index d530f34..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/Authorization/PermissionAuthorizationHandlerTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Security.Claims; -using FluentAssertions; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Authorization.Policy; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using TakeoutSaaS.Module.Authorization.Policies; - -namespace TakeoutSaaS.Integration.Tests.Authorization; - -public sealed class PermissionAuthorizationHandlerTests -{ - [Theory] - [InlineData("platform-announcement:create")] - [InlineData("platform-announcement:publish")] - [InlineData("platform-announcement:revoke")] - [InlineData("tenant-announcement:publish")] - [InlineData("tenant-announcement:revoke")] - public async Task GivenUserWithPermission_WhenAuthorize_ThenSucceeds(string permission) - { - // Arrange - var requirement = new PermissionRequirement(new[] { permission }); - var identity = new ClaimsIdentity(new[] - { - new Claim(PermissionAuthorizationHandler.PermissionClaimType, permission) - }, "Test"); - var user = new ClaimsPrincipal(identity); - var context = new AuthorizationHandlerContext(new[] { requirement }, user, null); - var handler = new PermissionAuthorizationHandler(); - - // Act - await handler.HandleAsync(context); - - // Assert - context.HasSucceeded.Should().BeTrue(); - } - - [Fact] - public async Task GivenUserWithoutPermission_WhenAuthorize_ThenFails() - { - // Arrange - var requirement = new PermissionRequirement(new[] { "platform-announcement:create" }); - var user = new ClaimsPrincipal(new ClaimsIdentity(authenticationType: "Test")); - var context = new AuthorizationHandlerContext(new[] { requirement }, user, null); - var handler = new PermissionAuthorizationHandler(); - - // Act - await handler.HandleAsync(context); - - // Assert - context.HasSucceeded.Should().BeFalse(); - } - - [Fact] - public async Task GivenAuthenticatedUserWithoutPermission_WhenEvaluatingPolicy_ThenForbidden() - { - // Arrange - var services = new ServiceCollection(); - services.AddAuthorization(); - services.AddSingleton(); - services.AddSingleton(); - var provider = services.BuildServiceProvider(); - - var policyProvider = provider.GetRequiredService(); - var policy = await policyProvider.GetPolicyAsync( - PermissionAuthorizationPolicyProvider.BuildPolicyName(new[] { "platform-announcement:create" })); - - var user = new ClaimsPrincipal(new ClaimsIdentity(authenticationType: "Test")); - var authenticateResult = AuthenticateResult.Success(new AuthenticationTicket(user, "Test")); - var httpContext = new DefaultHttpContext { RequestServices = provider, User = user }; - var evaluator = provider.GetRequiredService(); - - // Act - var result = await evaluator.AuthorizeAsync(policy!, authenticateResult, httpContext, resource: null); - - // Assert - result.Forbidden.Should().BeTrue(); - } -} diff --git a/tests/TakeoutSaaS.Integration.Tests/Fixtures/DictionarySqliteTestDatabase.cs b/tests/TakeoutSaaS.Integration.Tests/Fixtures/DictionarySqliteTestDatabase.cs deleted file mode 100644 index 176c414..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/Fixtures/DictionarySqliteTestDatabase.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using TakeoutSaaS.Infrastructure.Dictionary.Persistence; - -namespace TakeoutSaaS.Integration.Tests.Fixtures; - -/// -/// 集成测试用 SQLite 内存数据库(字典库)。 -/// -public sealed class DictionarySqliteTestDatabase : IDisposable -{ - private readonly SqliteConnection _connection; - private readonly TestIdGenerator _idGenerator = new(); - private bool _initialized; - - public DictionarySqliteTestDatabase() - { - _connection = new SqliteConnection("Filename=:memory:"); - _connection.Open(); - Options = new DbContextOptionsBuilder() - .UseSqlite(_connection) - .EnableSensitiveDataLogging() - .Options; - } - - public DbContextOptions Options { get; } - - public DictionaryDbContext CreateContext(long tenantId, long userId = 0) - { - EnsureCreated(); - // 1. AdminApi 不使用租户上下文;tenantId 参数仅用于兼容测试调用方签名 - _ = tenantId; - - // 2. (空行后) 按需注入当前用户与 ID 生成器 - return new DictionaryDbContext( - Options, - userId == 0 ? null : new TestCurrentUserAccessor(userId), - _idGenerator); - } - - public void EnsureCreated() - { - if (_initialized) - { - return; - } - - // 1. 创建并初始化数据库结构 - using var context = new DictionaryDbContext(Options, idGenerator: _idGenerator); - context.Database.EnsureCreated(); - _initialized = true; - } - - public void Dispose() - { - _connection.Dispose(); - } -} diff --git a/tests/TakeoutSaaS.Integration.Tests/Fixtures/SqliteTestDatabase.cs b/tests/TakeoutSaaS.Integration.Tests/Fixtures/SqliteTestDatabase.cs deleted file mode 100644 index a66a5da..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/Fixtures/SqliteTestDatabase.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using TakeoutSaaS.Infrastructure.App.Persistence; - -namespace TakeoutSaaS.Integration.Tests.Fixtures; - -/// -/// 集成测试用 SQLite 内存数据库(业务主库)。 -/// -public sealed class SqliteTestDatabase : IDisposable -{ - private readonly SqliteConnection _connection; - private readonly TestIdGenerator _idGenerator = new(); - private bool _initialized; - - public SqliteTestDatabase() - { - _connection = new SqliteConnection("Filename=:memory:"); - _connection.Open(); - Options = new DbContextOptionsBuilder() - .UseSqlite(_connection) - .EnableSensitiveDataLogging() - .Options; - } - - public DbContextOptions Options { get; } - - public TakeoutAdminDbContext CreateContext(long tenantId, long userId = 0) - { - EnsureCreated(); - // 1. AdminApi 不使用租户上下文;tenantId 参数仅用于兼容测试调用方签名 - _ = tenantId; - - // 2. (空行后) 按需注入当前用户与 ID 生成器 - return new TakeoutAdminDbContext( - Options, - userId == 0 ? null : new TestCurrentUserAccessor(userId), - _idGenerator); - } - - public void EnsureCreated() - { - if (_initialized) - { - return; - } - - // 1. 创建并初始化数据库结构 - using var context = new TakeoutAdminDbContext(Options, idGenerator: _idGenerator); - context.Database.EnsureCreated(); - _initialized = true; - } - - public void Dispose() - { - _connection.Dispose(); - } -} diff --git a/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestCurrentUserAccessor.cs b/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestCurrentUserAccessor.cs deleted file mode 100644 index b7ab4d3..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestCurrentUserAccessor.cs +++ /dev/null @@ -1,10 +0,0 @@ -using TakeoutSaaS.Shared.Abstractions.Security; - -namespace TakeoutSaaS.Integration.Tests.Fixtures; - -public sealed class TestCurrentUserAccessor(long userId) : ICurrentUserAccessor -{ - public long UserId { get; set; } = userId; - - public bool IsAuthenticated => UserId != 0; -} diff --git a/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestDictionaryHybridCache.cs b/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestDictionaryHybridCache.cs deleted file mode 100644 index 9bd13c4..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestDictionaryHybridCache.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Concurrent; -using TakeoutSaaS.Application.Dictionary.Abstractions; -using TakeoutSaaS.Domain.Dictionary.Enums; - -namespace TakeoutSaaS.Integration.Tests.Fixtures; - -public sealed class TestDictionaryHybridCache : IDictionaryHybridCache -{ - private readonly ConcurrentDictionary _cache = new(StringComparer.OrdinalIgnoreCase); - - public async Task GetOrCreateAsync( - string key, - TimeSpan ttl, - Func> factory, - CancellationToken cancellationToken = default) - { - if (_cache.TryGetValue(key, out var cached) && cached is T typed) - { - return typed; - } - - var value = await factory(cancellationToken); - if (value is not null) - { - _cache[key] = value; - } - - return value; - } - - public Task InvalidateAsync( - string prefix, - CacheInvalidationOperation operation = CacheInvalidationOperation.Update, - CancellationToken cancellationToken = default) - { - foreach (var key in _cache.Keys) - { - if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - _cache.TryRemove(key, out _); - } - } - - return Task.CompletedTask; - } -} diff --git a/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestIdGenerator.cs b/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestIdGenerator.cs deleted file mode 100644 index 5c61f3a..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestIdGenerator.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Threading; -using TakeoutSaaS.Shared.Abstractions.Ids; - -namespace TakeoutSaaS.Integration.Tests.Fixtures; - -/// -/// 集成测试用雪花 ID 生成器(递增模拟)。 -/// -public sealed class TestIdGenerator : IIdGenerator -{ - private long _current; - - /// - /// 生成下一个 ID。 - /// - /// 递增的 long ID。 - public long NextId() - => Interlocked.Increment(ref _current); -} - diff --git a/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestTenantProvider.cs b/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestTenantProvider.cs deleted file mode 100644 index 89c0e4d..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/Fixtures/TestTenantProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -using TakeoutSaaS.Shared.Abstractions.Tenancy; - -namespace TakeoutSaaS.Integration.Tests.Fixtures; - -public sealed class TestTenantProvider(long tenantId) : ITenantProvider -{ - public long TenantId { get; set; } = tenantId; - - public long GetCurrentTenantId() => TenantId; -} diff --git a/tests/TakeoutSaaS.Integration.Tests/Performance/AnnouncementQueryPerformanceTests.cs b/tests/TakeoutSaaS.Integration.Tests/Performance/AnnouncementQueryPerformanceTests.cs deleted file mode 100644 index 290991a..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/Performance/AnnouncementQueryPerformanceTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Diagnostics; -using FluentAssertions; -using TakeoutSaaS.Application.App.Tenants.Handlers; -using TakeoutSaaS.Application.App.Tenants.Queries; -using TakeoutSaaS.Domain.Tenants.Entities; -using TakeoutSaaS.Domain.Tenants.Enums; -using TakeoutSaaS.Infrastructure.App.Repositories; -using TakeoutSaaS.Integration.Tests.Fixtures; - -namespace TakeoutSaaS.Integration.Tests.Performance; - -/// -/// 公告查询性能相关测试。 -/// -public sealed class AnnouncementQueryPerformanceTests -{ - [Fact] - public async Task GivenLargeDataset_WhenQueryingAnnouncements_ThenCompletesWithinThreshold() - { - // 1. 准备 - using var database = new SqliteTestDatabase(); - using var context = database.CreateContext(tenantId: 900); - var announcements = new List(); - for (var i = 0; i < 1000; i++) - { - var tenantId = i % 2 == 0 ? 900 : 0; - var targetType = i % 10 == 0 ? "ROLES" : "ALL_TENANTS"; - var targetParameters = i % 10 == 0 ? "{\"roles\":[\"ops\"]}" : null; - - announcements.Add(new TenantAnnouncement - { - Id = 10000 + i, - TenantId = tenantId, - Title = "公告", - Content = "内容", - AnnouncementType = TenantAnnouncementType.System, - Priority = i % 5, - EffectiveFrom = DateTime.UtcNow.AddDays(-1), - PublisherScope = tenantId == 0 ? PublisherScope.Platform : PublisherScope.Tenant, - Status = AnnouncementStatus.Published, - TargetType = targetType, - TargetParameters = targetParameters, - RowVersion = new byte[] { 1 } - }); - } - - context.TenantAnnouncements.AddRange(announcements); - await context.SaveChangesAsync(); - context.ChangeTracker.Clear(); - var announcementRepository = new EfTenantAnnouncementRepository(context); - var handler = new GetTenantsAnnouncementsQueryHandler(announcementRepository); - var query = new GetTenantsAnnouncementsQuery - { - TenantId = 900, - Page = 1, - PageSize = 50 - }; - - // 2. (空行后) 执行 - var stopwatch = Stopwatch.StartNew(); - var result = await handler.Handle(query, CancellationToken.None); - stopwatch.Stop(); - - // 3. (空行后) 断言:TotalCount 为估算口径(page * size * 3)过滤后的数量 - result.Items.Count.Should().Be(50); // 请求的页大小 - result.TotalCount.Should().BeLessThanOrEqualTo(150); // 最多是 estimatedLimit - result.TotalCount.Should().BeGreaterThan(0); // 至少有一些结果 - stopwatch.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(5)); - } -} diff --git a/tests/TakeoutSaaS.Integration.Tests/TakeoutSaaS.Integration.Tests.csproj b/tests/TakeoutSaaS.Integration.Tests/TakeoutSaaS.Integration.Tests.csproj deleted file mode 100644 index 24ff938..0000000 --- a/tests/TakeoutSaaS.Integration.Tests/TakeoutSaaS.Integration.Tests.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net10.0 - enable - enable - false - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - -