using Elsa.Expressions.JavaScript.Contracts; using Elsa.Expressions.JavaScript.TypeDefinitions.Contracts; using Elsa.Expressions.JavaScript.TypeDefinitions.Models; using Elsa.Extensions; using Elsa.Features.Services; using Elsa.Secrets.Contracts; using Elsa.Secrets.Models; using Elsa.Testing.Shared; using Elsa.Workflows; using Elsa.Workflows.Activities; using Microsoft.Extensions.DependencyInjection; using Xunit.Abstractions; namespace Elsa.JavaScript.IntegrationTests; public class SecretsJavaScriptTests { private readonly TestSecretResolver _secretResolver = new(); private readonly WorkflowTestFixture _fixture; public SecretsJavaScriptTests(ITestOutputHelper testOutputHelper) { _fixture = new(testOutputHelper); _fixture .ConfigureServices(services => services.AddSingleton(_secretResolver)) .ConfigureElsa(ConfigureElsa); } [Fact] public async Task GetSecret_ReturnsPromiseCompatibleSecretValue() { var result = await EvaluateScriptAsync("return getSecret('api:key');"); Assert.Equal("secret-value", result); Assert.Equal(["api:key"], _secretResolver.ResolvedNames); } [Fact] public async Task GetSecret_ComposesWithThen() { var result = await EvaluateScriptAsync("return getSecret('api:key').then(value => value + '-suffix');"); Assert.Equal("secret-value-suffix", result); } [Fact] public async Task GetSecret_ComposesWithAsyncIife() { var result = await EvaluateScriptAsync("return (async () => await getSecret('api:key'))();"); Assert.Equal("secret-value", result); } [Fact] public async Task TypeDefinitions_DocumentGetSecretAsPromiseOfString() { var typeDefinitions = await GenerateTypeDefinitionsAsync(); Assert.Contains("declare function getSecret(name: string): Promise;", typeDefinitions); } private static void ConfigureElsa(IModule module) { module.UseSecretsJavaScript(); } private async Task EvaluateScriptAsync(string script) { var context = await _fixture.CreateExpressionExecutionContextAsync(); var evaluator = _fixture.Services.GetRequiredService(); var result = await evaluator.EvaluateAsync(script, typeof(T), context); return result is T typedResult ? typedResult : (T)Convert.ChangeType(result, typeof(T))!; } private async Task GenerateTypeDefinitionsAsync() { await _fixture.BuildAsync(); var workflow = new Workflow(); var workflowGraphBuilder = _fixture.Services.GetRequiredService(); var workflowGraph = await workflowGraphBuilder.BuildAsync(workflow); var typeDefinitionContext = new TypeDefinitionContext(workflowGraph, null, null, CancellationToken.None); var typeDefinitionService = _fixture.Services.GetRequiredService(); return await typeDefinitionService.GenerateTypeDefinitionsAsync(typeDefinitionContext); } private class TestSecretResolver : ISecretResolver { private readonly Dictionary _secrets = new(StringComparer.OrdinalIgnoreCase) { ["api:key"] = "secret-value" }; public List ResolvedNames { get; } = []; public Task ResolveAsync(string name, CancellationToken cancellationToken = default) { ResolvedNames.Add(name); return Task.FromResult(_secrets[name]); } public Task ResolveAsync(SecretReference reference, CancellationToken cancellationToken = default) { return ResolveAsync(reference.Name, cancellationToken); } } }