< Summary

Information
Class: Elsa.ExternalAuthentication.Services.ConnectionTestOperationResult
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Services/ConnectionTestService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 3
Coverable lines: 3
Total lines: 114
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%210%
get_Observation()100%210%
get_CurrentRevision()100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Services/ConnectionTestService.cs

#LineLine coverage
 1using System.Diagnostics;
 2using System.Security.Claims;
 3using Elsa.Common;
 4using Elsa.ExternalAuthentication.Contracts;
 5using Elsa.ExternalAuthentication.Models;
 6using Elsa.ExternalAuthentication.Notifications;
 7
 8namespace Elsa.ExternalAuthentication.Services;
 9
 10/// <summary>Runs an explicit adapter test and stores only its latest redacted outcome.</summary>
 11public sealed class ConnectionTestService(
 12    IdentityProviderConnectionManagementService management,
 13    IExternalAuthenticationAdapterRegistry adapters,
 14    IEnumerable<ISecretBindingResolver> secretBindingResolvers,
 15    IConnectionObservationStore observations,
 16    ISystemClock clock,
 17    ExternalAuthenticationSecurityNotifier notifier)
 18{
 19    private readonly IReadOnlyDictionary<string, ISecretBindingResolver> _resolvers = secretBindingResolvers.ToDictionar
 20
 21    public async ValueTask<ConnectionTestOperationResult> TestAsync(string connectionId, long expectedRevision, string t
 22    {
 23        var lookup = await management.FindAsync(connectionId, tenantId, cancellationToken);
 24        if (lookup is not ManagementConnectionLookupResult.Found(var connection))
 25            return new ConnectionTestOperationResult.NotFound();
 26        if (connection.Connection.Revision != expectedRevision)
 27            return new ConnectionTestOperationResult.PreconditionFailed(connection.Connection.Revision);
 28        if (!adapters.TryGet(connection.Connection.AdapterType, out var adapter))
 29            return new ConnectionTestOperationResult.Unavailable();
 30
 31        var timer = Stopwatch.StartNew();
 32        ConnectionTestResult test;
 33        IReadOnlyDictionary<string, ResolvedSecretBinding> secrets = new Dictionary<string, ResolvedSecretBinding>();
 34        try
 35        {
 36            secrets = await ResolveSecretsAsync(connection.Connection.SecretBindings, cancellationToken);
 37            test = await adapter.TestAsync(new ConnectionTestContext(connection, secrets, clock), cancellationToken);
 38        }
 39        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 40        {
 41            throw;
 42        }
 43        catch
 44        {
 45            test = new ConnectionTestResult(ConnectionObservationStatus.Failed, "unavailable", "The provider test could 
 46        }
 47        finally
 48        {
 49            timer.Stop();
 50            foreach (var secret in secrets.Values)
 51                secret.Value.Dispose();
 52        }
 53
 54        var observation = new ConnectionObservation(
 55            connection.Connection.Id,
 56            connection.Connection.MaterialRevision,
 57            clock.UtcNow,
 58            test.Status,
 59            SafeCategory(test.Category),
 60            timer.Elapsed,
 61            SafeSummary(test.Summary),
 62            test.Warnings.Select(SafeSummary).ToArray(),
 63            Guid.NewGuid().ToString("N"));
 64        await observations.SaveLatestAsync(observation, cancellationToken);
 65        await notifier.PublishAsync(new IdentityProviderConnectionTested(
 66            ExternalAuthenticationSecurityNotifier.Context(
 67                ActorId(actor),
 68                tenantId,
 69                connection.Connection.Id,
 70                null,
 71                observation.Status == ConnectionObservationStatus.Failed ? SecurityEventOutcome.Failed : SecurityEventOu
 72                "Identity provider connection test completed."),
 73            observation.TestedMaterialRevision,
 74            observation.Status.ToString().ToLowerInvariant(),
 75            observation.Category,
 76            observation.Duration), cancellationToken);
 77        return new ConnectionTestOperationResult.Completed(observation);
 78    }
 79
 80    private async ValueTask<IReadOnlyDictionary<string, ResolvedSecretBinding>> ResolveSecretsAsync(IDictionary<string, 
 81    {
 82        var result = new Dictionary<string, ResolvedSecretBinding>(StringComparer.Ordinal);
 83        try
 84        {
 85            foreach (var (name, binding) in bindings)
 86            {
 87                if (!_resolvers.TryGetValue(binding.ResolverType, out var resolver))
 88                    throw new InvalidOperationException("The secret binding resolver is unavailable.");
 89                result[name] = await resolver.ResolveAsync(binding, cancellationToken);
 90            }
 91            return result;
 92        }
 93        catch
 94        {
 95            foreach (var secret in result.Values)
 96                secret.Value.Dispose();
 97            throw;
 98        }
 99    }
 100
 101    private static string? ActorId(ClaimsPrincipal actor) => actor.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? actor.
 102    // Adapter messages are already contractually safe, but cap them at a predictable diagnostic size.
 103    private static string SafeSummary(string value) => string.IsNullOrWhiteSpace(value) ? "No additional details are ava
 104    private static string SafeCategory(string value) => string.IsNullOrWhiteSpace(value) ? "unknown" : value.Length <= 1
 105}
 106
 107public abstract record ConnectionTestOperationResult
 108{
 0109    private ConnectionTestOperationResult() { }
 0110    public sealed record Completed(ConnectionObservation Observation) : ConnectionTestOperationResult;
 111    public sealed record NotFound : ConnectionTestOperationResult;
 0112    public sealed record PreconditionFailed(long CurrentRevision) : ConnectionTestOperationResult;
 113    public sealed record Unavailable : ConnectionTestOperationResult;
 114}