< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.Transports.SqlServer.SqlServerReplyTargetProvider
Assembly: AsyncResponse.Transports.SqlServer
File(s): /_/src/Transports/AsyncResponse.Transports.SqlServer/SqlServerReplyTargetProvider.cs
Line coverage
100%
Covered lines: 42
Uncovered lines: 0
Coverable lines: 42
Total lines: 79
Line coverage: 100%
Branch coverage
100%
Covered branches: 12
Total branches: 12
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetReplyTarget(...)100%88100%
ResolveTarget(...)100%44100%

File(s)

/_/src/Transports/AsyncResponse.Transports.SqlServer/SqlServerReplyTargetProvider.cs

#LineLine coverage
 1using Microsoft.Extensions.Options;
 2
 3namespace AsyncResponse.Transports.SqlServer;
 4
 2115internal sealed class SqlServerReplyTargetProvider(
 2116    IOptions<SqlServerAsyncResponseTransportOptions> _options) : IAsyncResponseReplyTargetProvider
 7{
 8    /// <inheritdoc />
 9    public AsyncResponseReplyTarget GetReplyTarget(string? name = null)
 10    {
 1611        var options = _options.Value;
 1612        SqlServerTransportOptionsValidator.ValidateCommon(options);
 1613        var targetName = string.IsNullOrWhiteSpace(name)
 1614            ? options.DefaultReplyTargetName
 1615            : name;
 16
 1617        var target = ResolveTarget(options, targetName);
 1418        var queueOptionName = StringComparer.Ordinal.Equals(targetName, options.DefaultReplyTargetName)
 1419            ? $"{nameof(SqlServerReplyTargetOptions)}.{nameof(SqlServerReplyTargetOptions.ResponseQueue)}"
 1420            : $"{nameof(SqlServerAsyncResponseTransportOptions.ReplyTargets)}[\"{targetName}\"]." +
 1421              $"{nameof(SqlServerReplyTargetOptions.ResponseQueue)}";
 1422        var responseQueue = SqlServerTransportOptionsValidator.Required(target.ResponseQueue, queueOptionName);
 23
 24        // ValidateCommon covers the three transport-wide queues, but a NAMED reply target's queue
 25        // reaches the same nvarchar(200) column by a different route: it is emitted as the reply
 26        // address and as the "queue" property a remote publisher inserts with. Unvalidated, an
 27        // over-long name fails the insert outright and a space-padded one lands a row that the
 28        // exact-matching claim predicate will never hand to any subscriber. Validated here rather
 29        // than in ValidateCommon so a target added by mutating the dictionary directly is covered.
 1430        SqlServerTransportOptionsValidator.ValidateQueueName(responseQueue, queueOptionName);
 31
 32        // ValidateCommon enforces three-way distinctness for the transport-wide queues because all
 33        // logical queues share one table; a NAMED target reaches that same table by another route
 34        // and must honor the same rule — a target aimed at the worker (or dead-letter) queue lands
 35        // responses as rows the worker subscriber claims, NAKs to the cap, and dead-letters, while
 36        // the waiter times out. Matching the transport-wide ResponseQueue is fine: that is
 37        // literally the default target's destination.
 1038        if (StringComparer.Ordinal.Equals(responseQueue, options.WorkerQueue)
 1039            || StringComparer.Ordinal.Equals(responseQueue, options.DeadLetterQueue))
 40        {
 441            throw new InvalidOperationException(
 442                $"SQL Server async-response reply target '{targetName}' uses queue '{responseQueue}', which collides wit
 443                $"{nameof(SqlServerAsyncResponseTransportOptions.WorkerQueue)} or {nameof(SqlServerAsyncResponseTranspor
 444                "all queues share one table, so the target's responses would be consumed as worker jobs (or buried as de
 45        }
 46
 647        var properties = new Dictionary<string, string>(target.Properties, StringComparer.Ordinal)
 648        {
 649            ["schema"] = options.SchemaName,
 650            ["table"] = options.MessageTable,
 651            ["queue"] = responseQueue,
 652            ["correlationIdHeader"] = options.CorrelationIdHeader
 653        };
 54
 655        return new AsyncResponseReplyTarget
 656        {
 657            Name = targetName,
 658            Transport = SqlServerAsyncResponseTransportOptions.TransportName,
 659            Address = responseQueue,
 660            Properties = properties
 661        };
 62    }
 63
 64    private static SqlServerReplyTargetOptions ResolveTarget(
 65        SqlServerAsyncResponseTransportOptions options,
 66        string targetName)
 67    {
 1668        if (options.ReplyTargets.TryGetValue(targetName, out var configured))
 1269            return configured;
 70
 471        if (StringComparer.Ordinal.Equals(targetName, options.DefaultReplyTargetName))
 272            return new SqlServerReplyTargetOptions { ResponseQueue = options.ResponseQueue };
 73
 274        throw new InvalidOperationException(
 275            $"SQL Server async-response reply target '{targetName}' is not configured. " +
 276            $"Configure {nameof(SqlServerAsyncResponseTransportOptions.ResponseQueue)} for the default target " +
 277            $"or add a named target with {nameof(SqlServerAsyncResponseTransportOptions.AddReplyTarget)}.");
 78    }
 79}