Files
tiku-backend.net/Tiku.IntegrationTests/ContentV2/PlatformContentPackageProjectionTests.cs
xiong 3f5957d744
Some checks failed
ci / release-gate (push) Has been cancelled
feat(content): establish v2 learning delivery foundation
2026-08-06 09:52:18 +08:00

181 lines
6.7 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Content;
using Tiku.Domain.Content;
using Tiku.Domain.Learning;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;
using Tiku.IntegrationTests.Api;
namespace Tiku.IntegrationTests.ContentV2;
public sealed class PlatformContentPackageProjectionTests
{
[Fact]
public async Task Cell_projection_is_idempotent_and_ignores_out_of_order_packages()
{
await using var factory = new ApiTestFactory();
var fixture = await SeedPackagesAsync(factory);
using var scope = factory.CreateSystemScope("Apply platform package events to a Cell");
var service = scope.ServiceProvider.GetRequiredService<IPlatformContentPackageProjectionService>();
var applied = await service.ApplyAsync(new ApplyPlatformContentPackageCommand(
fixture.OwnerTenantId,
fixture.NewPackageVersionId,
fixture.PackageCode,
"learning-cell-a",
2,
fixture.NewHash));
var duplicate = await service.ApplyAsync(new ApplyPlatformContentPackageCommand(
fixture.OwnerTenantId,
fixture.NewPackageVersionId,
fixture.PackageCode,
"learning-cell-a",
2,
fixture.NewHash));
var old = await service.ApplyAsync(new ApplyPlatformContentPackageCommand(
fixture.OwnerTenantId,
fixture.OldPackageVersionId,
fixture.PackageCode,
"learning-cell-a",
1,
fixture.OldHash));
Assert.Equal(CellPackageApplyStatus.Applied, applied.Status);
Assert.Equal(CellPackageApplyStatus.Duplicate, duplicate.Status);
Assert.Equal(CellPackageApplyStatus.IgnoredOutOfOrder, old.Status);
var conflict = await Assert.ThrowsAsync<ContentV2Exception>(() => service.ApplyAsync(
new ApplyPlatformContentPackageCommand(
fixture.OwnerTenantId,
fixture.OldPackageVersionId,
fixture.PackageCode,
"learning-cell-a",
2,
fixture.OldHash)));
Assert.Equal("platform_package_sequence_conflict", conflict.Code);
var db = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
var projection = await db.PlatformContentPackageCellProjections.AsNoTracking().SingleAsync();
Assert.Equal(fixture.NewPackageVersionId, projection.PlatformContentPackageVersionId);
Assert.Equal(2, projection.EventSequence);
Assert.Equal(fixture.NewHash, projection.ContentHash);
Assert.Equal(CellProjectionStatus.Ready, projection.Status);
}
private static async Task<PackageFixture> SeedPackagesAsync(ApiTestFactory factory)
{
var ownerTenantId = Guid.NewGuid();
var businessLineId = Guid.NewGuid();
var curriculumId = Guid.NewGuid();
var curriculumVersionId = Guid.NewGuid();
var oldReleaseId = Guid.NewGuid();
var newReleaseId = Guid.NewGuid();
var oldPackageVersionId = Guid.NewGuid();
var newPackageVersionId = Guid.NewGuid();
var packageCode = "national-math";
var oldHash = new string('1', 64);
var newHash = new string('2', 64);
var now = DateTimeOffset.UtcNow;
await factory.SeedAsync(
new Tenant
{
Id = ownerTenantId,
Slug = $"platform-{ownerTenantId:N}",
Name = "Platform content owner",
Mode = TenantMode.PlatformOwned
},
new BusinessLine
{
Id = businessLineId,
Code = $"business-{businessLineId:N}",
Name = "Generic exam business"
},
new Curriculum
{
Id = curriculumId,
TenantId = ownerTenantId,
BusinessLineId = businessLineId,
Code = $"curriculum-{curriculumId:N}",
Name = "Platform curriculum"
});
await factory.SeedAsync(
new CurriculumVersion
{
Id = curriculumVersionId,
TenantId = ownerTenantId,
CurriculumId = curriculumId,
Name = "Platform curriculum V1",
Status = ContentDefinitionStatus.Published,
PublishedAt = now
});
await factory.SeedAsync(
new ContentRelease
{
Id = oldReleaseId,
TenantId = ownerTenantId,
BusinessLineId = businessLineId,
CurriculumVersionId = curriculumVersionId,
ReleaseNo = 1,
Name = "Old release",
SourceFingerprint = oldHash,
Status = ContentReleaseStatus.Published,
PublishedAt = now
},
new ContentRelease
{
Id = newReleaseId,
TenantId = ownerTenantId,
BusinessLineId = businessLineId,
CurriculumVersionId = curriculumVersionId,
ReleaseNo = 2,
Name = "New release",
SourceFingerprint = newHash,
Status = ContentReleaseStatus.Published,
PublishedAt = now
});
await factory.SeedAsync(
new PlatformContentPackageVersion
{
Id = oldPackageVersionId,
TenantId = ownerTenantId,
PackageCode = packageCode,
VersionNo = 1,
BusinessLineId = businessLineId,
ContentReleaseId = oldReleaseId,
ContentHash = oldHash,
Status = PlatformContentPackageStatus.Published,
PublishedAt = now
},
new PlatformContentPackageVersion
{
Id = newPackageVersionId,
TenantId = ownerTenantId,
PackageCode = packageCode,
VersionNo = 2,
BusinessLineId = businessLineId,
ContentReleaseId = newReleaseId,
ContentHash = newHash,
Status = PlatformContentPackageStatus.Published,
PublishedAt = now
});
return new PackageFixture(
ownerTenantId,
oldPackageVersionId,
newPackageVersionId,
packageCode,
oldHash,
newHash);
}
private sealed record PackageFixture(
Guid OwnerTenantId,
Guid OldPackageVersionId,
Guid NewPackageVersionId,
string PackageCode,
string OldHash,
string NewHash);
}