181 lines
6.7 KiB
C#
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);
|
|
}
|