Add project files.

This commit is contained in:
2025-08-18 17:11:43 +03:30
parent 807dc263be
commit e5eb10d604
3943 changed files with 223719 additions and 0 deletions
@@ -0,0 +1,25 @@
using Elsa.Workflows.ComponentTests.Fixtures;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Abstractions;
[Collection(nameof(AppCollection))]
public abstract class AppComponentTest(App app) : IDisposable
{
protected WorkflowServer WorkflowServer { get; } = app.WorkflowServer;
protected Cluster Cluster { get; } = app.Cluster;
protected Infrastructure Infrastructure { get; } = app.Infrastructure;
protected IServiceScope Scope { get; } = app.WorkflowServer.Services.CreateScope();
void IDisposable.Dispose()
{
// Disposing the Scope here and in other places where it is created somehow seems to cause the test runner to hang when running other test projects.
// Let's comment it out for the time being.
//Scope.Dispose();
OnDispose();
}
protected virtual void OnDispose()
{
}
}
@@ -0,0 +1,56 @@
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using Elsa.Expressions.Models;
using Elsa.Extensions;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.ComponentTests.Activities;
public class TriggerSignal : CodeActivity
{
/// <inheritdoc />
[JsonConstructor]
private TriggerSignal(string? source = null, int? line = null) : base(source, line)
{
}
/// <inheritdoc />
public TriggerSignal(string eventName, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(new Literal<string>(eventName), source, line)
{
}
/// <inheritdoc />
public TriggerSignal(Func<string> eventName, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null)
: this(Expression.DelegateExpression(eventName), source, line)
{
}
/// <inheritdoc />
public TriggerSignal(Func<ExpressionExecutionContext, string?> eventName, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null)
: this(Expression.DelegateExpression(eventName), source, line)
{
}
/// <inheritdoc />
public TriggerSignal(Variable<string> variable, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) => EventName = new Input<string>(variable);
/// <inheritdoc />
public TriggerSignal(Literal<string> literal, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) => EventName = new Input<string>(literal);
/// <inheritdoc />
public TriggerSignal(Expression expression, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) => EventName = new Input<string>(expression, new MemoryBlockReference());
/// <inheritdoc />
public TriggerSignal(Input<string> eventName, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) => EventName = eventName;
public Input<string> EventName { get; set; }
protected override void Execute(ActivityExecutionContext context)
{
var testEventManager = context.GetRequiredService<SignalManager>();
var eventName = EventName.Get(context);
testEventManager.Trigger(eventName);
}
}
@@ -0,0 +1,17 @@
using Elsa.MassTransit.Messages;
using Elsa.Testing.Shared;
using Elsa.Testing.Shared.Services;
using Hangfire.Annotations;
using MassTransit;
namespace Elsa.Workflows.ComponentTests.Consumers;
[UsedImplicitly]
public class WorkflowDefinitionEventConsumer(WorkflowDefinitionEvents workflowDefinitionEvents) : IConsumer<WorkflowDefinitionDeleted>
{
public Task Consume(ConsumeContext<WorkflowDefinitionDeleted> context)
{
workflowDefinitionEvents.OnWorkflowDefinitionDeleted(new WorkflowDefinitionDeletedEventArgs(context.Message.Id));
return Task.CompletedTask;
}
}
@@ -0,0 +1,26 @@
using Elsa.Caching;
using Elsa.Testing.Shared;
using Elsa.Testing.Shared.Services;
using Hangfire.Annotations;
using Microsoft.Extensions.Primitives;
namespace Elsa.Workflows.ComponentTests.Decorators;
/// <summary>
/// Provides a decorator for the `IChangeTokenSignaler` interface that triggers change token events.
/// This class is used to raise change token signal events and delegate the actual work to the decorated service.
/// </summary>
[UsedImplicitly]
public class EventPublishingChangeTokenSignaler(IChangeTokenSignaler decoratedService, TriggerChangeTokenSignalEvents triggerChangeTokenSignalEvents) : IChangeTokenSignaler
{
public IChangeToken GetToken(string key)
{
return decoratedService.GetToken(key);
}
public ValueTask TriggerTokenAsync(string key, CancellationToken cancellationToken = default)
{
triggerChangeTokenSignalEvents.RaiseChangeTokenSignalTriggered(new TriggerChangeTokenSignalEventArgs(key));
return decoratedService.TriggerTokenAsync(key, cancellationToken);
}
}
@@ -0,0 +1,29 @@
using Hangfire.Annotations;
namespace Elsa.Workflows.ComponentTests.Fixtures;
[UsedImplicitly]
public class App : IAsyncLifetime
{
public App()
{
Infrastructure = new();
Cluster = new(Infrastructure);
WorkflowServer = Cluster.Pod1;
}
public Infrastructure Infrastructure { get; set; }
public Cluster Cluster { get; set; }
public WorkflowServer WorkflowServer { get; set; }
public async Task InitializeAsync()
{
await Infrastructure.InitializeAsync();
}
public async Task DisposeAsync()
{
await Cluster.DisposeAsync();
await Infrastructure.DisposeAsync();
}
}
@@ -0,0 +1,9 @@
namespace Elsa.Workflows.ComponentTests.Fixtures;
[CollectionDefinition(nameof(AppCollection))]
public class AppCollection : ICollectionFixture<App>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
@@ -0,0 +1,18 @@
using Hangfire.Annotations;
namespace Elsa.Workflows.ComponentTests.Fixtures;
[UsedImplicitly]
public class Cluster(Infrastructure infrastructure) : IAsyncDisposable
{
public WorkflowServer Pod3 { get; set; } = new(infrastructure, "http://localhost:5003");
public WorkflowServer Pod2 { get; set; } = new(infrastructure, "http://localhost:5002");
public WorkflowServer Pod1 { get; set; } = new(infrastructure, "http://localhost:5001");
public async ValueTask DisposeAsync()
{
await Pod3.DisposeAsync();
await Pod2.DisposeAsync();
await Pod1.DisposeAsync();
}
}
@@ -0,0 +1,32 @@
using Testcontainers.PostgreSql;
using Testcontainers.RabbitMq;
namespace Elsa.Workflows.ComponentTests.Fixtures;
public class Infrastructure : IAsyncLifetime
{
public readonly PostgreSqlContainer DbContainer = new PostgreSqlBuilder()
.WithImage("postgres:latest")
.WithDatabase("elsa")
.WithUsername("postgres")
.WithPassword("postgres")
.Build();
public readonly RabbitMqContainer RabbitMqContainer = new RabbitMqBuilder()
.WithImage("rabbitmq:4-management")
.Build();
public Task InitializeAsync()
{
return Task.WhenAll(
DbContainer.StartAsync(),
RabbitMqContainer.StartAsync());
}
public Task DisposeAsync()
{
return Task.WhenAll(
DbContainer.StopAsync(),
RabbitMqContainer.StopAsync());
}
}
@@ -0,0 +1,138 @@
using System.Reflection;
using Elsa.Alterations.Extensions;
using Elsa.Caching;
using Elsa.EntityFrameworkCore.Extensions;
using Elsa.EntityFrameworkCore.Modules.Alterations;
using Elsa.EntityFrameworkCore.Modules.Identity;
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.Extensions;
using Elsa.Identity.Providers;
using Elsa.MassTransit.Extensions;
using Elsa.Testing.Shared.Handlers;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Consumers;
using Elsa.Workflows.ComponentTests.Decorators;
using Elsa.Workflows.ComponentTests.Materializers;
using Elsa.Workflows.ComponentTests.WorkflowProviders;
using Elsa.Workflows.Management;
using Elsa.Workflows.Runtime.Distributed.Extensions;
using FluentStorage;
using Hangfire.Annotations;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Refit;
using static Elsa.Api.Client.RefitSettingsHelper;
namespace Elsa.Workflows.ComponentTests.Fixtures;
[UsedImplicitly]
public class WorkflowServer(Infrastructure infrastructure, string url) : WebApplicationFactory<Program>
{
public TClient CreateApiClient<TClient>()
{
var client = CreateClient();
client.BaseAddress = new(client.BaseAddress!, "/elsa/api");
client.Timeout = TimeSpan.FromMinutes(1);
return RestService.For<TClient>(client, CreateRefitSettings(Services));
}
public HttpClient CreateHttpWorkflowClient()
{
var client = CreateClient();
client.BaseAddress = new(client.BaseAddress!, "/workflows/");
client.Timeout = TimeSpan.FromMinutes(1);
return client;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
var dbConnectionString = infrastructure.DbContainer.GetConnectionString();
var rabbitMqConnectionString = infrastructure.RabbitMqContainer.GetConnectionString();
builder.UseUrls(url);
if (Program.ConfigureForTest == null)
{
Program.ConfigureForTest = elsa =>
{
elsa.AddWorkflowsFrom<WorkflowServer>();
elsa.AddActivitiesFrom<WorkflowServer>();
elsa.UseDefaultAuthentication(defaultAuthentication => defaultAuthentication.UseAdminApiKey());
elsa.UseFluentStorageProvider(sp =>
{
var assemblyLocation = Assembly.GetExecutingAssembly().Location;
var assemblyDirectory = Path.GetDirectoryName(assemblyLocation)!;
var workflowsDirectorySegments = new[]
{
assemblyDirectory, "Scenarios"
};
var workflowsDirectory = Path.Join(workflowsDirectorySegments);
return StorageFactory.Blobs.DirectoryFiles(workflowsDirectory);
});
elsa.UseMassTransit(massTransit =>
{
//massTransit.UseRabbitMq(rabbitMqConnectionString);
massTransit.Services.AddSingleton<WorkflowDefinitionEvents>();
massTransit.AddConsumer<WorkflowDefinitionEventConsumer>("elsa-test-workflow-definition-updates", true);
});
elsa.UseIdentity(identity => identity.UseEntityFrameworkCore(ef => ef.UsePostgreSql(dbConnectionString)));
elsa.UseWorkflowManagement(management =>
{
management.UseEntityFrameworkCore(ef => ef.UsePostgreSql(dbConnectionString));
management.UseMassTransitDispatcher();
management.UseCache();
});
elsa.UseWorkflowRuntime(runtime =>
{
runtime.UseEntityFrameworkCore(ef => ef.UsePostgreSql(dbConnectionString));
runtime.UseCache();
runtime.UseMassTransitDispatcher();
runtime.UseDistributedRuntime();
});
elsa.UseDistributedCache(distributedCaching =>
{
distributedCaching.UseMassTransit();
});
elsa.UseJavaScript(options =>
{
options.AllowClrAccess = true;
options.ConfigureEngine(engine =>
{
engine.SetValue("getStaticValue", () => StaticValueHolder.Value);
});
});
elsa.UseAlterations(alterations =>
{
alterations.UseEntityFrameworkCore(ef => ef.UsePostgreSql(dbConnectionString));
});
elsa.UseHttp(http =>
{
http.UseCache();
});
};
}
builder.ConfigureTestServices(services =>
{
services
.AddSingleton<SignalManager>()
.AddScoped<WorkflowEvents>()
.AddScoped<WorkflowDefinitionEvents>()
.AddSingleton<TriggerChangeTokenSignalEvents>()
.AddScoped<IWorkflowMaterializer, TestWorkflowMaterializer>()
.AddNotificationHandlersFrom<WorkflowServer>()
.AddWorkflowDefinitionProvider<TestWorkflowProvider>()
.AddNotificationHandlersFrom<WorkflowEventHandlers>()
.Decorate<IChangeTokenSignaler, EventPublishingChangeTokenSignaler>()
;
});
}
protected override void ConfigureClient(HttpClient client)
{
client.DefaultRequestHeaders.Authorization = new("ApiKey", AdminApiKeyProvider.DefaultApiKey);
}
}
@@ -0,0 +1,30 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.ComponentTests.WorkflowProviders;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Runtime;
namespace Elsa.Workflows.ComponentTests.Materializers;
/// <summary>
/// A workflow materializer that deserializes workflows created from <see cref="TestWorkflowProvider"/>.
/// </summary>
public class TestWorkflowMaterializer(IEnumerable<IWorkflowsProvider> workflowProviders) : IWorkflowMaterializer
{
/// <summary>
/// The name of the materializer.
/// </summary>
public const string MaterializerName = "Test";
/// <inheritdoc />
public string Name => MaterializerName;
/// <inheritdoc />
public ValueTask<Workflow> MaterializeAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default)
{
var testProvider = (TestWorkflowProvider)workflowProviders.Single(x => x is TestWorkflowProvider);
var materializedWorkflow = testProvider.MaterializedWorkflows.First(x => x.Workflow.Identity.Id == definition.Id);
return ValueTask.FromResult(materializedWorkflow.Workflow);
}
}
@@ -0,0 +1,14 @@
using Elsa.Workflows.Runtime;
namespace Elsa.Workflows.ComponentTests.WorkflowProviders;
public class TestWorkflowProvider : IWorkflowsProvider
{
public ICollection<MaterializedWorkflow> MaterializedWorkflows { get; set; } = new List<MaterializedWorkflow>();
public string Name => "Test";
public ValueTask<IEnumerable<MaterializedWorkflow>> GetWorkflowsAsync(CancellationToken cancellationToken = default)
{
return new(MaterializedWorkflows);
}
}