< Summary

Information
Class: Program
Assembly: Elsa.Server.Web
File(s): /home/runner/work/elsa-core/elsa-core/src/apps/Elsa.Server.Web/Program.cs
Line coverage
85%
Covered lines: 175
Uncovered lines: 30
Coverable lines: 205
Total lines: 312
Line coverage: 85.3%
Branch coverage
67%
Covered branches: 38
Total branches: 56
Branch coverage: 67.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
<Main>$()67.85%665685.29%
get_ConfigureForTest()100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/apps/Elsa.Server.Web/Program.cs

#LineLine coverage
 1using System.Text.Encodings.Web;
 2using System.Threading.RateLimiting;
 3using Elsa.Caching.Options;
 4using Elsa.Common.RecurringTasks;
 5using Elsa.Expressions.Helpers;
 6using Elsa.Extensions;
 7using Elsa.Features.Services;
 8using Elsa.Http.Options;
 9using Elsa.Identity.Multitenancy;
 10using Elsa.Persistence.EFCore.Extensions;
 11using Elsa.Persistence.EFCore.Modules.Management;
 12using Elsa.Persistence.EFCore.Modules.Runtime;
 13using Elsa.Server.Web.Activities;
 14using Elsa.Server.Web.ActivityHosts;
 15using Elsa.Server.Web.Filters;
 16using Elsa.Tenants;
 17using Elsa.Tenants.AspNetCore;
 18using Elsa.Tenants.Extensions;
 19using Elsa.WorkflowProviders.BlobStorage.ElsaScript.Extensions;
 20using Elsa.Workflows;
 21using Elsa.Workflows.Activities.Flowchart.Extensions;
 22using Elsa.Workflows.Api;
 23using Elsa.Workflows.CommitStates.Strategies;
 24using Elsa.Workflows.IncidentStrategies;
 25using Elsa.Workflows.LogPersistence;
 26using Elsa.Workflows.Options;
 27using Elsa.Workflows.Runtime.Distributed.Extensions;
 28using Elsa.Workflows.Runtime.Options;
 29using Elsa.Workflows.Runtime.Tasks;
 30using JetBrains.Annotations;
 31using Microsoft.AspNetCore.RateLimiting;
 32using Microsoft.Extensions.Diagnostics.HealthChecks;
 33using Microsoft.Extensions.Options;
 34
 35// ReSharper disable RedundantAssignment
 36const bool useReadOnlyMode = false;
 37const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests.
 38const bool useStructuredLogs = false; // Enable to inspect backend logs from Elsa Studio.
 39const bool useMultitenancy = true;
 40const bool disableVariableWrappers = false;
 41const string elsaApiRateLimitingPolicy = "elsa-api";
 42const string httpWorkflowRateLimitingPolicy = "elsa-http-workflows";
 43
 344ObjectConverter.StrictMode = true;
 45
 346var builder = WebApplication.CreateBuilder(args);
 347var services = builder.Services;
 348var configuration = builder.Configuration;
 349var identitySection = configuration.GetSection("Identity");
 350var identityTokenSection = identitySection.GetSection("Tokens");
 351var ingressRateLimitingSection = configuration.GetSection("IngressRateLimiting");
 352var useIngressRateLimiting = ingressRateLimitingSection.GetValue("Enabled", false);
 353var registerIngressRateLimitingPolicies = ingressRateLimitingSection.GetValue("RegisterReferencePolicies", useIngressRat
 354var configuredAllowLocalDistributedRuntimeLockProvider = configuration.GetValue<bool?>("DistributedRuntime:AllowLocalLoc
 355var allowLocalDistributedRuntimeLockProvider =
 356    configuredAllowLocalDistributedRuntimeLockProvider ?? builder.Environment.IsDevelopment();
 57
 358services
 359    .AddElsa(elsa =>
 360    {
 361        elsa
 362            .AddActivitiesFrom<Program>()
 363            .AddActivityHost<Penguin>()
 364            .AddWorkflowsFrom<Program>()
 365            .UseIdentity(identity =>
 366            {
 667                identity.TokenOptions += options => identityTokenSection.Bind(options);
 368                identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
 369                identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
 670                identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
 371            })
 372            .UseDefaultAuthentication()
 373            .UseWorkflows(workflows =>
 374            {
 375                workflows.UseCommitStrategies(strategies =>
 376                {
 377                    strategies.AddStandardStrategies();
 378                    strategies.Add("Every 10 seconds", new PeriodicWorkflowStrategy(TimeSpan.FromSeconds(10)));
 679                });
 380            })
 381            .UseFlowchart(flowchart => flowchart.UseTokenBasedExecution())
 382            .UseWorkflowManagement(management =>
 383            {
 684                management.UseEntityFrameworkCore(ef => ef.UseSqlite());
 385                management.SetDefaultLogPersistenceMode(LogPersistenceMode.Inherit);
 386                management.UseCache();
 387                management.UseReadOnlyMode(useReadOnlyMode);
 388            })
 389            .UseWorkflowRuntime(runtime =>
 390            {
 691                runtime.UseEntityFrameworkCore(ef => ef.UseSqlite());
 392                runtime.UseCache();
 393                runtime.UseDistributedRuntime();
 394                // This sample host acknowledges single-host file-system locks in development or explicit single-host op
 395                runtime.DistributedLockingOptions = options => options.AllowLocalLockProviderInDistributedRuntime = allo
 396            })
 397            .UseWorkflowsApi()
 398            .UseDashboardApi()
 399            .UseWorkflowRuntimeDashboard()
 3100            .UseFluentStorageProvider()
 3101            .UseElsaScriptBlobStorage()
 3102            .UseScheduling()
 3103            .UseCSharp(options =>
 3104            {
 3105                configuration.GetSection("Scripting:CSharp").Bind(options);
 3106                options.DisableWrappers = disableVariableWrappers;
 3107                options.AppendScript("string Greet(string name) => $\"Hello {name}!\";");
 3108                options.AppendScript("string SayHelloWorld() => Greet(\"World\");");
 3109            })
 3110            .UseJavaScript(options =>
 3111            {
 3112                options.AllowClrAccess = true;
 3113                options.ConfigureEngine(engine =>
 3114                {
 19115                    engine.Execute("function greet(name) { return `Hello ${name}!`; }");
 19116                    engine.Execute("function sayHelloWorld() { return greet('World'); }");
 22117                });
 3118            })
 3119            .UsePython(python =>
 3120            {
 3121                python.PythonOptions += options =>
 3122                {
 3123                    // Make sure to configure the path to the python DLL. E.g. /opt/homebrew/Cellar/python@3.11/3.11.6_1
 3124                    // alternatively, you can set the PYTHONNET_PYDLL environment variable.
 3125                    configuration.GetSection("Scripting:Python").Bind(options);
 3126
 3127                    options.AddScript(sb =>
 3128                    {
 3129                        sb.AppendLine("def greet():");
 3130                        sb.AppendLine("    return \"Hello, welcome to Python!\"");
 6131                    });
 6132                };
 3133            })
 6134            .UseLiquid(liquid => liquid.FluidOptions = options => options.Encoder = HtmlEncoder.Default)
 3135            .UseHttp(http =>
 3136            {
 6137                http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options);
 3138                http.UseCache();
 6139            });
 3140
 3141        if(useMultitenancy)
 3142        {
 3143            elsa.UseTenants(tenants =>
 3144            {
 4145                tenants.UseConfigurationBasedTenantsProvider(options => configuration.GetSection("Multitenancy").Bind(op
 4146                tenants.ConfigureMultitenancy(options => options.TenantResolverPipelineBuilder = new TenantResolverPipel
 4147                    .Append<CurrentUserTenantResolver>());
 6148            });
 3149        }
 3150
 3151        if(useStructuredLogs)
 3152        {
 3153            elsa
 3154                .UseStructuredLogs()
 3155                .UseStructuredLogsDashboard();
 3156        }
 3157
 3158        ConfigureForTest?.Invoke(elsa);
 6159    });
 160
 161// Obfuscate HTTP request headers.
 3162services.AddActivityStateFilter<HttpRequestAuthenticationHeaderFilter>();
 163
 164// Optionally configure recurring tasks using alternative schedules.
 3165services.Configure<RecurringTaskOptions>(options =>
 3166{
 3167    options.Schedule.ConfigureTask<TriggerBookmarkQueueRecurringTask>(TimeSpan.FromSeconds(300));
 3168    options.Schedule.ConfigureTask<PurgeBookmarkQueueRecurringTask>(TimeSpan.FromSeconds(300));
 3169    options.Schedule.ConfigureTask<RestartInterruptedWorkflowsTask>(TimeSpan.FromSeconds(15));
 6170});
 171
 9172services.Configure<RuntimeOptions>(options => { options.InactivityThreshold = TimeSpan.FromSeconds(15); });
 3173services.Configure<BookmarkQueuePurgeOptions>(options => options.Ttl = TimeSpan.FromSeconds(3600));
 6174services.Configure<CachingOptions>(options => options.CacheDuration = TimeSpan.FromDays(1));
 3175services.Configure<IncidentOptions>(options => options.DefaultIncidentStrategy = typeof(ContinueWithIncidentsStrategy));
 3176if (useIngressRateLimiting)
 177{
 0178    services.PostConfigure<ApiEndpointOptions>(options =>
 0179    {
 0180        if (options.RateLimitingPolicyName == null)
 0181            options.RateLimitingPolicyName = elsaApiRateLimitingPolicy;
 0182    });
 0183    services.PostConfigure<HttpActivityOptions>(options =>
 0184    {
 0185        if (options.RateLimitingPolicyName == null)
 0186            options.RateLimitingPolicyName = httpWorkflowRateLimitingPolicy;
 0187    });
 188}
 189
 3190services.AddRateLimiter(options =>
 3191{
 0192    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
 3193
 0194    if (registerIngressRateLimitingPolicies)
 3195    {
 0196        options.AddFixedWindowLimiter(elsaApiRateLimitingPolicy, limiterOptions =>
 0197        {
 0198            limiterOptions.PermitLimit = ingressRateLimitingSection.GetValue("ApiPermitLimit", 120);
 0199            limiterOptions.Window = TimeSpan.FromSeconds(ingressRateLimitingSection.GetValue("ApiWindowSeconds", 60));
 0200            limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
 0201            limiterOptions.QueueLimit = ingressRateLimitingSection.GetValue("ApiQueueLimit", 0);
 0202        });
 0203        options.AddFixedWindowLimiter(httpWorkflowRateLimitingPolicy, limiterOptions =>
 0204        {
 0205            limiterOptions.PermitLimit = ingressRateLimitingSection.GetValue("HttpWorkflowPermitLimit", 60);
 0206            limiterOptions.Window = TimeSpan.FromSeconds(ingressRateLimitingSection.GetValue("HttpWorkflowWindowSeconds"
 0207            limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
 0208            limiterOptions.QueueLimit = ingressRateLimitingSection.GetValue("HttpWorkflowQueueLimit", 0);
 0209        });
 3210    }
 3211});
 3212services
 3213    .AddHealthChecks()
 3214    .AddElsaReadinessChecks(includeDistributedLocks: true);
 3215services.AddControllers();
 9216services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithE
 217
 218// Build the web application.
 3219var app = builder.Build();
 220
 221
 222// Configure the pipeline.
 3223if (app.Environment.IsDevelopment())
 3224    app.UseDeveloperExceptionPage();
 225
 226// CORS.
 3227app.UseCors();
 228
 229// Health checks.
 3230app.MapHealthChecks("/health/live", new()
 3231{
 0232    Predicate = _ => false
 3233});
 3234app.MapHealthChecks("/health/ready", new()
 3235{
 0236    Predicate = check => check.Tags.Contains(HealthCheckExtensions.ElsaTag) && check.Tags.Contains(HealthCheckExtensions
 3237    ResultStatusCodes =
 3238    {
 3239        [HealthStatus.Degraded] = StatusCodes.Status503ServiceUnavailable,
 3240        [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
 3241    }
 3242});
 3243app.MapHealthChecks("/", new()
 3244{
 0245    Predicate = _ => false
 3246});
 247
 248// Elsa API endpoints for designer.
 3249var apiEndpointOptions = app.Services.GetRequiredService<IOptions<ApiEndpointOptions>>().Value;
 3250var routePrefix = apiEndpointOptions.RoutePrefix;
 3251app.MapWorkflowsApi(routePrefix);
 252
 253// Routing used for SignalR.
 3254app.UseRouting();
 255
 3256app.UseWorkflowsApiRateLimiting(routePrefix, apiEndpointOptions.RateLimitingPolicyName);
 257
 258// Elsa HTTP Endpoint activities.
 3259var httpActivityOptions = app.Services.GetRequiredService<IOptions<HttpActivityOptions>>().Value;
 3260app.UseWorkflowsRateLimiting(httpActivityOptions.BasePath, httpActivityOptions.RateLimitingPolicyName);
 3261if (useIngressRateLimiting ||
 3262    !string.IsNullOrWhiteSpace(apiEndpointOptions.RateLimitingPolicyName) ||
 3263    !string.IsNullOrWhiteSpace(httpActivityOptions.RateLimitingPolicyName))
 0264    app.UseRateLimiter();
 265
 266// Security.
 3267app.UseAuthentication();
 3268app.UseAuthorization();
 269
 270// Multitenancy.
 271if (useMultitenancy)
 3272    app.UseTenants();
 273
 274// Captures unhandled exceptions and returns a JSON response.
 3275app.UseJsonSerializationErrorHandler();
 276
 3277app.UseWorkflows();
 278
 3279app.MapControllers();
 280
 281// Swagger API documentation.
 3282if (app.Environment.IsDevelopment())
 283{
 3284    app.UseSwaggerUI();
 285}
 286
 287// SignalR.
 288if (useSignalR)
 289{
 290    app.UseWorkflowsSignalRHubs();
 291}
 292
 293// Structured log streaming for Studio diagnostics.
 294if (useStructuredLogs)
 295{
 296    app.UseStructuredLogs();
 297}
 298
 299// Run.
 3300await app.RunAsync();
 301
 302/// <summary>
 303/// The main entry point for the application made public for end to end testing.
 304/// </summary>
 305[UsedImplicitly]
 306public partial class Program
 307{
 308    /// <summary>
 309    /// Set by the test runner to configure the module for testing.
 310    /// </summary>
 7311    public static Action<IModule>? ConfigureForTest { get; set; }
 312}

Methods/Properties

<Main>$()
get_ConfigureForTest()