Add project files.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
+17
@@ -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;
|
||||
}
|
||||
}
|
||||
+26
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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);
|
||||
}
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user