From 319f24a347d69f00c660bec865569559802b61f7 Mon Sep 17 00:00:00 2001 From: Marvin Mall Date: Mon, 2 Mar 2026 18:35:07 +0100 Subject: [PATCH 1/6] Create new projects for Elsa.FakeData module --- Elsa.Extensions.sln | 36 +++++++++++++++++++ .../Elsa.FakeData/Elsa.FakeData.csproj | 24 +++++++++++++ .../fakedata/Elsa.FakeData/FodyWeavers.xml | 1 + .../Elsa.FakeData.UnitTests.csproj | 11 ++++++ 4 files changed, 72 insertions(+) create mode 100644 src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj create mode 100644 src/modules/fakedata/Elsa.FakeData/FodyWeavers.xml create mode 100644 test/modules/fakedata/Elsa.FakeData.UnitTests/Elsa.FakeData.UnitTests.csproj diff --git a/Elsa.Extensions.sln b/Elsa.Extensions.sln index 7a22cadf..80bfebc4 100644 --- a/Elsa.Extensions.sln +++ b/Elsa.Extensions.sln @@ -297,6 +297,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Scheduling.Quartz.Unit EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Scheduling.Quartz.ComponentTests", "test\modules\scheduling\Elsa.Scheduling.Quartz.ComponentTests\Elsa.Scheduling.Quartz.ComponentTests.csproj", "{50798373-8A89-4159-BFD9-830D259538D6}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fakedata", "fakedata", "{3A829F79-B19C-4100-929E-E78B86001D00}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.FakeData", "src\modules\fakedata\Elsa.FakeData\Elsa.FakeData.csproj", "{5501314E-52CF-4D02-86C0-180C4F4470F2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fakedata", "fakedata", "{DCDBD383-FB19-4174-B51D-968613BEEB35}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.FakeData.UnitTests", "test\modules\fakedata\Elsa.FakeData.UnitTests\Elsa.FakeData.UnitTests.csproj", "{2F9BD705-112C-44FB-81BD-48515C308CEF}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "agents", "agents", "{0125BAC5-45B7-4E9B-8A2B-55F075BF537A}" ProjectSection(SolutionItems) = preProject .github\agents\release-notes.agent.md = .github\agents\release-notes.agent.md @@ -1456,6 +1464,30 @@ Global {50798373-8A89-4159-BFD9-830D259538D6}.Release|x64.Build.0 = Release|Any CPU {50798373-8A89-4159-BFD9-830D259538D6}.Release|x86.ActiveCfg = Release|Any CPU {50798373-8A89-4159-BFD9-830D259538D6}.Release|x86.Build.0 = Release|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Debug|x64.Build.0 = Debug|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Debug|x86.Build.0 = Debug|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Release|Any CPU.Build.0 = Release|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Release|x64.ActiveCfg = Release|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Release|x64.Build.0 = Release|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Release|x86.ActiveCfg = Release|Any CPU + {5501314E-52CF-4D02-86C0-180C4F4470F2}.Release|x86.Build.0 = Release|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Debug|x64.ActiveCfg = Debug|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Debug|x64.Build.0 = Debug|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Debug|x86.Build.0 = Debug|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Release|Any CPU.Build.0 = Release|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Release|x64.ActiveCfg = Release|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Release|x64.Build.0 = Release|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Release|x86.ActiveCfg = Release|Any CPU + {2F9BD705-112C-44FB-81BD-48515C308CEF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1587,6 +1619,10 @@ Global {65B9F528-568B-4E0E-894E-F22E0B7C64BB} = {6E46B8AD-FA47-474F-B959-0B530771C28C} {50798373-8A89-4159-BFD9-830D259538D6} = {6E46B8AD-FA47-474F-B959-0B530771C28C} {0125BAC5-45B7-4E9B-8A2B-55F075BF537A} = {D0212324-351E-4CA6-95EE-27754B5367CC} + {3A829F79-B19C-4100-929E-E78B86001D00} = {30CF0330-4B09-4784-B499-46BED303810B} + {5501314E-52CF-4D02-86C0-180C4F4470F2} = {3A829F79-B19C-4100-929E-E78B86001D00} + {DCDBD383-FB19-4174-B51D-968613BEEB35} = {3DDE6F89-531C-47F8-9CD7-7A4E6984FA48} + {2F9BD705-112C-44FB-81BD-48515C308CEF} = {DCDBD383-FB19-4174-B51D-968613BEEB35} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {11A771DA-B728-445E-8A88-AE1C84C3B3A6} diff --git a/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj b/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj new file mode 100644 index 00000000..def38a02 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj @@ -0,0 +1,24 @@ + + + + + Provides activities to generate fake data for testing and benchmarking purposes. + + elsa module fakedata testing benchmarking + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/fakedata/Elsa.FakeData/FodyWeavers.xml b/src/modules/fakedata/Elsa.FakeData/FodyWeavers.xml new file mode 100644 index 00000000..5e025acb --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/FodyWeavers.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Elsa.FakeData.UnitTests.csproj b/test/modules/fakedata/Elsa.FakeData.UnitTests/Elsa.FakeData.UnitTests.csproj new file mode 100644 index 00000000..fbe3b380 --- /dev/null +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Elsa.FakeData.UnitTests.csproj @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file From bb073f8ead841ba04587893f540455f010b8872b Mon Sep 17 00:00:00 2001 From: Marvin Mall Date: Mon, 2 Mar 2026 21:34:08 +0100 Subject: [PATCH 2/6] Add new activities to generate fake data including tests --- .../Activities/GenerateFakeDataActivity.cs | 69 ++++++++++ .../Activities/GenerateFakeOrders.cs | 43 +++++++ .../Activities/GenerateFakePersons.cs | 45 +++++++ .../Activities/GenerateFakeProducts.cs | 40 ++++++ .../Activities/GenerateFakeUsers.cs | 40 ++++++ .../Elsa.FakeData/Elsa.FakeData.csproj | 5 +- .../Extensions/ModuleExtensions.cs | 19 +++ .../Extensions/ObjectExtensions.cs | 9 ++ .../Elsa.FakeData/Features/FakeDataFeature.cs | 22 ++++ .../Elsa.FakeData/Models/FakeOrder.cs | 18 +++ .../Elsa.FakeData/Models/FakePerson.cs | 21 ++++ .../Elsa.FakeData/Models/FakeProduct.cs | 16 +++ .../fakedata/Elsa.FakeData/Models/FakeUser.cs | 16 +++ .../Activities/GenerateFakeOrdersTests.cs | 116 +++++++++++++++++ .../Activities/GenerateFakePersonsTests.cs | 119 ++++++++++++++++++ .../Activities/GenerateFakeProductsTests.cs | 114 +++++++++++++++++ .../Activities/GenerateFakeUsersTests.cs | 115 +++++++++++++++++ 17 files changed, 825 insertions(+), 2 deletions(-) create mode 100644 src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeOrders.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeProducts.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Extensions/ModuleExtensions.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Extensions/ObjectExtensions.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Features/FakeDataFeature.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Models/FakeOrder.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Models/FakePerson.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Models/FakeProduct.cs create mode 100644 src/modules/fakedata/Elsa.FakeData/Models/FakeUser.cs create mode 100644 test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeOrdersTests.cs create mode 100644 test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakePersonsTests.cs create mode 100644 test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeProductsTests.cs create mode 100644 test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeUsersTests.cs diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs new file mode 100644 index 00000000..0f6ba7c0 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs @@ -0,0 +1,69 @@ +using Bogus; +using Elsa.Extensions; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; + +namespace Elsa.FakeData.Activities; + +/// +/// Provides a base implementation for activities that generate collections of fake data +/// using the Bogus library. +/// +public abstract class GenerateFakeDataActivity : CodeActivity> where T : class +{ + /// + protected GenerateFakeDataActivity(string? source = null, int? line = null) : base(source, line) + { + } + + /// + /// The number of fake records to generate. + /// + [Input( + DisplayName = "Number of records", + Description = "The number of fake records to generate. Default: 10")] + public Input Count { get; set; } = new(10); + + /// + /// The locale to use when generating fake data (e.g. 'en', 'de', 'fr', 'nl'). + /// See the Bogus locale list for all supported values. + /// + [Input( + DisplayName = "Locale", + Description = "The locale to use when generating fake data (e.g. 'en', 'de', 'fr', 'nl'). Default: en")] + public Input Locale { get; set; } = new("en"); + + /// + /// A seed value for the random number generator. + /// Providing a seed will ensure that the same fake data is generated on each run, which can be useful for deterministic testing. + /// If not provided, a random seed will be used, resulting in different data on each execution. + /// + [Input( + DisplayName = "Seed", + Description = "With a seed, each run will produce the identical data (for deterministic tests).")] + public Input Seed { get; set; } = new((int?)null); + + /// + protected override ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var count = Count.Get(context); + var locale = Locale.GetOrDefault(context) ?? "en"; + var seed = Seed.GetOrDefault(context, () => null); + + var faker = CreateFaker(locale, seed); + var items = faker.Generate(count); + + context.Set(Result, items); + + return ValueTask.CompletedTask; + } + + /// + /// Creates a configured instance for the given locale. + /// + /// The locale string (e.g. "en", "de", "fr"). + /// An optional seed value for deterministic data generation. + /// A configured instance. + protected abstract Faker CreateFaker(string locale, int? seed); +} diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeOrders.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeOrders.cs new file mode 100644 index 00000000..3d4ac861 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeOrders.cs @@ -0,0 +1,43 @@ +using System.Globalization; +using System.Runtime.CompilerServices; +using Bogus; +using Elsa.FakeData.Extensions; +using Elsa.FakeData.Models; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; + +namespace Elsa.FakeData.Activities; + +/// +/// Generates a collection of fake records. +/// +[Activity( + Namespace = "Elsa", + Category = "Fake Data", + DisplayName = "Generate Orders", + Description = "Generates a collection of fake order records.", + Kind = ActivityKind.Task)] +public class GenerateFakeOrders : GenerateFakeDataActivity +{ + /// + public GenerateFakeOrders([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) + { + } + + /// + protected override Faker CreateFaker(string locale, int? seed) + { + return new Faker(locale) + .When(seed is not null, f => f.UseSeed(seed!.Value)) + .RuleFor(o => o.Id, f => f.Random.Guid()) + .RuleFor(o => o.OrderNumber, f => f.Random.Number(100_000, 1_000_000_000).ToString(CultureInfo.InvariantCulture)) + .RuleFor(o => o.OrderDate, f => f.Date.Past()) + .RuleFor(o => o.Status, f => f.PickRandom("Pending", "Processing", "Shipped", "Delivered", "Cancelled")) + .RuleFor(o => o.CustomerName, f => f.Person.FullName) + .RuleFor(o => o.CustomerEmail, f => f.Person.Email) + .RuleFor(o => o.ShippingAddress, f => f.Address.FullAddress()) + .RuleFor(o => o.SubTotal, f => f.Finance.Amount(10, 500)) + .RuleFor(o => o.Tax, (_, o) => Math.Round(o.SubTotal * 0.1m, 2)) + .RuleFor(o => o.Total, (_, o) => o.SubTotal + o.Tax); + } +} diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs new file mode 100644 index 00000000..ef503e94 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs @@ -0,0 +1,45 @@ +using System.Runtime.CompilerServices; +using Bogus; +using Elsa.FakeData.Extensions; +using Elsa.FakeData.Models; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; + +namespace Elsa.FakeData.Activities; + +/// +/// Generates a collection of fake records. +/// +[Activity( + Namespace = "Elsa", + Category = "Fake Data", + DisplayName = "Generate Persons", + Description = "Generates a collection of fake person records.", + Kind = ActivityKind.Task)] +public class GenerateFakePersons : GenerateFakeDataActivity +{ + /// + public GenerateFakePersons([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) + { + } + + /// + protected override Faker CreateFaker(string locale, int? seed) + { + return new Faker(locale) + .When(seed is not null, f => f.UseSeed(seed!.Value)) + .RuleFor(p => p.Id, f => f.Random.Guid()) + .RuleFor(p => p.FirstName, f => f.Person.FirstName) + .RuleFor(p => p.LastName, f => f.Person.LastName) + .RuleFor(p => p.FullName, (f) => f.Person.FullName) + .RuleFor(p => p.Email, (f, p) => f.Person.Email) + .RuleFor(p => p.Phone, f => f.Phone.PhoneNumber()) + .RuleFor(p => p.DateOfBirth, f => f.Date.Past(80, DateTime.Now.AddYears(-18))) + .RuleFor(p => p.Gender, f => f.PickRandom("Male", "Female", "Non-binary")) + .RuleFor(p => p.Address, f => f.Address.StreetAddress()) + .RuleFor(p => p.City, f => f.Address.City()) + .RuleFor(p => p.State, f => f.Address.State()) + .RuleFor(p => p.ZipCode, f => f.Address.ZipCode()) + .RuleFor(p => p.Country, f => f.Address.Country()); + } +} diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeProducts.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeProducts.cs new file mode 100644 index 00000000..b69eddb2 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeProducts.cs @@ -0,0 +1,40 @@ +using System.Runtime.CompilerServices; +using Bogus; +using Elsa.FakeData.Extensions; +using Elsa.FakeData.Models; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; + +namespace Elsa.FakeData.Activities; + +/// +/// Generates a collection of fake records. +/// +[Activity( + Namespace = "Elsa", + Category = "Fake Data", + DisplayName = "Generate Products", + Description = "Generates a collection of fake product records.", + Kind = ActivityKind.Task)] +public class GenerateFakeProducts : GenerateFakeDataActivity +{ + /// + public GenerateFakeProducts([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) + { + } + + /// + protected override Faker CreateFaker(string locale, int? seed) + { + return new Faker(locale) + .When(seed is not null, f => f.UseSeed(seed!.Value)) + .RuleFor(p => p.Id, f => f.Random.Guid()) + .RuleFor(p => p.Name, f => f.Commerce.ProductName()) + .RuleFor(p => p.Description, f => f.Commerce.ProductDescription()) + .RuleFor(p => p.Price, f => f.Finance.Amount(1, 1000)) + .RuleFor(p => p.Category, f => f.Commerce.Categories(1)[0]) + .RuleFor(p => p.Department, f => f.Commerce.Department()) + .RuleFor(p => p.Rating, f => f.Random.Number(1, 5)) + .RuleFor(p => p.Stock, f => f.Random.Int(0, 1000)); + } +} diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs new file mode 100644 index 00000000..8f352404 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs @@ -0,0 +1,40 @@ +using System.Runtime.CompilerServices; +using Bogus; +using Elsa.FakeData.Extensions; +using Elsa.FakeData.Models; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; + +namespace Elsa.FakeData.Activities; + +/// +/// Generates a collection of fake records. +/// +[Activity( + Namespace = "Elsa", + Category = "Fake Data", + DisplayName = "Generate Users", + Description = "Generates a collection of fake user records.", + Kind = ActivityKind.Task)] +public class GenerateFakeUsers : GenerateFakeDataActivity +{ + /// + public GenerateFakeUsers([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) + { + } + + /// + protected override Faker CreateFaker(string locale, int? seed) + { + return new Faker(locale) + .When(seed is not null, f => f.UseSeed(seed!.Value)) + .RuleFor(u => u.Id, f => f.Random.Guid()) + .RuleFor(u => u.FirstName, f => f.Person.FirstName) + .RuleFor(u => u.LastName, f => f.Person.LastName) + .RuleFor(u => u.Username, (f, u) => f.Person.UserName) + .RuleFor(u => u.Email, (f, u) => f.Person.Email) + .RuleFor(u => u.Avatar, f => f.Person.Avatar) + .RuleFor(u => u.Roles, f => [f.PickRandom("Admin", "User", "Moderator", "Guest")]) + .RuleFor(u => u.CreatedAt, f => f.Date.Past(5)); + } +} diff --git a/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj b/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj index def38a02..9bcdb4fe 100644 --- a/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj +++ b/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj @@ -1,13 +1,14 @@ - + Provides activities to generate fake data for testing and benchmarking purposes. - elsa module fakedata testing benchmarking + elsa module fakedata testing benchmarking bogus + diff --git a/src/modules/fakedata/Elsa.FakeData/Extensions/ModuleExtensions.cs b/src/modules/fakedata/Elsa.FakeData/Extensions/ModuleExtensions.cs new file mode 100644 index 00000000..d577690b --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Extensions/ModuleExtensions.cs @@ -0,0 +1,19 @@ +using Elsa.Extensions; +using Elsa.FakeData.Features; +using Elsa.Features.Services; + +namespace Elsa.FakeData.Extensions; + +/// +/// Provides extension methods for configuring fake data services. +/// +public static class ModuleExtensions +{ + /// + /// Installs the FakeData module. + /// + public static IModule UseFakeData(this IModule module, Action? configure = null) + { + return module.Use(configure); + } +} \ No newline at end of file diff --git a/src/modules/fakedata/Elsa.FakeData/Extensions/ObjectExtensions.cs b/src/modules/fakedata/Elsa.FakeData/Extensions/ObjectExtensions.cs new file mode 100644 index 00000000..a96eb6e3 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Extensions/ObjectExtensions.cs @@ -0,0 +1,9 @@ +namespace Elsa.FakeData.Extensions; + +internal static class ObjectExtensions +{ + public static T When(this T obj, bool condition, Func action) + { + return condition ? action(obj) : obj; + } +} diff --git a/src/modules/fakedata/Elsa.FakeData/Features/FakeDataFeature.cs b/src/modules/fakedata/Elsa.FakeData/Features/FakeDataFeature.cs new file mode 100644 index 00000000..5e863d43 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Features/FakeDataFeature.cs @@ -0,0 +1,22 @@ +using Elsa.Extensions; +using Elsa.Features.Abstractions; +using Elsa.Features.Services; + +namespace Elsa.FakeData.Features; + +/// +/// A feature that provides activities to generate fake data for testing and benchmarking purposes. +/// +public class FakeDataFeature : FeatureBase +{ + /// + public FakeDataFeature(IModule module) : base(module) + { + } + + /// + public override void Configure() + { + Module.AddActivitiesFrom(); + } +} \ No newline at end of file diff --git a/src/modules/fakedata/Elsa.FakeData/Models/FakeOrder.cs b/src/modules/fakedata/Elsa.FakeData/Models/FakeOrder.cs new file mode 100644 index 00000000..9aaa208c --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Models/FakeOrder.cs @@ -0,0 +1,18 @@ +namespace Elsa.FakeData.Models; + +/// +/// Represents a fake order record. +/// +public class FakeOrder +{ + public required Guid Id { get; set; } + public required string OrderNumber { get; set; } + public required DateTime OrderDate { get; set; } + public required string Status { get; set; } + public required string CustomerName { get; set; } + public required string CustomerEmail { get; set; } + public required string ShippingAddress { get; set; } + public required decimal SubTotal { get; set; } + public required decimal Tax { get; set; } + public required decimal Total { get; set; } +} \ No newline at end of file diff --git a/src/modules/fakedata/Elsa.FakeData/Models/FakePerson.cs b/src/modules/fakedata/Elsa.FakeData/Models/FakePerson.cs new file mode 100644 index 00000000..0c186938 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Models/FakePerson.cs @@ -0,0 +1,21 @@ +namespace Elsa.FakeData.Models; + +/// +/// Represents a fake person record. +/// +public class FakePerson +{ + public required Guid Id { get; set; } + public required string FirstName { get; set; } + public required string LastName { get; set; } + public required string FullName { get; set; } + public required string Email { get; set; } + public required string Phone { get; set; } + public required DateTime DateOfBirth { get; set; } + public required string Gender { get; set; } + public required string Address { get; set; } + public required string City { get; set; } + public required string State { get; set; } + public required string ZipCode { get; set; } + public required string Country { get; set; } +} diff --git a/src/modules/fakedata/Elsa.FakeData/Models/FakeProduct.cs b/src/modules/fakedata/Elsa.FakeData/Models/FakeProduct.cs new file mode 100644 index 00000000..ee506a95 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Models/FakeProduct.cs @@ -0,0 +1,16 @@ +namespace Elsa.FakeData.Models; + +/// +/// Represents a fake product record. +/// +public class FakeProduct +{ + public required Guid Id { get; set; } + public required string Name { get; set; } + public required string Description { get; set; } + public required decimal Price { get; set; } + public required string Category { get; set; } + public required string Department { get; set; } + public required int Rating { get; set; } + public required int Stock { get; set; } +} diff --git a/src/modules/fakedata/Elsa.FakeData/Models/FakeUser.cs b/src/modules/fakedata/Elsa.FakeData/Models/FakeUser.cs new file mode 100644 index 00000000..21517926 --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/Models/FakeUser.cs @@ -0,0 +1,16 @@ +namespace Elsa.FakeData.Models; + +/// +/// Represents a fake user account record. +/// +public class FakeUser +{ + public required Guid Id { get; set; } + public required string Username { get; set; } + public required string Email { get; set; } + public required string FirstName { get; set; } + public required string LastName { get; set; } + public required string Avatar { get; set; } + public required string[] Roles { get; set; } + public required DateTime CreatedAt { get; set; } +} diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeOrdersTests.cs b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeOrdersTests.cs new file mode 100644 index 00000000..a8927737 --- /dev/null +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeOrdersTests.cs @@ -0,0 +1,116 @@ +using Elsa.FakeData.Activities; +using Elsa.FakeData.Models; +using Elsa.Testing.Shared; +using Elsa.Workflows.Models; + +namespace Elsa.FakeData.UnitTests.Activities; + +public class GenerateFakeOrdersTests +{ + [Fact] + public async Task Execute_WithDefaultCount_Generates10Records() + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeOrders { Result = result }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + + // Assert + var orders = context.Get(result); + Assert.NotNull(orders); + Assert.Equal(10, orders.Count); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(25)] + public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeOrders { Count = new Input(count), Result = result }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + + // Assert + var orders = context.Get(result); + Assert.NotNull(orders); + Assert.Equal(count, orders.Count); + } + + [Fact] + public async Task Execute_WithSeed_ProducesDeterministicData() + { + const int seed = 42; + const int count = 5; + + // Arrange + var result1 = new Output>(); + var activity1 = new GenerateFakeOrders + { + Count = new Input(count), + Seed = new Input(seed), + Result = result1, + }; + + var result2 = new Output>(); + var activity2 = new GenerateFakeOrders + { + Count = new Input(count), + Seed = new Input(seed), + Result = result2, + }; + + // Act + var context1 = await new ActivityTestFixture(activity1).ExecuteAsync(); + var orders1 = context1.Get(result1)!.ToList(); + + var context2 = await new ActivityTestFixture(activity2).ExecuteAsync(); + var orders2 = context2.Get(result2)!.ToList(); + + // Assert + Assert.Equal(orders1.Count, orders2.Count); + for (var i = 0; i < orders1.Count; i++) + { + Assert.Equal(orders1[i].Id, orders2[i].Id); + Assert.Equal(orders1[i].OrderNumber, orders2[i].OrderNumber); + Assert.Equal(orders1[i].CustomerEmail, orders2[i].CustomerEmail); + } + } + + [Fact] + public async Task Execute_GeneratesOrders_WithAllPropertiesPopulated() + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeOrders + { + Count = new Input(5), + Seed = new Input(1), + Result = result, + }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + var orders = context.Get(result)!; + + // Assert + foreach (var order in orders) + { + Assert.NotEqual(Guid.Empty, order.Id); + Assert.False(string.IsNullOrWhiteSpace(order.OrderNumber)); + Assert.NotEqual(default, order.OrderDate); + Assert.False(string.IsNullOrWhiteSpace(order.Status)); + Assert.False(string.IsNullOrWhiteSpace(order.CustomerName)); + Assert.False(string.IsNullOrWhiteSpace(order.CustomerEmail)); + Assert.False(string.IsNullOrWhiteSpace(order.ShippingAddress)); + Assert.True(order.SubTotal > 0); + Assert.True(order.Tax >= 0); + Assert.True(order.Total > 0); + } + } +} diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakePersonsTests.cs b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakePersonsTests.cs new file mode 100644 index 00000000..8b570a8e --- /dev/null +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakePersonsTests.cs @@ -0,0 +1,119 @@ +using Elsa.FakeData.Activities; +using Elsa.FakeData.Models; +using Elsa.Testing.Shared; +using Elsa.Workflows.Models; + +namespace Elsa.FakeData.UnitTests.Activities; + +public class GenerateFakePersonsTests +{ + [Fact] + public async Task Execute_WithDefaultCount_Generates10Records() + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakePersons { Result = result }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + + // Assert + var persons = context.Get(result); + Assert.NotNull(persons); + Assert.Equal(10, persons.Count); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(25)] + public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakePersons { Count = new Input(count), Result = result }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + + // Assert + var persons = context.Get(result); + Assert.NotNull(persons); + Assert.Equal(count, persons.Count); + } + + [Fact] + public async Task Execute_WithSeed_ProducesDeterministicData() + { + const int seed = 42; + const int count = 5; + + // Arrange + var result1 = new Output>(); + var activity1 = new GenerateFakePersons + { + Count = new Input(count), + Seed = new Input(seed), + Result = result1, + }; + + var result2 = new Output>(); + var activity2 = new GenerateFakePersons + { + Count = new Input(count), + Seed = new Input(seed), + Result = result2, + }; + + // Act + var context1 = await new ActivityTestFixture(activity1).ExecuteAsync(); + var persons1 = context1.Get(result1)!.ToList(); + + var context2 = await new ActivityTestFixture(activity2).ExecuteAsync(); + var persons2 = context2.Get(result2)!.ToList(); + + // Assert + Assert.Equal(persons1.Count, persons2.Count); + for (var i = 0; i < persons1.Count; i++) + { + Assert.Equal(persons1[i].Id, persons2[i].Id); + Assert.Equal(persons1[i].FullName, persons2[i].FullName); + Assert.Equal(persons1[i].Email, persons2[i].Email); + } + } + + [Fact] + public async Task Execute_GeneratesPersons_WithAllPropertiesPopulated() + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakePersons + { + Count = new Input(5), + Seed = new Input(1), + Result = result, + }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + var persons = context.Get(result)!; + + // Assert + foreach (var person in persons) + { + Assert.NotEqual(Guid.Empty, person.Id); + Assert.False(string.IsNullOrWhiteSpace(person.FirstName)); + Assert.False(string.IsNullOrWhiteSpace(person.LastName)); + Assert.False(string.IsNullOrWhiteSpace(person.FullName)); + Assert.False(string.IsNullOrWhiteSpace(person.Email)); + Assert.False(string.IsNullOrWhiteSpace(person.Phone)); + Assert.NotEqual(default, person.DateOfBirth); + Assert.False(string.IsNullOrWhiteSpace(person.Gender)); + Assert.False(string.IsNullOrWhiteSpace(person.Address)); + Assert.False(string.IsNullOrWhiteSpace(person.City)); + Assert.False(string.IsNullOrWhiteSpace(person.State)); + Assert.False(string.IsNullOrWhiteSpace(person.ZipCode)); + Assert.False(string.IsNullOrWhiteSpace(person.Country)); + } + } +} diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeProductsTests.cs b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeProductsTests.cs new file mode 100644 index 00000000..8ea47ef0 --- /dev/null +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeProductsTests.cs @@ -0,0 +1,114 @@ +using Elsa.FakeData.Activities; +using Elsa.FakeData.Models; +using Elsa.Testing.Shared; +using Elsa.Workflows.Models; + +namespace Elsa.FakeData.UnitTests.Activities; + +public class GenerateFakeProductsTests +{ + [Fact] + public async Task Execute_WithDefaultCount_Generates10Records() + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeProducts { Result = result }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + + // Assert + var products = context.Get(result); + Assert.NotNull(products); + Assert.Equal(10, products.Count); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(25)] + public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeProducts { Count = new Input(count), Result = result }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + + // Assert + var products = context.Get(result); + Assert.NotNull(products); + Assert.Equal(count, products.Count); + } + + [Fact] + public async Task Execute_WithSeed_ProducesDeterministicData() + { + const int seed = 42; + const int count = 5; + + // Arrange + var result1 = new Output>(); + var activity1 = new GenerateFakeProducts + { + Count = new Input(count), + Seed = new Input(seed), + Result = result1, + }; + + var result2 = new Output>(); + var activity2 = new GenerateFakeProducts + { + Count = new Input(count), + Seed = new Input(seed), + Result = result2, + }; + + // Act + var context1 = await new ActivityTestFixture(activity1).ExecuteAsync(); + var products1 = context1.Get(result1)!.ToList(); + + var context2 = await new ActivityTestFixture(activity2).ExecuteAsync(); + var products2 = context2.Get(result2)!.ToList(); + + // Assert + Assert.Equal(products1.Count, products2.Count); + for (var i = 0; i < products1.Count; i++) + { + Assert.Equal(products1[i].Id, products2[i].Id); + Assert.Equal(products1[i].Name, products2[i].Name); + Assert.Equal(products1[i].Price, products2[i].Price); + } + } + + [Fact] + public async Task Execute_GeneratesProducts_WithAllPropertiesPopulated() + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeProducts + { + Count = new Input(5), + Seed = new Input(1), + Result = result, + }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + var products = context.Get(result)!; + + // Assert + foreach (var product in products) + { + Assert.NotEqual(Guid.Empty, product.Id); + Assert.False(string.IsNullOrWhiteSpace(product.Name)); + Assert.False(string.IsNullOrWhiteSpace(product.Description)); + Assert.True(product.Price > 0); + Assert.False(string.IsNullOrWhiteSpace(product.Category)); + Assert.False(string.IsNullOrWhiteSpace(product.Department)); + Assert.InRange(product.Rating, 1, 5); + Assert.True(product.Stock >= 0); + } + } +} diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeUsersTests.cs b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeUsersTests.cs new file mode 100644 index 00000000..f33026e5 --- /dev/null +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeUsersTests.cs @@ -0,0 +1,115 @@ +using Elsa.FakeData.Activities; +using Elsa.FakeData.Models; +using Elsa.Testing.Shared; +using Elsa.Workflows.Models; + +namespace Elsa.FakeData.UnitTests.Activities; + +public class GenerateFakeUsersTests +{ + [Fact] + public async Task Execute_WithDefaultCount_Generates10Records() + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeUsers { Result = result }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + + // Assert + var users = context.Get(result); + Assert.NotNull(users); + Assert.Equal(10, users.Count); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(25)] + public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeUsers { Count = new Input(count), Result = result }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + + // Assert + var users = context.Get(result); + Assert.NotNull(users); + Assert.Equal(count, users.Count); + } + + [Fact] + public async Task Execute_WithSeed_ProducesDeterministicData() + { + const int seed = 42; + const int count = 5; + + // Arrange + var result1 = new Output>(); + var activity1 = new GenerateFakeUsers + { + Count = new Input(count), + Seed = new Input(seed), + Result = result1, + }; + + var result2 = new Output>(); + var activity2 = new GenerateFakeUsers + { + Count = new Input(count), + Seed = new Input(seed), + Result = result2, + }; + + // Act + var context1 = await new ActivityTestFixture(activity1).ExecuteAsync(); + var users1 = context1.Get(result1)!.ToList(); + + var context2 = await new ActivityTestFixture(activity2).ExecuteAsync(); + var users2 = context2.Get(result2)!.ToList(); + + // Assert + Assert.Equal(users1.Count, users2.Count); + for (var i = 0; i < users1.Count; i++) + { + Assert.Equal(users1[i].Id, users2[i].Id); + Assert.Equal(users1[i].Username, users2[i].Username); + Assert.Equal(users1[i].Email, users2[i].Email); + } + } + + [Fact] + public async Task Execute_GeneratesUsers_WithAllPropertiesPopulated() + { + // Arrange + var result = new Output>(); + var activity = new GenerateFakeUsers + { + Count = new Input(5), + Seed = new Input(1), + Result = result, + }; + + // Act + var context = await new ActivityTestFixture(activity).ExecuteAsync(); + var users = context.Get(result)!; + + // Assert + foreach (var user in users) + { + Assert.NotEqual(Guid.Empty, user.Id); + Assert.False(string.IsNullOrWhiteSpace(user.Username)); + Assert.False(string.IsNullOrWhiteSpace(user.Email)); + Assert.False(string.IsNullOrWhiteSpace(user.FirstName)); + Assert.False(string.IsNullOrWhiteSpace(user.LastName)); + Assert.False(string.IsNullOrWhiteSpace(user.Avatar)); + Assert.NotNull(user.Roles); + Assert.NotEmpty(user.Roles); + Assert.NotEqual(default, user.CreatedAt); + } + } +} From abeacd72174c31149e857829e04024c3371af2bc Mon Sep 17 00:00:00 2001 From: Marvin Mall Date: Mon, 2 Mar 2026 21:43:28 +0100 Subject: [PATCH 3/6] Add README for fake data module --- src/modules/fakedata/Elsa.FakeData/README.md | 163 +++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/modules/fakedata/Elsa.FakeData/README.md diff --git a/src/modules/fakedata/Elsa.FakeData/README.md b/src/modules/fakedata/Elsa.FakeData/README.md new file mode 100644 index 00000000..929b42fc --- /dev/null +++ b/src/modules/fakedata/Elsa.FakeData/README.md @@ -0,0 +1,163 @@ +# Fake Data Extension + +
+ 📖 Table of Contents +
    +
  1. Overview
  2. +
  3. Features
  4. +
  5. + Getting Started + +
  6. +
  7. + Configuration + +
  8. +
  9. Usage
  10. +
  11. Activities
  12. +
  13. Examples
  14. +
  15. Planned Features
  16. +
  17. Limitations
  18. +
  19. Troubleshooting
  20. +
  21. Notes & Comments
  22. +
+
+ +## 🧠 Overview + +This package extends [Elsa Workflows](https://github.com/elsa-workflows/elsa-core) with support for **fake data**. +It introduces custom activities that make it easy to generate some fake data for testing, prototyping, or any other purpose where you might need to generate random data within your workflows. + +## ✨ Key Features + +- Activities: `GenerateFakeOrders`, `GenerateFakePersons`, `GenerateFakeProducts` and `GenerateFakeUsers` +- Configurable output: specify the number of items to generate +- Deterministic if needed: option to set a seed for reproducible results + +--- + +## ⚡ Getting Started + +### 📋 Prerequisites + +- Elsa Workflows **3.6** or higher installed in your project. + +## 🛠 Installation + +The following NuGet packages are available for this extension: + +```bash +Elsa.FakeData +``` + +You can install the clients via NuGet: + +```bash +dotnet add package Elsa.FakeData +``` + +## ⚙️ Configuration + +### Program.cs + +Register the extension in your application startup: + +```csharp +using Elsa.Extensions; +using Elsa.FakeData.Extensions; + +services.AddElsa(elsa => +{ + elsa.UseFakeData(); +} +``` + +--- + +## 📌 Usage + +Once the extension is registered with your required implementations, the activities will be ready to use, either via code or [Elsa Studio](https://github.com/elsa-workflows/elsa-studio). + +## 🚀 Activities + +This extension comes with the following activities: + +### GenerateFakeOrders + +| Properties | Type | Description | Input/Output | Notes | +| ---------- | ---- | ----------- | --- | ----- | +| Count | int | The number of fake records to generate. | Input | Default: 10 | +| Locale | string | The locale to use when generating fake data (e.g. 'en', 'de', 'fr', 'nl') | Input | Default: en | +| Seed | int? | With a seed, each run will produce the identical data (for deterministic tests). | Input | - | + +### GenerateFakePersons + +| Properties | Type | Description | Input/Output | Notes | +| ---------- | ---- | ----------- | --- | ----- | +| Count | int | The number of fake records to generate. | Input | Default: 10 | +| Locale | string | The locale to use when generating fake data (e.g. 'en', 'de', 'fr', 'nl') | Input | Default: en | +| Seed | int? | With a seed, each run will produce the identical data (for deterministic tests). | Input | - | + +### GenerateFakeProducts + +| Properties | Type | Description | Input/Output | Notes | +| ---------- | ---- | ----------- | --- | ----- | +| Count | int | The number of fake records to generate. | Input | Default: 10 | +| Locale | string | The locale to use when generating fake data (e.g. 'en', 'de', 'fr', 'nl') | Input | Default: en | +| Seed | int? | With a seed, each run will produce the identical data (for deterministic tests). | Input | - | + +### GenerateFakeUsers + +| Properties | Type | Description | Input/Output | Notes | +| ---------- | ---- | ----------- | --- | ----- | +| Count | int | The number of fake records to generate. | Input | Default: 10 | +| Locale | string | The locale to use when generating fake data (e.g. 'en', 'de', 'fr', 'nl') | Input | Default: en | +| Seed | int? | With a seed, each run will produce the identical data (for deterministic tests). | Input | - | + + +## 🧪 Examples + +### Example of your feature + +```csharp +new GenerateFakeOrders +{ + Count = new Input(100), + Locale = new Input("de"), + Seed = new Input(42), +} +``` + +--- + +## 🚧 Limitations + +- Supports only a predefined set of fake data types (orders, persons, products, users). +- Locale support is limited to a few languages (en, de, fr, nl), based on the `Bogus` library. + +--- + +## 🆘 Troubleshooting + +### Common Errors + +No issues known at this time. If you encounter any problems, please open an issue with details about the error and your environment. + +--- + +## 🗺️ Planned Features + +No further generators planned at this time, but if you have suggestions for additional fake data types or features, please let us know! + +--- + +## 🗒️ Notes & Comments + +This extension was developed to add fake data functionality to Elsa Workflows. +If you have ideas for improvement, encounter issues, or want to share how you're using it, feel free to open an issue or start a discussion! \ No newline at end of file From c07e1e3b1e6e0abdbecfc63428ced89cf57212c0 Mon Sep 17 00:00:00 2001 From: Marvin Mall Date: Mon, 2 Mar 2026 21:45:28 +0100 Subject: [PATCH 4/6] Add fake data module to workbench projects --- src/workbench/Elsa.Server.Web/Elsa.Server.Web.csproj | 1 + src/workbench/Elsa.Server.Web/Program.cs | 2 ++ .../Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj | 1 + src/workbench/Elsa.ServerAndStudio.Web/Program.cs | 2 ++ 4 files changed, 6 insertions(+) diff --git a/src/workbench/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/workbench/Elsa.Server.Web/Elsa.Server.Web.csproj index 2e37fc91..f6724f80 100644 --- a/src/workbench/Elsa.Server.Web/Elsa.Server.Web.csproj +++ b/src/workbench/Elsa.Server.Web/Elsa.Server.Web.csproj @@ -60,6 +60,7 @@ + diff --git a/src/workbench/Elsa.Server.Web/Program.cs b/src/workbench/Elsa.Server.Web/Program.cs index af9a2e30..3ef1ffc5 100644 --- a/src/workbench/Elsa.Server.Web/Program.cs +++ b/src/workbench/Elsa.Server.Web/Program.cs @@ -8,6 +8,7 @@ using Elsa.DropIns.Extensions; using Elsa.Expressions.Helpers; using Elsa.Extensions; +using Elsa.FakeData.Extensions; using Elsa.Features.Services; using Elsa.Identity.Multitenancy; using Elsa.OpenTelemetry.Middleware; @@ -487,6 +488,7 @@ alterations.UseMassTransitDispatcher(); } }) + .UseFakeData() .UseOpenTelemetry(otel => otel.UseNewRootActivityForRemoteParent = true) .UseWorkflowContexts(); diff --git a/src/workbench/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj b/src/workbench/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj index 1d9b5aa4..47f601ee 100644 --- a/src/workbench/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj +++ b/src/workbench/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj @@ -49,6 +49,7 @@ + diff --git a/src/workbench/Elsa.ServerAndStudio.Web/Program.cs b/src/workbench/Elsa.ServerAndStudio.Web/Program.cs index be6a4cc9..b95cd80b 100644 --- a/src/workbench/Elsa.ServerAndStudio.Web/Program.cs +++ b/src/workbench/Elsa.ServerAndStudio.Web/Program.cs @@ -1,6 +1,7 @@ using Elsa.Agents; using Elsa.Expressions.JavaScript.Libraries.Extensions; using Elsa.Extensions; +using Elsa.FakeData.Extensions; using Elsa.Persistence.EFCore.Extensions; using Elsa.Persistence.EFCore.Modules.Management; using Elsa.Persistence.EFCore.Modules.Runtime; @@ -139,6 +140,7 @@ .UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options)) .UseWebhooks(webhooks => webhooks.ConfigureSinks = options => builder.Configuration.GetSection("Webhooks:Sinks").Bind(options)) .UseWorkflowsApi() + .UseFakeData() .AddActivitiesFrom() .AddWorkflowsFrom(); From 6d57981a6b3439ace831836f21c7596ba0366230 Mon Sep 17 00:00:00 2001 From: Marvin Mall Date: Mon, 2 Mar 2026 22:14:15 +0100 Subject: [PATCH 5/6] Produce array as output, not ICollection --- .../Activities/GenerateFakeDataActivity.cs | 4 ++-- .../Activities/GenerateFakeOrdersTests.cs | 16 ++++++++-------- .../Activities/GenerateFakePersonsTests.cs | 14 +++++++------- .../Activities/GenerateFakeProductsTests.cs | 14 +++++++------- .../Activities/GenerateFakeUsersTests.cs | 14 +++++++------- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs index 0f6ba7c0..f561fda5 100644 --- a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs @@ -10,7 +10,7 @@ namespace Elsa.FakeData.Activities; /// Provides a base implementation for activities that generate collections of fake data /// using the Bogus library. /// -public abstract class GenerateFakeDataActivity : CodeActivity> where T : class +public abstract class GenerateFakeDataActivity : CodeActivity where T : class { /// protected GenerateFakeDataActivity(string? source = null, int? line = null) : base(source, line) @@ -54,7 +54,7 @@ protected override ValueTask ExecuteAsync(ActivityExecutionContext context) var faker = CreateFaker(locale, seed); var items = faker.Generate(count); - context.Set(Result, items); + context.Set(Result, items.ToArray()); return ValueTask.CompletedTask; } diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeOrdersTests.cs b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeOrdersTests.cs index a8927737..fc893b51 100644 --- a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeOrdersTests.cs +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeOrdersTests.cs @@ -11,7 +11,7 @@ public class GenerateFakeOrdersTests public async Task Execute_WithDefaultCount_Generates10Records() { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeOrders { Result = result }; // Act @@ -20,7 +20,7 @@ public async Task Execute_WithDefaultCount_Generates10Records() // Assert var orders = context.Get(result); Assert.NotNull(orders); - Assert.Equal(10, orders.Count); + Assert.Equal(10, orders.Length); } [Theory] @@ -30,7 +30,7 @@ public async Task Execute_WithDefaultCount_Generates10Records() public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeOrders { Count = new Input(count), Result = result }; // Act @@ -39,7 +39,7 @@ public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) // Assert var orders = context.Get(result); Assert.NotNull(orders); - Assert.Equal(count, orders.Count); + Assert.Equal(count, orders.Length); } [Fact] @@ -49,15 +49,15 @@ public async Task Execute_WithSeed_ProducesDeterministicData() const int count = 5; // Arrange - var result1 = new Output>(); + var result1 = new Output(); var activity1 = new GenerateFakeOrders { Count = new Input(count), Seed = new Input(seed), Result = result1, }; - - var result2 = new Output>(); + + var result2 = new Output(); var activity2 = new GenerateFakeOrders { Count = new Input(count), @@ -86,7 +86,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() public async Task Execute_GeneratesOrders_WithAllPropertiesPopulated() { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeOrders { Count = new Input(5), diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakePersonsTests.cs b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakePersonsTests.cs index 8b570a8e..b7f0c90d 100644 --- a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakePersonsTests.cs +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakePersonsTests.cs @@ -11,7 +11,7 @@ public class GenerateFakePersonsTests public async Task Execute_WithDefaultCount_Generates10Records() { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakePersons { Result = result }; // Act @@ -20,7 +20,7 @@ public async Task Execute_WithDefaultCount_Generates10Records() // Assert var persons = context.Get(result); Assert.NotNull(persons); - Assert.Equal(10, persons.Count); + Assert.Equal(10, persons.Length); } [Theory] @@ -30,7 +30,7 @@ public async Task Execute_WithDefaultCount_Generates10Records() public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakePersons { Count = new Input(count), Result = result }; // Act @@ -39,7 +39,7 @@ public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) // Assert var persons = context.Get(result); Assert.NotNull(persons); - Assert.Equal(count, persons.Count); + Assert.Equal(count, persons.Length); } [Fact] @@ -49,7 +49,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() const int count = 5; // Arrange - var result1 = new Output>(); + var result1 = new Output(); var activity1 = new GenerateFakePersons { Count = new Input(count), @@ -57,7 +57,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() Result = result1, }; - var result2 = new Output>(); + var result2 = new Output(); var activity2 = new GenerateFakePersons { Count = new Input(count), @@ -86,7 +86,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() public async Task Execute_GeneratesPersons_WithAllPropertiesPopulated() { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakePersons { Count = new Input(5), diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeProductsTests.cs b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeProductsTests.cs index 8ea47ef0..98da56d8 100644 --- a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeProductsTests.cs +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeProductsTests.cs @@ -11,7 +11,7 @@ public class GenerateFakeProductsTests public async Task Execute_WithDefaultCount_Generates10Records() { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeProducts { Result = result }; // Act @@ -20,7 +20,7 @@ public async Task Execute_WithDefaultCount_Generates10Records() // Assert var products = context.Get(result); Assert.NotNull(products); - Assert.Equal(10, products.Count); + Assert.Equal(10, products.Length); } [Theory] @@ -30,7 +30,7 @@ public async Task Execute_WithDefaultCount_Generates10Records() public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeProducts { Count = new Input(count), Result = result }; // Act @@ -39,7 +39,7 @@ public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) // Assert var products = context.Get(result); Assert.NotNull(products); - Assert.Equal(count, products.Count); + Assert.Equal(count, products.Length); } [Fact] @@ -49,7 +49,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() const int count = 5; // Arrange - var result1 = new Output>(); + var result1 = new Output(); var activity1 = new GenerateFakeProducts { Count = new Input(count), @@ -57,7 +57,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() Result = result1, }; - var result2 = new Output>(); + var result2 = new Output(); var activity2 = new GenerateFakeProducts { Count = new Input(count), @@ -86,7 +86,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() public async Task Execute_GeneratesProducts_WithAllPropertiesPopulated() { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeProducts { Count = new Input(5), diff --git a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeUsersTests.cs b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeUsersTests.cs index f33026e5..d971dfe8 100644 --- a/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeUsersTests.cs +++ b/test/modules/fakedata/Elsa.FakeData.UnitTests/Activities/GenerateFakeUsersTests.cs @@ -11,7 +11,7 @@ public class GenerateFakeUsersTests public async Task Execute_WithDefaultCount_Generates10Records() { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeUsers { Result = result }; // Act @@ -20,7 +20,7 @@ public async Task Execute_WithDefaultCount_Generates10Records() // Assert var users = context.Get(result); Assert.NotNull(users); - Assert.Equal(10, users.Count); + Assert.Equal(10, users.Length); } [Theory] @@ -30,7 +30,7 @@ public async Task Execute_WithDefaultCount_Generates10Records() public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeUsers { Count = new Input(count), Result = result }; // Act @@ -39,7 +39,7 @@ public async Task Execute_WithCustomCount_GeneratesExpectedCount(int count) // Assert var users = context.Get(result); Assert.NotNull(users); - Assert.Equal(count, users.Count); + Assert.Equal(count, users.Length); } [Fact] @@ -49,7 +49,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() const int count = 5; // Arrange - var result1 = new Output>(); + var result1 = new Output(); var activity1 = new GenerateFakeUsers { Count = new Input(count), @@ -57,7 +57,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() Result = result1, }; - var result2 = new Output>(); + var result2 = new Output(); var activity2 = new GenerateFakeUsers { Count = new Input(count), @@ -86,7 +86,7 @@ public async Task Execute_WithSeed_ProducesDeterministicData() public async Task Execute_GeneratesUsers_WithAllPropertiesPopulated() { // Arrange - var result = new Output>(); + var result = new Output(); var activity = new GenerateFakeUsers { Count = new Input(5), From c7737101c632793bbbf5789fd538284d9cc79fdc Mon Sep 17 00:00:00 2001 From: Namoshek Date: Tue, 3 Mar 2026 17:47:07 +0100 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../Elsa.FakeData/Activities/GenerateFakeDataActivity.cs | 5 +++++ .../fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs | 2 +- .../fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs | 4 ++-- src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs index f561fda5..c010578d 100644 --- a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeDataActivity.cs @@ -48,6 +48,10 @@ protected GenerateFakeDataActivity(string? source = null, int? line = null) : ba protected override ValueTask ExecuteAsync(ActivityExecutionContext context) { var count = Count.Get(context); + + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(Count), count, "The number of records to generate must be zero or greater."); + var locale = Locale.GetOrDefault(context) ?? "en"; var seed = Seed.GetOrDefault(context, () => null); @@ -58,6 +62,7 @@ protected override ValueTask ExecuteAsync(ActivityExecutionContext context) return ValueTask.CompletedTask; } + } /// /// Creates a configured instance for the given locale. diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs index ef503e94..16bf67a2 100644 --- a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakePersons.cs @@ -34,7 +34,7 @@ protected override Faker CreateFaker(string locale, int? seed) .RuleFor(p => p.FullName, (f) => f.Person.FullName) .RuleFor(p => p.Email, (f, p) => f.Person.Email) .RuleFor(p => p.Phone, f => f.Phone.PhoneNumber()) - .RuleFor(p => p.DateOfBirth, f => f.Date.Past(80, DateTime.Now.AddYears(-18))) + .RuleFor(p => p.DateOfBirth, f => f.Date.Past(80, new DateTime(2000, 1, 1))); .RuleFor(p => p.Gender, f => f.PickRandom("Male", "Female", "Non-binary")) .RuleFor(p => p.Address, f => f.Address.StreetAddress()) .RuleFor(p => p.City, f => f.Address.City()) diff --git a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs index 8f352404..2e739534 100644 --- a/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs +++ b/src/modules/fakedata/Elsa.FakeData/Activities/GenerateFakeUsers.cs @@ -31,8 +31,8 @@ protected override Faker CreateFaker(string locale, int? seed) .RuleFor(u => u.Id, f => f.Random.Guid()) .RuleFor(u => u.FirstName, f => f.Person.FirstName) .RuleFor(u => u.LastName, f => f.Person.LastName) - .RuleFor(u => u.Username, (f, u) => f.Person.UserName) - .RuleFor(u => u.Email, (f, u) => f.Person.Email) + .RuleFor(u => u.Username, f => f.Person.UserName) + .RuleFor(u => u.Email, f => f.Person.Email) .RuleFor(u => u.Avatar, f => f.Person.Avatar) .RuleFor(u => u.Roles, f => [f.PickRandom("Admin", "User", "Moderator", "Guest")]) .RuleFor(u => u.CreatedAt, f => f.Date.Past(5)); diff --git a/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj b/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj index 9bcdb4fe..6e3a6ad0 100644 --- a/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj +++ b/src/modules/fakedata/Elsa.FakeData/Elsa.FakeData.csproj @@ -10,7 +10,7 @@ - +