Files
tiku-backend.net/Tiku.IntegrationTests/Api/LearningAccessPolicyTests.cs

226 lines
9.0 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Learning;
using Tiku.Domain.Commerce;
using Tiku.Domain.Identity;
using Tiku.Domain.Learning;
using Tiku.Domain.Tenancy;
namespace Tiku.IntegrationTests.Api;
public sealed class LearningAccessPolicyTests
{
[Fact]
public async Task Target_region_history_is_isolated_per_business_line()
{
await using var factory = new ApiTestFactory();
var tenantId = Guid.NewGuid();
var userId = Guid.NewGuid();
var primaryBusinessLineId = Guid.NewGuid();
var futureBusinessLineId = Guid.NewGuid();
var primaryRegionId = Guid.NewGuid();
var futureRegionId = Guid.NewGuid();
await factory.SeedAsync(
new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Multi-business Tenant" },
new User { Id = userId, Phone = $"137{Random.Shared.Next(10_000_000, 99_999_999)}" },
new BusinessLine
{
Id = primaryBusinessLineId,
Code = $"primary-{primaryBusinessLineId:N}",
Name = "当前主业务",
RegionAccessStrategy = LearningRegionAccessStrategy.NationalWithStudentTargetRegion
},
new BusinessLine
{
Id = futureBusinessLineId,
Code = $"future-{futureBusinessLineId:N}",
Name = "未来新增业务",
RegionAccessStrategy = LearningRegionAccessStrategy.NationalWithStudentTargetRegion
},
new MarketRegion { Id = primaryRegionId, Code = $"p-{primaryRegionId:N}"[..18], Name = "主业务地区" },
new MarketRegion { Id = futureRegionId, Code = $"f-{futureRegionId:N}"[..18], Name = "新增业务地区" },
new TenantLearningLicense
{
TenantId = tenantId,
BusinessLineId = primaryBusinessLineId,
IsPrimary = true,
IncludesNational = true
},
new TenantLearningLicense
{
TenantId = tenantId,
BusinessLineId = futureBusinessLineId,
IsPrimary = false,
IncludesNational = true
},
new StudentTargetRegionHistory
{
TenantId = tenantId,
UserId = userId,
BusinessLineId = primaryBusinessLineId,
MarketRegionId = primaryRegionId
},
new StudentTargetRegionHistory
{
TenantId = tenantId,
UserId = userId,
BusinessLineId = futureBusinessLineId,
MarketRegionId = futureRegionId
});
using var scope = factory.CreateTenantScope(tenantId);
var target = await scope.ServiceProvider.GetRequiredService<ILearningAccessAdministrationService>()
.GetTargetRegionAsync(new LearningActor(tenantId, userId));
Assert.NotNull(target);
Assert.Equal(primaryRegionId, target.MarketRegionId);
}
[Theory]
[InlineData(LearningRegionAccessStrategy.NationalOnly, true, 1, 0)]
[InlineData(LearningRegionAccessStrategy.LicensedRegions, false, 2, 2)]
[InlineData(LearningRegionAccessStrategy.NationalWithStudentTargetRegion, true, 2, 1)]
[InlineData(LearningRegionAccessStrategy.NationalWithLicensedRegions, true, 3, 2)]
public async Task Configured_region_strategy_compiles_expected_slice_and_region_grants(
LearningRegionAccessStrategy strategy,
bool includesNational,
int expectedSliceCount,
int expectedRegionCount)
{
await using var factory = new ApiTestFactory();
var tenantId = Guid.NewGuid();
var userId = Guid.NewGuid();
var businessLineId = Guid.NewGuid();
var licenseId = Guid.NewGuid();
var productId = Guid.NewGuid();
var regionA = Guid.NewGuid();
var regionB = Guid.NewGuid();
var nationalSlice = Slice(tenantId, businessLineId, LearningRegionScopeKind.National, null);
var regionASlice = Slice(tenantId, businessLineId, LearningRegionScopeKind.Region, regionA);
var regionBSlice = Slice(tenantId, businessLineId, LearningRegionScopeKind.Region, regionB);
var slices = new[] { nationalSlice, regionASlice, regionBSlice };
var entities = new List<object>
{
new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Policy Tenant" },
new User { Id = userId, Phone = $"136{Random.Shared.Next(10_000_000, 99_999_999)}" },
new BusinessLine
{
Id = businessLineId,
Code = $"p-{(int)strategy}-{tenantId:N}",
Name = "配置化业务线",
RegionAccessStrategy = strategy
},
new MarketRegion { Id = regionA, Code = $"a-{regionA:N}"[..18], Name = "地区 A" },
new MarketRegion { Id = regionB, Code = $"b-{regionB:N}"[..18], Name = "地区 B" },
new TenantLearningLicense
{
Id = licenseId,
TenantId = tenantId,
BusinessLineId = businessLineId,
IncludesNational = includesNational,
StartsAt = DateTimeOffset.UtcNow.AddDays(-1)
},
new TenantLearningLicenseRegion
{
TenantId = tenantId,
LicenseId = licenseId,
MarketRegionId = regionA,
IsBaseRegion = true
},
new TenantLearningLicenseRegion
{
TenantId = tenantId,
LicenseId = licenseId,
MarketRegionId = regionB
},
new LearningProduct
{
Id = productId,
TenantId = tenantId,
BusinessLineId = businessLineId,
Code = "all-slices",
Name = "全部测试切片"
},
new Entitlement
{
TenantId = tenantId,
UserId = userId,
LearningProductId = productId,
StartsAt = DateTimeOffset.UtcNow.AddDays(-1),
ExpiresAt = DateTimeOffset.UtcNow.AddDays(1),
Status = EntitlementStatus.Active,
SourceType = "integration_test"
},
new LearningAccessVersion { TenantId = tenantId, UserId = userId }
};
entities.AddRange(slices);
entities.AddRange(slices.Select(slice => new LearningProductScope
{
TenantId = tenantId,
ProductId = productId,
ContentSliceOwnerTenantId = tenantId,
ContentSliceId = slice.Id
}));
if (strategy == LearningRegionAccessStrategy.NationalWithStudentTargetRegion)
entities.Add(new StudentTargetRegionHistory
{
TenantId = tenantId,
UserId = userId,
BusinessLineId = businessLineId,
MarketRegionId = regionA
});
await factory.SeedAsync([.. entities]);
using var scope = factory.CreateTenantScope(tenantId);
var snapshot = await scope.ServiceProvider.GetRequiredService<ILearningAccessService>()
.GetSnapshotAsync(new LearningActor(tenantId, userId));
Assert.Equal(expectedSliceCount, snapshot.ContentSliceIds.Count);
Assert.Equal(expectedRegionCount, snapshot.LicensedRegionIds.Count);
Assert.Equal(includesNational, snapshot.ContentSliceIds.Contains(nationalSlice.Id));
Assert.Equal(
strategy == LearningRegionAccessStrategy.NationalWithStudentTargetRegion ? regionA : (Guid?)null,
snapshot.TargetRegionId);
}
[Fact]
public async Task Snapshot_fails_closed_without_an_active_primary_license()
{
await using var factory = new ApiTestFactory();
var tenantId = Guid.NewGuid();
var userId = Guid.NewGuid();
await factory.SeedAsync(new Tenant
{
Id = tenantId,
Slug = tenantId.ToString("N"),
Name = "Unlicensed Tenant"
});
using var scope = factory.CreateTenantScope(tenantId);
var exception = await Assert.ThrowsAsync<LearningAccessException>(() =>
scope.ServiceProvider.GetRequiredService<ILearningAccessService>()
.GetSnapshotAsync(new LearningActor(tenantId, userId)));
Assert.Equal("learning_license_required", exception.Code);
}
private static ContentSlice Slice(
Guid tenantId,
Guid businessLineId,
LearningRegionScopeKind regionScope,
Guid? regionId)
{
return new ContentSlice
{
Id = Guid.NewGuid(),
TenantId = tenantId,
BusinessLineId = businessLineId,
RegionScope = regionScope,
MarketRegionId = regionId,
ResourceType = LearningContentResourceType.Collection,
ResourceId = Guid.NewGuid(),
Status = ContentSliceStatus.Active
};
}
}