refactor: 移除平台公告与跨租户目标
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||||
|
using TakeoutSaaS.Application.App.Tenants.Targeting;
|
||||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||||
@@ -57,6 +58,11 @@ public sealed class CreateTenantAnnouncementCommandHandler(
|
|||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.ValidationFailed, "目标受众类型不能为空");
|
throw new BusinessException(ErrorCodes.ValidationFailed, "目标受众类型不能为空");
|
||||||
}
|
}
|
||||||
|
// 4.1 (空行后) 校验目标受众类型:租户端禁止跨租户目标类型
|
||||||
|
if (!TenantAnnouncementTargetTypePolicy.IsAllowed(request.TargetType))
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.ValidationFailed, "租户端不支持该目标受众类型");
|
||||||
|
}
|
||||||
|
|
||||||
if (request.EffectiveTo.HasValue && request.EffectiveFrom >= request.EffectiveTo.Value)
|
if (request.EffectiveTo.HasValue && request.EffectiveFrom >= request.EffectiveTo.Value)
|
||||||
{
|
{
|
||||||
@@ -66,6 +72,8 @@ public sealed class CreateTenantAnnouncementCommandHandler(
|
|||||||
// 5. (空行后) 构建公告实体
|
// 5. (空行后) 构建公告实体
|
||||||
var tenantId = currentTenantId;
|
var tenantId = currentTenantId;
|
||||||
var publisherUserId = currentUserAccessor.UserId == 0 ? (long?)null : currentUserAccessor.UserId;
|
var publisherUserId = currentUserAccessor.UserId == 0 ? (long?)null : currentUserAccessor.UserId;
|
||||||
|
// 5.1 (空行后) 规范化目标类型,避免写入脏数据
|
||||||
|
var normalizedTargetType = TenantAnnouncementTargetTypePolicy.Normalize(request.TargetType);
|
||||||
var announcement = new TenantAnnouncement
|
var announcement = new TenantAnnouncement
|
||||||
{
|
{
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
@@ -78,7 +86,7 @@ public sealed class CreateTenantAnnouncementCommandHandler(
|
|||||||
PublisherScope = request.PublisherScope,
|
PublisherScope = request.PublisherScope,
|
||||||
PublisherUserId = publisherUserId,
|
PublisherUserId = publisherUserId,
|
||||||
Status = AnnouncementStatus.Draft,
|
Status = AnnouncementStatus.Draft,
|
||||||
TargetType = request.TargetType.Trim(),
|
TargetType = normalizedTargetType,
|
||||||
TargetParameters = request.TargetParameters
|
TargetParameters = request.TargetParameters
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ public sealed class GetAnnouncementByIdQueryHandler(
|
|||||||
{
|
{
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
// 1. 查询公告主体(含平台公告)
|
// 1. 查询公告主体
|
||||||
var announcement = await announcementRepository.FindByIdInScopeAsync(tenantId, request.AnnouncementId, cancellationToken);
|
var announcement = await announcementRepository.FindByIdAsync(tenantId, request.AnnouncementId, cancellationToken);
|
||||||
if (announcement == null)
|
if (announcement == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ public sealed class MarkAnnouncementAsReadCommandHandler(
|
|||||||
{
|
{
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
// 1. 查询公告(含平台公告)
|
// 1. 查询公告
|
||||||
var announcement = await announcementRepository.FindByIdInScopeAsync(tenantId, request.AnnouncementId, cancellationToken);
|
var announcement = await announcementRepository.FindByIdAsync(tenantId, request.AnnouncementId, cancellationToken);
|
||||||
if (announcement == null)
|
if (announcement == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||||
|
using TakeoutSaaS.Application.App.Tenants.Targeting;
|
||||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
@@ -42,6 +43,11 @@ public sealed class UpdateTenantAnnouncementCommandHandler(
|
|||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空");
|
throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空");
|
||||||
}
|
}
|
||||||
|
// 3.1 (空行后) 校验目标受众类型:租户端禁止跨租户目标类型
|
||||||
|
if (!TenantAnnouncementTargetTypePolicy.IsAllowed(request.TargetType))
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.ValidationFailed, "租户端不支持该目标受众类型");
|
||||||
|
}
|
||||||
|
|
||||||
// 4. (空行后) 查询公告
|
// 4. (空行后) 查询公告
|
||||||
var announcement = await announcementRepository.FindByIdAsync(currentTenantId, request.AnnouncementId, cancellationToken);
|
var announcement = await announcementRepository.FindByIdAsync(currentTenantId, request.AnnouncementId, cancellationToken);
|
||||||
@@ -63,7 +69,7 @@ public sealed class UpdateTenantAnnouncementCommandHandler(
|
|||||||
// 5. (空行后) 更新字段
|
// 5. (空行后) 更新字段
|
||||||
announcement.Title = request.Title.Trim();
|
announcement.Title = request.Title.Trim();
|
||||||
announcement.Content = request.Content;
|
announcement.Content = request.Content;
|
||||||
announcement.TargetType = string.IsNullOrWhiteSpace(request.TargetType) ? announcement.TargetType : request.TargetType.Trim();
|
announcement.TargetType = TenantAnnouncementTargetTypePolicy.Normalize(request.TargetType);
|
||||||
announcement.TargetParameters = request.TargetParameters;
|
announcement.TargetParameters = request.TargetParameters;
|
||||||
announcement.RowVersion = request.RowVersion;
|
announcement.RowVersion = request.RowVersion;
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ public static class TargetTypeFilter
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1. 租户端严格限制:公告必须属于当前租户
|
||||||
|
if (announcement.TenantId != context.TenantId)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var targetType = announcement.TargetType?.Trim();
|
var targetType = announcement.TargetType?.Trim();
|
||||||
if (string.IsNullOrWhiteSpace(targetType))
|
if (string.IsNullOrWhiteSpace(targetType))
|
||||||
{
|
{
|
||||||
@@ -37,13 +43,7 @@ public static class TargetTypeFilter
|
|||||||
|
|
||||||
return normalized switch
|
return normalized switch
|
||||||
{
|
{
|
||||||
"ALL" => announcement.TenantId == 0
|
"ALL" or "ALL_TENANTS" or "TENANT_ALL" => ApplyPayloadConstraints(payload, parsed, context, allowEmpty: true),
|
||||||
? ApplyPayloadConstraints(payload, parsed, context, allowEmpty: true)
|
|
||||||
: announcement.TenantId == context.TenantId
|
|
||||||
&& ApplyPayloadConstraints(payload, parsed, context, allowEmpty: true),
|
|
||||||
"ALL_TENANTS" => ApplyPayloadConstraints(payload, parsed, context, allowEmpty: true),
|
|
||||||
"TENANT_ALL" => announcement.TenantId == context.TenantId
|
|
||||||
&& ApplyPayloadConstraints(payload, parsed, context, allowEmpty: true),
|
|
||||||
"SPECIFIC_TENANTS" => RequireTenantMatch(payload, parsed, context)
|
"SPECIFIC_TENANTS" => RequireTenantMatch(payload, parsed, context)
|
||||||
&& ApplyPayloadConstraints(payload, parsed, context, allowEmpty: false),
|
&& ApplyPayloadConstraints(payload, parsed, context, allowEmpty: false),
|
||||||
"USERS" or "SPECIFIC_USERS" or "USER_IDS" => RequireUserMatch(payload, parsed, context)
|
"USERS" or "SPECIFIC_USERS" or "USER_IDS" => RequireUserMatch(payload, parsed, context)
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Collections.Frozen;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Application.App.Tenants.Targeting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 租户公告目标受众类型策略(租户端)。
|
||||||
|
/// </summary>
|
||||||
|
public static class TenantAnnouncementTargetTypePolicy
|
||||||
|
{
|
||||||
|
private static readonly FrozenSet<string> AllowedTargetTypes = FrozenSet.ToFrozenSet(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"ALL",
|
||||||
|
"TENANT_ALL",
|
||||||
|
"USERS",
|
||||||
|
"SPECIFIC_USERS",
|
||||||
|
"USER_IDS",
|
||||||
|
"ROLES",
|
||||||
|
"ROLE",
|
||||||
|
"PERMISSIONS",
|
||||||
|
"PERMISSION",
|
||||||
|
"MERCHANTS",
|
||||||
|
"MERCHANT_IDS"
|
||||||
|
},
|
||||||
|
StringComparer.Ordinal);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断目标受众类型在租户端是否允许。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetType">目标受众类型。</param>
|
||||||
|
/// <returns>允许返回 true,否则 false。</returns>
|
||||||
|
public static bool IsAllowed(string? targetType)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(targetType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = Normalize(targetType);
|
||||||
|
return AllowedTargetTypes.Contains(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 规范化目标受众类型(Trim + UpperInvariant)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetType">目标受众类型。</param>
|
||||||
|
/// <returns>规范化后的类型。</returns>
|
||||||
|
public static string Normalize(string targetType) => targetType.Trim().ToUpperInvariant();
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||||
|
using TakeoutSaaS.Application.App.Tenants.Targeting;
|
||||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Tenants.Validators;
|
namespace TakeoutSaaS.Application.App.Tenants.Validators;
|
||||||
@@ -23,6 +24,10 @@ public sealed class CreateAnnouncementCommandValidator : AbstractValidator<Creat
|
|||||||
|
|
||||||
RuleFor(x => x.TargetType)
|
RuleFor(x => x.TargetType)
|
||||||
.NotEmpty();
|
.NotEmpty();
|
||||||
|
// 1. (空行后) 限制租户端目标类型,禁止跨租户目标
|
||||||
|
RuleFor(x => x.TargetType)
|
||||||
|
.Must(TenantAnnouncementTargetTypePolicy.IsAllowed)
|
||||||
|
.WithMessage("租户端不支持该目标受众类型");
|
||||||
|
|
||||||
RuleFor(x => x.TenantId)
|
RuleFor(x => x.TenantId)
|
||||||
.GreaterThan(0)
|
.GreaterThan(0)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||||
|
using TakeoutSaaS.Application.App.Tenants.Targeting;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Tenants.Validators;
|
namespace TakeoutSaaS.Application.App.Tenants.Validators;
|
||||||
|
|
||||||
@@ -20,6 +21,11 @@ public sealed class UpdateAnnouncementCommandValidator : AbstractValidator<Updat
|
|||||||
RuleFor(x => x.Content)
|
RuleFor(x => x.Content)
|
||||||
.NotEmpty();
|
.NotEmpty();
|
||||||
|
|
||||||
|
RuleFor(x => x.TargetType)
|
||||||
|
.NotEmpty()
|
||||||
|
.Must(TenantAnnouncementTargetTypePolicy.IsAllowed)
|
||||||
|
.WithMessage("租户端不支持该目标受众类型");
|
||||||
|
|
||||||
RuleFor(x => x.RowVersion)
|
RuleFor(x => x.RowVersion)
|
||||||
.NotNull()
|
.NotNull()
|
||||||
.Must(rowVersion => rowVersion != null && rowVersion.Length > 0)
|
.Must(rowVersion => rowVersion != null && rowVersion.Length > 0)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Domain.Tenants.Repositories;
|
|||||||
public interface ITenantAnnouncementRepository
|
public interface ITenantAnnouncementRepository
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询公告列表(包含平台公告 TenantId=0),按类型、状态与生效时间筛选。
|
/// 查询公告列表,按类型、状态与生效时间筛选。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tenantId">租户 ID。</param>
|
/// <param name="tenantId">租户 ID。</param>
|
||||||
/// <param name="keyword">关键词(标题/内容)。</param>
|
/// <param name="keyword">关键词(标题/内容)。</param>
|
||||||
@@ -37,16 +37,7 @@ public interface ITenantAnnouncementRepository
|
|||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 按 ID 获取公告(包含平台公告 TenantId=0)。
|
/// 查询未读公告。
|
||||||
/// </summary>
|
|
||||||
/// <param name="tenantId">租户 ID。</param>
|
|
||||||
/// <param name="announcementId">公告 ID。</param>
|
|
||||||
/// <param name="cancellationToken">取消标记。</param>
|
|
||||||
/// <returns>公告实体或 null。</returns>
|
|
||||||
Task<TenantAnnouncement?> FindByIdInScopeAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询未读公告(包含平台公告 TenantId=0)。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tenantId">租户 ID。</param>
|
/// <param name="tenantId">租户 ID。</param>
|
||||||
/// <param name="userId">用户 ID。</param>
|
/// <param name="userId">用户 ID。</param>
|
||||||
|
|||||||
@@ -25,10 +25,8 @@ public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context)
|
|||||||
int? limit = null,
|
int? limit = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var tenantIds = new[] { tenantId, 0L };
|
|
||||||
var query = context.TenantAnnouncements.AsNoTracking()
|
var query = context.TenantAnnouncements.AsNoTracking()
|
||||||
.IgnoreQueryFilters()
|
.Where(x => x.TenantId == tenantId);
|
||||||
.Where(x => tenantIds.Contains(x.TenantId));
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(keyword))
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
{
|
{
|
||||||
@@ -86,15 +84,6 @@ public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context)
|
|||||||
return await query.ToListAsync(cancellationToken);
|
return await query.ToListAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task<TenantAnnouncement?> FindByIdInScopeAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var tenantIds = new[] { tenantId, 0L };
|
|
||||||
return context.TenantAnnouncements.AsNoTracking()
|
|
||||||
.IgnoreQueryFilters()
|
|
||||||
.FirstOrDefaultAsync(x => tenantIds.Contains(x.TenantId) && x.Id == announcementId, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<TenantAnnouncement>> SearchUnreadAsync(
|
public async Task<IReadOnlyList<TenantAnnouncement>> SearchUnreadAsync(
|
||||||
long tenantId,
|
long tenantId,
|
||||||
@@ -104,10 +93,8 @@ public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context)
|
|||||||
DateTime? effectiveAt,
|
DateTime? effectiveAt,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var tenantIds = new[] { tenantId, 0L };
|
|
||||||
var announcementQuery = context.TenantAnnouncements.AsNoTracking()
|
var announcementQuery = context.TenantAnnouncements.AsNoTracking()
|
||||||
.IgnoreQueryFilters()
|
.Where(x => x.TenantId == tenantId);
|
||||||
.Where(x => tenantIds.Contains(x.TenantId));
|
|
||||||
|
|
||||||
if (status.HasValue)
|
if (status.HasValue)
|
||||||
{
|
{
|
||||||
@@ -128,7 +115,6 @@ public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var readQuery = context.TenantAnnouncementReads.AsNoTracking()
|
var readQuery = context.TenantAnnouncementReads.AsNoTracking()
|
||||||
.IgnoreQueryFilters()
|
|
||||||
.Where(x => x.TenantId == tenantId);
|
.Where(x => x.TenantId == tenantId);
|
||||||
|
|
||||||
readQuery = userId.HasValue
|
readQuery = userId.HasValue
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public sealed class GetAnnouncementByIdQueryHandlerTests
|
|||||||
|
|
||||||
var announcementRepository = new Mock<ITenantAnnouncementRepository>();
|
var announcementRepository = new Mock<ITenantAnnouncementRepository>();
|
||||||
announcementRepository
|
announcementRepository
|
||||||
.Setup(x => x.FindByIdInScopeAsync(99, 500, It.IsAny<CancellationToken>()))
|
.Setup(x => x.FindByIdAsync(99, 500, It.IsAny<CancellationToken>()))
|
||||||
.ReturnsAsync((TenantAnnouncement?)null);
|
.ReturnsAsync((TenantAnnouncement?)null);
|
||||||
|
|
||||||
var readRepository = new Mock<ITenantAnnouncementReadRepository>();
|
var readRepository = new Mock<ITenantAnnouncementReadRepository>();
|
||||||
@@ -55,7 +55,7 @@ public sealed class GetAnnouncementByIdQueryHandlerTests
|
|||||||
|
|
||||||
var announcementRepository = new Mock<ITenantAnnouncementRepository>();
|
var announcementRepository = new Mock<ITenantAnnouncementRepository>();
|
||||||
announcementRepository
|
announcementRepository
|
||||||
.Setup(x => x.FindByIdInScopeAsync(100, 1, It.IsAny<CancellationToken>()))
|
.Setup(x => x.FindByIdAsync(100, 1, It.IsAny<CancellationToken>()))
|
||||||
.ReturnsAsync(announcement);
|
.ReturnsAsync(announcement);
|
||||||
|
|
||||||
var readRepository = new Mock<ITenantAnnouncementReadRepository>();
|
var readRepository = new Mock<ITenantAnnouncementReadRepository>();
|
||||||
@@ -94,7 +94,7 @@ public sealed class GetAnnouncementByIdQueryHandlerTests
|
|||||||
|
|
||||||
var announcementRepository = new Mock<ITenantAnnouncementRepository>();
|
var announcementRepository = new Mock<ITenantAnnouncementRepository>();
|
||||||
announcementRepository
|
announcementRepository
|
||||||
.Setup(x => x.FindByIdInScopeAsync(200, 10, It.IsAny<CancellationToken>()))
|
.Setup(x => x.FindByIdAsync(200, 10, It.IsAny<CancellationToken>()))
|
||||||
.ReturnsAsync(announcement);
|
.ReturnsAsync(announcement);
|
||||||
|
|
||||||
var readRepository = new Mock<ITenantAnnouncementReadRepository>();
|
var readRepository = new Mock<ITenantAnnouncementReadRepository>();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public static class AnnouncementTestData
|
|||||||
EffectiveFrom = DateTime.UtcNow.AddHours(-1),
|
EffectiveFrom = DateTime.UtcNow.AddHours(-1),
|
||||||
EffectiveTo = DateTime.UtcNow.AddHours(2),
|
EffectiveTo = DateTime.UtcNow.AddHours(2),
|
||||||
PublisherScope = PublisherScope.Tenant,
|
PublisherScope = PublisherScope.Tenant,
|
||||||
TargetType = "ALL_TENANTS",
|
TargetType = "TENANT_ALL",
|
||||||
TargetParameters = null
|
TargetParameters = null
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ public static class AnnouncementTestData
|
|||||||
AnnouncementId = 9001,
|
AnnouncementId = 9001,
|
||||||
Title = "更新公告",
|
Title = "更新公告",
|
||||||
Content = "更新内容",
|
Content = "更新内容",
|
||||||
TargetType = "ALL_TENANTS",
|
TargetType = "TENANT_ALL",
|
||||||
TargetParameters = null,
|
TargetParameters = null,
|
||||||
RowVersion = new byte[] { 1, 2, 3 }
|
RowVersion = new byte[] { 1, 2, 3 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public sealed class AnnouncementRegressionTests
|
|||||||
AnnouncementId = announcement.Id,
|
AnnouncementId = announcement.Id,
|
||||||
Title = "更新后的标题",
|
Title = "更新后的标题",
|
||||||
Content = "更新后的内容",
|
Content = "更新后的内容",
|
||||||
TargetType = "ALL_TENANTS",
|
TargetType = "TENANT_ALL",
|
||||||
RowVersion = announcement.RowVersion
|
RowVersion = announcement.RowVersion
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ public sealed class AnnouncementRegressionTests
|
|||||||
EffectiveTo = null,
|
EffectiveTo = null,
|
||||||
PublisherScope = PublisherScope.Tenant,
|
PublisherScope = PublisherScope.Tenant,
|
||||||
Status = AnnouncementStatus.Draft,
|
Status = AnnouncementStatus.Draft,
|
||||||
TargetType = "ALL_TENANTS",
|
TargetType = "TENANT_ALL",
|
||||||
TargetParameters = null,
|
TargetParameters = null,
|
||||||
RowVersion = new byte[] { 1 }
|
RowVersion = new byte[] { 1 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ public sealed class AnnouncementWorkflowTests
|
|||||||
AnnouncementId = announcement.Id,
|
AnnouncementId = announcement.Id,
|
||||||
Title = "更新标题",
|
Title = "更新标题",
|
||||||
Content = "更新内容",
|
Content = "更新内容",
|
||||||
TargetType = "ALL_TENANTS",
|
TargetType = "TENANT_ALL",
|
||||||
RowVersion = announcement.RowVersion
|
RowVersion = announcement.RowVersion
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ public sealed class AnnouncementWorkflowTests
|
|||||||
AnnouncementId = announcement.Id,
|
AnnouncementId = announcement.Id,
|
||||||
Title = "并发更新",
|
Title = "并发更新",
|
||||||
Content = "内容",
|
Content = "内容",
|
||||||
TargetType = "ALL_TENANTS",
|
TargetType = "TENANT_ALL",
|
||||||
RowVersion = new byte[] { 1 }
|
RowVersion = new byte[] { 1 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ public sealed class AnnouncementWorkflowTests
|
|||||||
EffectiveTo = DateTime.UtcNow.AddMinutes(30),
|
EffectiveTo = DateTime.UtcNow.AddMinutes(30),
|
||||||
PublisherScope = PublisherScope.Tenant,
|
PublisherScope = PublisherScope.Tenant,
|
||||||
Status = AnnouncementStatus.Draft,
|
Status = AnnouncementStatus.Draft,
|
||||||
TargetType = "ALL_TENANTS",
|
TargetType = "TENANT_ALL",
|
||||||
TargetParameters = null,
|
TargetParameters = null,
|
||||||
RowVersion = new byte[] { 1 }
|
RowVersion = new byte[] { 1 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ namespace TakeoutSaaS.Integration.Tests.App.Tenants;
|
|||||||
public sealed class TenantAnnouncementRepositoryScopeTests
|
public sealed class TenantAnnouncementRepositoryScopeTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GivenTenantAndPlatformAnnouncements_WhenSearchAsync_ThenReturnsBoth()
|
public async Task GivenDifferentTenantsAnnouncements_WhenSearchAsync_ThenReturnsOnlyCurrentTenant()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using var database = new SqliteTestDatabase();
|
using var database = new SqliteTestDatabase();
|
||||||
using var context = database.CreateContext(tenantId: 800);
|
using var context = database.CreateContext(tenantId: 800);
|
||||||
|
|
||||||
var tenantAnnouncement = CreateAnnouncement(tenantId: 800, id: 9200);
|
var tenantAnnouncement = CreateAnnouncement(tenantId: 800, id: 9200);
|
||||||
var platformAnnouncement = CreateAnnouncement(tenantId: 0, id: 9201);
|
var otherTenantAnnouncement = CreateAnnouncement(tenantId: 801, id: 9201);
|
||||||
|
|
||||||
context.TenantAnnouncements.AddRange(tenantAnnouncement, platformAnnouncement);
|
context.TenantAnnouncements.AddRange(tenantAnnouncement, otherTenantAnnouncement);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
context.ChangeTracker.Clear();
|
context.ChangeTracker.Clear();
|
||||||
|
|
||||||
@@ -37,7 +37,8 @@ public sealed class TenantAnnouncementRepositoryScopeTests
|
|||||||
cancellationToken: CancellationToken.None);
|
cancellationToken: CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
results.Select(x => x.Id).Should().Contain(new[] { tenantAnnouncement.Id, platformAnnouncement.Id });
|
results.Select(x => x.Id).Should().Contain(tenantAnnouncement.Id);
|
||||||
|
results.Select(x => x.Id).Should().NotContain(otherTenantAnnouncement.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TenantAnnouncement CreateAnnouncement(long tenantId, long id)
|
private static TenantAnnouncement CreateAnnouncement(long tenantId, long id)
|
||||||
@@ -52,7 +53,7 @@ public sealed class TenantAnnouncementRepositoryScopeTests
|
|||||||
EffectiveFrom = DateTime.UtcNow.AddMinutes(-5),
|
EffectiveFrom = DateTime.UtcNow.AddMinutes(-5),
|
||||||
PublisherScope = PublisherScope.Tenant,
|
PublisherScope = PublisherScope.Tenant,
|
||||||
Status = AnnouncementStatus.Draft,
|
Status = AnnouncementStatus.Draft,
|
||||||
TargetType = "ALL_TENANTS",
|
TargetType = "TENANT_ALL",
|
||||||
RowVersion = new byte[] { 1 }
|
RowVersion = new byte[] { 1 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ public sealed class AnnouncementQueryPerformanceTests
|
|||||||
var announcements = new List<TenantAnnouncement>();
|
var announcements = new List<TenantAnnouncement>();
|
||||||
for (var i = 0; i < 1000; i++)
|
for (var i = 0; i < 1000; i++)
|
||||||
{
|
{
|
||||||
var tenantId = i % 2 == 0 ? 900 : 0;
|
var tenantId = 900;
|
||||||
var targetType = i % 10 == 0 ? "ROLES" : "ALL_TENANTS";
|
var targetType = i % 10 == 0 ? "ROLES" : "TENANT_ALL";
|
||||||
var targetParameters = i % 10 == 0 ? "{\"roles\":[\"ops\"]}" : null;
|
var targetParameters = i % 10 == 0 ? "{\"roles\":[\"ops\"]}" : null;
|
||||||
|
|
||||||
announcements.Add(new TenantAnnouncement
|
announcements.Add(new TenantAnnouncement
|
||||||
@@ -34,7 +34,7 @@ public sealed class AnnouncementQueryPerformanceTests
|
|||||||
AnnouncementType = TenantAnnouncementType.System,
|
AnnouncementType = TenantAnnouncementType.System,
|
||||||
Priority = i % 5,
|
Priority = i % 5,
|
||||||
EffectiveFrom = DateTime.UtcNow.AddDays(-1),
|
EffectiveFrom = DateTime.UtcNow.AddDays(-1),
|
||||||
PublisherScope = tenantId == 0 ? PublisherScope.Platform : PublisherScope.Tenant,
|
PublisherScope = PublisherScope.Tenant,
|
||||||
Status = AnnouncementStatus.Published,
|
Status = AnnouncementStatus.Published,
|
||||||
TargetType = targetType,
|
TargetType = targetType,
|
||||||
TargetParameters = targetParameters,
|
TargetParameters = targetParameters,
|
||||||
|
|||||||
Reference in New Issue
Block a user