147 lines
5.9 KiB
C#
147 lines
5.9 KiB
C#
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Prefab.Catalog.Api.Data;
|
|
using Prefab.Catalog.Data;
|
|
using Prefab.Catalog.Data.Services;
|
|
using Prefab.Catalog.Domain.Entities;
|
|
using Prefab.Catalog.Domain.Services;
|
|
using Prefab.Data;
|
|
using Prefab.Handler;
|
|
using Shouldly;
|
|
|
|
namespace Prefab.Tests.Unit.Catalog;
|
|
|
|
public sealed class CatalogSeederShould
|
|
{
|
|
[Fact]
|
|
public async Task PopulateCatalogDataInSqliteDatabase()
|
|
{
|
|
await using var connection = new SqliteConnection("DataSource=:memory:");
|
|
await connection.OpenAsync(TestContext.Current.CancellationToken);
|
|
|
|
var services = new ServiceCollection();
|
|
services.AddSingleton<IHandlerContextAccessor, HandlerContextAccessor>();
|
|
|
|
services.AddDbContext<TestCatalogDb>(options =>
|
|
{
|
|
options.UseSqlite(connection);
|
|
});
|
|
|
|
services.AddDbContextFactory<TestCatalogDb>(options =>
|
|
{
|
|
options.UseSqlite(connection);
|
|
}, ServiceLifetime.Scoped);
|
|
|
|
services.AddScoped<AppDb>(sp => sp.GetRequiredService<TestCatalogDb>());
|
|
services.AddScoped<IPrefabDb>(sp => sp.GetRequiredService<TestCatalogDb>());
|
|
services.AddScoped<IModuleDb>(sp => sp.GetRequiredService<TestCatalogDb>());
|
|
services.AddScoped<IModuleDbReadOnly>(sp => sp.GetRequiredService<TestCatalogDb>());
|
|
services.AddScoped<ICatalogDbContextFactory, TestCatalogDbFactory>();
|
|
services.AddScoped<IUniqueChecker, UniqueChecker>();
|
|
services.AddSingleton<ILogger<Seeder>>(_ => NullLogger<Seeder>.Instance);
|
|
services.AddScoped<Seeder>();
|
|
|
|
await using var provider = services.BuildServiceProvider();
|
|
|
|
await using (var scope = provider.CreateAsyncScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<TestCatalogDb>();
|
|
await db.Database.EnsureCreatedAsync(TestContext.Current.CancellationToken);
|
|
}
|
|
|
|
await using (var scope = provider.CreateAsyncScope())
|
|
{
|
|
var seeder = scope.ServiceProvider.GetRequiredService<Seeder>();
|
|
await seeder.Execute(scope.ServiceProvider, TestContext.Current.CancellationToken);
|
|
}
|
|
|
|
await using (var scope = provider.CreateAsyncScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<TestCatalogDb>();
|
|
|
|
var allCategories = await db.Categories
|
|
.Where(c => c.DeletedOn == null)
|
|
.ToListAsync(TestContext.Current.CancellationToken);
|
|
|
|
allCategories.ShouldNotBeEmpty("Catalog seeder should create categories.");
|
|
|
|
var categories = allCategories
|
|
.Select(c => c.Slug)
|
|
.Where(slug => !string.IsNullOrWhiteSpace(slug))
|
|
.Select(slug => slug!.Trim())
|
|
.ToList();
|
|
|
|
var expectedSlugs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"ceiling-supports",
|
|
"boxes-and-covers",
|
|
"prefab-assemblies",
|
|
"rod-strut-hardware"
|
|
};
|
|
|
|
categories.ShouldBeSubsetOf(expectedSlugs, "Seeded catalog should only include the expected root categories.");
|
|
categories.Count.ShouldBe(expectedSlugs.Count, "Seeded catalog should include all expected root categories.");
|
|
|
|
var products = await db.Products
|
|
.Where(p => p.DeletedOn == null && p.Kind == ProductKind.Model)
|
|
.Include(p => p.Variants)
|
|
.Select(p => new { p.Slug, p.Name, VariantCount = p.Variants.Count })
|
|
.ToListAsync(TestContext.Current.CancellationToken);
|
|
|
|
products.ShouldNotBeEmpty("Catalog seeder should introduce product models for listing.");
|
|
products.Any(p => string.Equals(p.Slug, "ceiling-tbar-box-assembly", StringComparison.OrdinalIgnoreCase)).ShouldBeTrue();
|
|
products.Any(p => string.Equals(p.Slug, "fan-hanger-assembly", StringComparison.OrdinalIgnoreCase)).ShouldBeTrue();
|
|
products.Any(p => p.VariantCount > 0).ShouldBeTrue("Seed should include at least one model with variants.");
|
|
}
|
|
}
|
|
|
|
private sealed class TestCatalogDb : AppDb
|
|
{
|
|
public TestCatalogDb(DbContextOptions<TestCatalogDb> options, IHandlerContextAccessor accessor)
|
|
: base(options, accessor)
|
|
{
|
|
}
|
|
|
|
protected override void PrefabOnModelCreating(ModelBuilder builder)
|
|
{
|
|
base.PrefabOnModelCreating(builder);
|
|
|
|
foreach (var entity in builder.Model.GetEntityTypes())
|
|
{
|
|
var rowVersionProperty = entity.FindProperty("RowVersion");
|
|
if (rowVersionProperty is not null)
|
|
{
|
|
rowVersionProperty.IsNullable = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
foreach (var entry in ChangeTracker.Entries().Where(e => e.State == EntityState.Added))
|
|
{
|
|
var rowVersion = entry.Properties.FirstOrDefault(p => string.Equals(p.Metadata.Name, "RowVersion", StringComparison.Ordinal));
|
|
if (rowVersion is not null && rowVersion.CurrentValue is null)
|
|
{
|
|
rowVersion.CurrentValue = Guid.NewGuid().ToByteArray();
|
|
}
|
|
}
|
|
|
|
return base.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|
|
|
|
private sealed class TestCatalogDbFactory(IDbContextFactory<TestCatalogDb> dbFactory) : ICatalogDbContextFactory
|
|
{
|
|
public async ValueTask<IModuleDb> CreateWritableAsync(CancellationToken cancellationToken = default) =>
|
|
await dbFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
public async ValueTask<IModuleDbReadOnly> CreateReadOnlyAsync(CancellationToken cancellationToken = default) =>
|
|
await dbFactory.CreateDbContextAsync(cancellationToken);
|
|
}
|
|
}
|
|
|
|
|