elsa-core/test/unit/Elsa.Persistence.VNext.UnitTests/PersistenceVNextIntegrationTests.cs
Sipke Schoorstra c16995014c
Add Persistence vNext provider-neutral POC
Adds the Persistence vNext proof of concept, including provider-neutral schema/document abstractions, relational and document provider POCs, Elsa integration, runtime-defined entities, physicalization planning, workflow runtime evaluation, and focused tests.
2026-06-02 21:05:21 +02:00

127 lines
6 KiB
C#

using Elsa.Common;
using Elsa.Persistence.VNext.Builders;
using Elsa.Persistence.VNext.Contracts;
using Elsa.Persistence.VNext.Document;
using Elsa.Persistence.VNext.Extensions;
using Elsa.Persistence.VNext.Extensions.Contracts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Elsa.Persistence.VNext.UnitTests;
public class PersistenceVNextIntegrationTests
{
[Fact]
public void SchemaCatalog_ComposesRegisteredManifests()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddPersistenceVNext();
services.AddSingleton<IPersistenceSchemaProvider, OrdersSchemaProvider>();
services.AddSingleton<IPersistenceSchemaProvider, CustomersSchemaProvider>();
using var serviceProvider = services.BuildServiceProvider();
var schema = serviceProvider.GetRequiredService<IPersistenceSchemaCatalog>().DescribeSchema();
Assert.Equal("Elsa", schema.Name);
Assert.Contains(schema.StorageUnits, x => x.Name == "Orders");
Assert.Contains(schema.StorageUnits, x => x.Name == "Customers");
}
[Fact]
public async Task StartupTask_MaterializesRegisteredDocumentStoresAndRecordsStatus()
{
var store = new RecordingDocumentStore();
var services = new ServiceCollection();
services.AddLogging();
services.AddPersistenceVNext();
services.AddSingleton<IPersistenceSchemaProvider, OrdersSchemaProvider>();
services.AddSingleton<IDocumentStore>(store);
using var serviceProvider = services.BuildServiceProvider();
var startupTask = Assert.Single(serviceProvider.GetServices<IStartupTask>());
await startupTask.ExecuteAsync(CancellationToken.None);
var status = serviceProvider.GetRequiredService<IPersistenceVNextStatus>().Snapshot;
Assert.True(store.WasMaterialized);
Assert.True(status.Succeeded);
Assert.Contains("Orders", status.StorageUnits);
Assert.Contains(typeof(RecordingDocumentStore).FullName!, status.DocumentStoreTypes);
}
[Fact]
public async Task StartupTask_RecordsRecoveryHintsWhenMaterializationFails()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddPersistenceVNext();
services.AddSingleton<IPersistenceSchemaProvider, OrdersSchemaProvider>();
services.AddSingleton<IDocumentStore, FailingDocumentStore>();
using var serviceProvider = services.BuildServiceProvider();
var startupTask = Assert.Single(serviceProvider.GetServices<IStartupTask>());
await Assert.ThrowsAsync<InvalidOperationException>(() => startupTask.ExecuteAsync(CancellationToken.None));
var status = serviceProvider.GetRequiredService<IPersistenceVNextStatus>().Snapshot;
Assert.False(status.Succeeded);
Assert.Equal("provider unavailable", status.ErrorMessage);
Assert.Contains(status.RecoveryHints, x => x.Contains("connect to its database"));
Assert.Contains(status.RecoveryHints, x => x.Contains("materialization lock strategy"));
}
private class OrdersSchemaProvider : IPersistenceSchemaProvider
{
public PersistenceSchema DescribeSchema()
{
return new PersistenceSchemaBuilder("Orders")
.StorageUnit("Orders", storage => storage
.RequiredField("Id", PersistenceColumnType.String, 450)
.RequiredField("Status", PersistenceColumnType.String, 50)
.Key("PK_Orders", "Id")
.Index("IX_Orders_Status", "Status"))
.Build();
}
}
private class CustomersSchemaProvider : IPersistenceSchemaProvider
{
public PersistenceSchema DescribeSchema()
{
return new PersistenceSchemaBuilder("Customers")
.StorageUnit("Customers", storage => storage
.RequiredField("Id", PersistenceColumnType.String, 450)
.RequiredField("Name", PersistenceColumnType.String, 200)
.Key("PK_Customers", "Id")
.Index("IX_Customers_Name", "Name"))
.Build();
}
}
private class RecordingDocumentStore : IDocumentStore
{
public bool WasMaterialized { get; private set; }
public Task MaterializeAsync(CancellationToken cancellationToken = default)
{
WasMaterialized = true;
return Task.CompletedTask;
}
public Task<StoredDocument> SaveAsync(SaveDocumentRequest request, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<StoredDocument?> LoadAsync(string storageUnit, string id, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<bool> DeleteAsync(string storageUnit, string id, long? expectedVersion = null, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<IReadOnlyList<StoredDocument>> QueryAsync(DocumentQuery query, CancellationToken cancellationToken = default) => throw new NotSupportedException();
}
private class FailingDocumentStore : IDocumentStore
{
public Task MaterializeAsync(CancellationToken cancellationToken = default) => throw new InvalidOperationException("provider unavailable");
public Task<StoredDocument> SaveAsync(SaveDocumentRequest request, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<StoredDocument?> LoadAsync(string storageUnit, string id, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<bool> DeleteAsync(string storageUnit, string id, long? expectedVersion = null, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<IReadOnlyList<StoredDocument>> QueryAsync(DocumentQuery query, CancellationToken cancellationToken = default) => throw new NotSupportedException();
}
}