< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Models.KestrunContext
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Models/KestrunContext.cs
Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff
Line coverage
27%
Covered lines: 30
Uncovered lines: 79
Coverable lines: 109
Total lines: 359
Line coverage: 27.5%
Branch coverage
11%
Covered branches: 5
Total branches: 44
Branch coverage: 11.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 09/12/2025 - 03:43:11 Line coverage: 100% (9/9) Branch coverage: 62.5% (5/8) Total lines: 54 Tag: Kestrun/Kestrun@d160286e3020330b1eb862d66a37db2e26fc904210/13/2025 - 16:52:37 Line coverage: 100% (30/30) Branch coverage: 62.5% (5/8) Total lines: 108 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/15/2025 - 21:27:26 Line coverage: 40% (30/75) Branch coverage: 25% (5/20) Total lines: 247 Tag: Kestrun/Kestrun@c33ec02a85e4f8d6061aeaab5a5e8c3a8b66559410/17/2025 - 15:48:30 Line coverage: 39.4% (30/76) Branch coverage: 25% (5/20) Total lines: 252 Tag: Kestrun/Kestrun@b8199aff869a847b75e185d0527ba45e04a43d8611/19/2025 - 02:25:56 Line coverage: 27.5% (30/109) Branch coverage: 11.3% (5/44) Total lines: 359 Tag: Kestrun/Kestrun@98ff905e5605a920343154665980a71211a03c6d12/12/2025 - 17:27:19 Line coverage: 20.1% (22/109) Branch coverage: 11.3% (5/44) Total lines: 359 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd12/15/2025 - 02:23:46 Line coverage: 27.5% (30/109) Branch coverage: 11.3% (5/44) Total lines: 359 Tag: Kestrun/Kestrun@7a3839f4de2254e22daae81ab8dc7cb2f40c8330 09/12/2025 - 03:43:11 Line coverage: 100% (9/9) Branch coverage: 62.5% (5/8) Total lines: 54 Tag: Kestrun/Kestrun@d160286e3020330b1eb862d66a37db2e26fc904210/13/2025 - 16:52:37 Line coverage: 100% (30/30) Branch coverage: 62.5% (5/8) Total lines: 108 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/15/2025 - 21:27:26 Line coverage: 40% (30/75) Branch coverage: 25% (5/20) Total lines: 247 Tag: Kestrun/Kestrun@c33ec02a85e4f8d6061aeaab5a5e8c3a8b66559410/17/2025 - 15:48:30 Line coverage: 39.4% (30/76) Branch coverage: 25% (5/20) Total lines: 252 Tag: Kestrun/Kestrun@b8199aff869a847b75e185d0527ba45e04a43d8611/19/2025 - 02:25:56 Line coverage: 27.5% (30/109) Branch coverage: 11.3% (5/44) Total lines: 359 Tag: Kestrun/Kestrun@98ff905e5605a920343154665980a71211a03c6d12/12/2025 - 17:27:19 Line coverage: 20.1% (22/109) Branch coverage: 11.3% (5/44) Total lines: 359 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd12/15/2025 - 02:23:46 Line coverage: 27.5% (30/109) Branch coverage: 11.3% (5/44) Total lines: 359 Tag: Kestrun/Kestrun@7a3839f4de2254e22daae81ab8dc7cb2f40c8330

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%11100%
get_Host()100%11100%
get_Request()100%11100%
get_Response()100%11100%
get_HttpContext()100%11100%
get_Session()100%22100%
get_HasSession()100%11100%
TryGetSession(...)100%11100%
get_Ct()100%11100%
get_Items()100%11100%
get_User()100%11100%
get_Connection()100%210%
ToString()50%66100%
BroadcastLogAsync()0%2040%
BroadcastLog(...)100%210%
BroadcastEventAsync()0%2040%
BroadcastEvent(...)100%210%
BroadcastToGroupAsync()0%2040%
BroadcastToGroup(...)100%210%
Challenge(...)100%210%
Challenge(...)0%4260%
Challenge(...)0%620%
ChallengeAsync(...)0%4260%
SignOut(...)100%210%
SignOut(...)0%2040%
SignOut(...)0%4260%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Models/KestrunContext.cs

#LineLine coverage
 1
 2
 3using System.Collections;
 4using System.Security.Claims;
 5using Kestrun.SignalR;
 6using Microsoft.AspNetCore.Authentication;
 7using Microsoft.AspNetCore.Http.Features;
 8
 9namespace Kestrun.Models;
 10
 11/// <summary>
 12/// Represents the context for a Kestrun request, including the request, response, HTTP context, and host.
 13/// </summary>
 14public sealed record KestrunContext
 15{
 16    /// <summary>
 17    /// Initializes a new instance of the <see cref="KestrunContext"/> class.
 18    /// This constructor is used when creating a new KestrunContext from an existing HTTP context.
 19    /// It initializes the KestrunRequest and KestrunResponse based on the provided HttpContext
 20    /// </summary>
 21    /// <param name="host">The Kestrun host.</param>
 22    /// <param name="request">The Kestrun request.</param>
 23    /// <param name="response">The Kestrun response.</param>
 24    /// <param name="httpContext">The associated HTTP context.</param>
 4825    public KestrunContext(Hosting.KestrunHost host, KestrunRequest request, KestrunResponse response, HttpContext httpCo
 26    {
 4827        ArgumentNullException.ThrowIfNull(host);
 4828        ArgumentNullException.ThrowIfNull(request);
 4829        ArgumentNullException.ThrowIfNull(response);
 4830        ArgumentNullException.ThrowIfNull(httpContext);
 31
 4832        Host = host;
 4833        Request = request;
 4834        Response = response;
 4835        HttpContext = httpContext;
 4836    }
 37
 38    /// <summary>
 39    /// Initializes a new instance of the <see cref="KestrunContext"/> class.
 40    /// This constructor is used when creating a new KestrunContext from an existing HTTP context.
 41    /// It initializes the KestrunRequest and KestrunResponse based on the provided HttpContext
 42    /// </summary>
 43    /// <param name="host">The Kestrun host.</param>
 44    /// <param name="httpContext">The associated HTTP context.</param>
 145    public KestrunContext(Hosting.KestrunHost host, HttpContext httpContext)
 46    {
 147        ArgumentNullException.ThrowIfNull(host);
 148        ArgumentNullException.ThrowIfNull(httpContext);
 49
 150        Host = host;
 151        HttpContext = httpContext;
 52
 153        Request = KestrunRequest.NewRequestSync(HttpContext);
 154        Response = new KestrunResponse(Request);
 155    }
 56
 57    /// <summary>
 58    /// The Kestrun host associated with this context.
 59    /// </summary>
 5260    public Hosting.KestrunHost Host { get; init; }
 61    /// <summary>
 62    /// The Kestrun request associated with this context.
 63    /// </summary>
 7764    public KestrunRequest Request { get; init; }
 65    /// <summary>
 66    /// The Kestrun response associated with this context.
 67    /// </summary>
 8168    public KestrunResponse Response { get; init; }
 69    /// <summary>
 70    /// The ASP.NET Core HTTP context associated with this Kestrun context.
 71    /// </summary>
 8272    public HttpContext HttpContext { get; init; }
 73    /// <summary>
 74    /// Returns the ASP.NET Core session if the Session middleware is active; otherwise null.
 75    /// </summary>
 1476    public ISession? Session => HttpContext.Features.Get<ISessionFeature>()?.Session;
 77
 78    /// <summary>
 79    /// True if Session middleware is active for this request.
 80    /// </summary>
 481    public bool HasSession => Session is not null;
 82
 83    /// <summary>
 84    /// Try pattern to get session without exceptions.
 85    /// </summary>
 86    public bool TryGetSession(out ISession? session)
 87    {
 288        session = Session;
 289        return session is not null;
 90    }
 91
 92    /// <summary>
 93    /// Gets the cancellation token that is triggered when the HTTP request is aborted.
 94    /// </summary>
 195    public CancellationToken Ct => HttpContext.RequestAborted;
 96    /// <summary>
 97    /// Gets the collection of key/value pairs associated with the current HTTP context.
 98    /// </summary>
 399    public IDictionary<object, object?> Items => HttpContext.Items;
 100
 101    /// <summary>
 102    /// Gets the user associated with the current HTTP context.
 103    /// </summary>
 2104    public ClaimsPrincipal User => HttpContext.User;
 105
 106    /// <summary>
 107    /// Gets the connection information for the current HTTP context.
 108    /// </summary>
 0109    public ConnectionInfo Connection => HttpContext.Connection;
 110
 111    /// <summary>
 112    /// Returns a string representation of the KestrunContext, including path, user, and session status.
 113    /// </summary>
 114    public override string ToString()
 2115        => $"KestrunContext{{ Host={Host}, Path={HttpContext.Request.Path}, User={User?.Identity?.Name ?? "<anon>"}, Has
 116
 117
 118    /// <summary>
 119    /// Asynchronously broadcasts a log message to all connected SignalR clients using the IRealtimeBroadcaster service.
 120    /// </summary>
 121    /// <param name="level">The log level (e.g., Information, Warning, Error, Debug, Verbose).</param>
 122    /// <param name="message">The log message to broadcast.</param>
 123    /// <param name="cancellationToken">Optional: Cancellation token.</param>
 124    /// <returns>True if the log was broadcast successfully; otherwise, false.</returns>
 125    public async Task<bool> BroadcastLogAsync(string level, string message, CancellationToken cancellationToken = defaul
 126    {
 0127        var svcProvider = HttpContext.RequestServices;
 128
 0129        if (svcProvider == null)
 130        {
 0131            Host.Logger.Warning("No service provider available to resolve IRealtimeBroadcaster.");
 0132            return false;
 133        }
 0134        if (svcProvider.GetService(typeof(IRealtimeBroadcaster)) is not IRealtimeBroadcaster broadcaster)
 135        {
 0136            Host.Logger.Warning("IRealtimeBroadcaster service is not registered. Make sure SignalR is configured with Ke
 0137            return false;
 138        }
 139        try
 140        {
 0141            await broadcaster.BroadcastLogAsync(level, message, cancellationToken);
 0142            Host.Logger.Debug("Broadcasted log message via SignalR: {Level} - {Message}", level, message);
 0143            return true;
 144        }
 0145        catch (Exception ex)
 146        {
 0147            Host.Logger.Error(ex, "Failed to broadcast log message: {Level} - {Message}", level, message);
 0148            return false;
 149        }
 0150    }
 151
 152    /// <summary>
 153    /// Synchronous wrapper for BroadcastLogAsync.
 154    /// </summary>
 155    /// <param name="level">The log level (e.g., Information, Warning, Error, Debug, Verbose).</param>
 156    /// <param name="message">The log message to broadcast.</param>
 157    /// <param name="cancellationToken">Optional: Cancellation token.</param>
 158    /// <returns>True if the log was broadcast successfully; otherwise, false.</returns>
 159    public bool BroadcastLog(string level, string message, CancellationToken cancellationToken = default) =>
 0160        BroadcastLogAsync(level, message, cancellationToken).GetAwaiter().GetResult();
 161
 162
 163    /// <summary>
 164    /// Asynchronously broadcasts a custom event to all connected SignalR clients using the IRealtimeBroadcaster service
 165    /// </summary>
 166    /// <param name="eventName">The event name (e.g., Information, Warning, Error, Debug, Verbose).</param>
 167    /// <param name="data">The event data to broadcast.</param>
 168    /// <param name="cancellationToken">Optional: Cancellation token.</param>
 169    /// <returns>True if the event was broadcast successfully; otherwise, false.</returns>
 170    public async Task<bool> BroadcastEventAsync(string eventName, object? data, CancellationToken cancellationToken = de
 171    {
 0172        var svcProvider = HttpContext.RequestServices;
 173
 0174        if (svcProvider == null)
 175        {
 0176            Host.Logger.Warning("No service provider available to resolve IRealtimeBroadcaster.");
 0177            return false;
 178        }
 0179        if (svcProvider.GetService(typeof(IRealtimeBroadcaster)) is not IRealtimeBroadcaster broadcaster)
 180        {
 0181            Host.Logger.Warning("IRealtimeBroadcaster service is not registered. Make sure SignalR is configured with Ke
 0182            return false;
 183        }
 184        try
 185        {
 0186            await broadcaster.BroadcastEventAsync(eventName, data, cancellationToken);
 0187            Host.Logger.Debug("Broadcasted event via SignalR: {EventName} - {Data}", eventName, data);
 0188            return true;
 189        }
 0190        catch (Exception ex)
 191        {
 0192            Host.Logger.Error(ex, "Failed to broadcast event: {EventName} - {Data}", eventName, data);
 0193            return false;
 194        }
 0195    }
 196
 197    /// <summary>
 198    /// Synchronous wrapper for BroadcastEventAsync.
 199    /// </summary>
 200    /// <param name="eventName">The event name (e.g., Information, Warning, Error, Debug, Verbose).</param>
 201    /// <param name="data">The event data to broadcast.</param>
 202    /// <param name="cancellationToken">Optional: Cancellation token.</param>
 203    /// <returns>True if the event was broadcast successfully; otherwise, false.</returns>
 204    public bool BroadcastEvent(string eventName, object? data, CancellationToken cancellationToken = default) =>
 0205      BroadcastEventAsync(eventName, data, cancellationToken).GetAwaiter().GetResult();
 206
 207
 208    /// <summary>
 209    /// Asynchronously broadcasts a message to a specific group of SignalR clients using the IRealtimeBroadcaster servic
 210    /// </summary>
 211    /// <param name="groupName">The name of the group to broadcast the message to.</param>
 212    /// <param name="method">The name of the method to invoke on the client.</param>
 213    /// <param name="message">The message to broadcast.</param>
 214    /// <param name="cancellationToken">Optional: Cancellation token.</param>
 215    /// <returns></returns>
 216    public async Task<bool> BroadcastToGroupAsync(string groupName, string method, object? message, CancellationToken ca
 217    {
 0218        var svcProvider = HttpContext.RequestServices;
 219
 0220        if (svcProvider == null)
 221        {
 0222            Host.Logger.Warning("No service provider available to resolve IRealtimeBroadcaster.");
 0223            return false;
 224        }
 0225        if (svcProvider.GetService(typeof(IRealtimeBroadcaster)) is not IRealtimeBroadcaster broadcaster)
 226        {
 0227            Host.Logger.Warning("IRealtimeBroadcaster service is not registered. Make sure SignalR is configured with Ke
 0228            return false;
 229        }
 230        try
 231        {
 0232            await broadcaster.BroadcastToGroupAsync(groupName, method, message, cancellationToken);
 0233            Host.Logger.Debug("Broadcasted log message to group via SignalR: {GroupName} - {Method} - {Message}", groupN
 0234            return true;
 235        }
 0236        catch (Exception ex)
 237        {
 0238            Host.Logger.Error(ex, "Failed to broadcast log message: {GroupName} - {Method} - {Message}", groupName, meth
 0239            return false;
 240        }
 0241    }
 242
 243    /// <summary>
 244    /// Synchronous wrapper for BroadcastToGroupAsync.
 245    /// </summary>
 246    /// <param name="groupName">The name of the group to broadcast the message to.</param>
 247    /// <param name="method">The name of the method to invoke on the client.</param>
 248    /// <param name="message">The message to broadcast.</param>
 249    /// <param name="cancellationToken">Optional: Cancellation token.</param>
 250    /// <returns></returns>
 251    public bool BroadcastToGroup(string groupName, string method, object? message, CancellationToken cancellationToken =
 0252      BroadcastToGroupAsync(groupName, method, message, cancellationToken).GetAwaiter().GetResult();
 253
 254    /// <summary>
 255    /// Synchronous wrapper for HttpContext.ChallengeAsync.
 256    /// </summary>
 257    /// <param name="scheme">The authentication scheme to challenge.</param>
 258    /// <param name="properties">The authentication properties to include in the challenge.</param>
 0259    public void Challenge(string? scheme, AuthenticationProperties? properties) => HttpContext.ChallengeAsync(scheme, pr
 260
 261    /// <summary>
 262    /// Synchronous wrapper for HttpContext.ChallengeAsync using a Hashtable for properties.
 263    /// </summary>
 264    /// <param name="scheme">The authentication scheme to challenge.</param>
 265    /// <param name="properties">The authentication properties to include in the challenge.</param>
 266    public void Challenge(string? scheme, Hashtable? properties)
 267    {
 0268        var dict = new Dictionary<string, string?>();
 0269        if (properties != null)
 270        {
 0271            foreach (DictionaryEntry entry in properties)
 272            {
 0273                dict[entry.Key.ToString()!] = entry.Value?.ToString();
 274            }
 275        }
 0276        AuthenticationProperties authProps = new(dict);
 0277        HttpContext.ChallengeAsync(scheme, authProps).GetAwaiter().GetResult();
 0278    }
 279
 280    /// <summary>
 281    /// Synchronous wrapper for HttpContext.ChallengeAsync using a Dictionary for properties.
 282    /// </summary>
 283    /// <param name="scheme">The authentication scheme to challenge.</param>
 284    /// <param name="properties">The authentication properties to include in the challenge.</param>
 285    public void Challenge(string? scheme, Dictionary<string, string?>? properties)
 286    {
 0287        if (properties == null)
 288        {
 0289            HttpContext.ChallengeAsync(scheme).GetAwaiter().GetResult();
 0290            return;
 291        }
 292
 0293        AuthenticationProperties authProps = new(properties);
 0294        HttpContext.ChallengeAsync(scheme, authProps).GetAwaiter().GetResult();
 0295    }
 296
 297    /// <summary>
 298    /// Asynchronous wrapper for HttpContext.ChallengeAsync using a Hashtable for properties.
 299    /// </summary>
 300    /// <param name="scheme">The authentication scheme to challenge.</param>
 301    /// <param name="properties">The authentication properties to include in the challenge.</param>
 302    /// <returns>Task representing the asynchronous operation.</returns>
 303    public Task ChallengeAsync(string? scheme, Hashtable? properties)
 304    {
 0305        var dict = new Dictionary<string, string?>();
 0306        if (properties != null)
 307        {
 0308            foreach (DictionaryEntry entry in properties)
 309            {
 0310                dict[entry.Key.ToString()!] = entry.Value?.ToString();
 311            }
 312        }
 0313        AuthenticationProperties authProps = new(dict);
 0314        return HttpContext.ChallengeAsync(scheme, authProps);
 315    }
 316
 317    /// <summary>
 318    /// Synchronous wrapper for HttpContext.SignOutAsync.
 319    /// </summary>
 320    /// <param name="scheme">The authentication scheme to sign out.</param>
 0321    public void SignOut(string? scheme) => HttpContext.SignOutAsync(scheme).GetAwaiter().GetResult();
 322    /// <summary>
 323    /// Synchronous wrapper for HttpContext.SignOutAsync.
 324    /// </summary>
 325    /// <param name="scheme">The authentication scheme to sign out.</param>
 326    /// <param name="properties">The authentication properties to include in the sign-out.</param>
 327    public void SignOut(string? scheme, AuthenticationProperties? properties)
 328    {
 0329        HttpContext.SignOutAsync(scheme, properties).GetAwaiter().GetResult();
 0330        if (properties != null && !string.IsNullOrWhiteSpace(properties.RedirectUri))
 331        {
 0332            Response.WriteStatusOnly(302);
 333        }
 0334    }
 335
 336    /// <summary>
 337    /// Synchronous wrapper for HttpContext.SignOutAsync using a Hashtable for properties.
 338    /// </summary>
 339    /// <param name="scheme">The authentication scheme to sign out.</param>
 340    /// <param name="properties">The authentication properties to include in the sign-out.</param>
 341    public void SignOut(string? scheme, Hashtable? properties)
 342    {
 0343        AuthenticationProperties? authProps = null;
 344        // Convert Hashtable to Dictionary<string, string?> for AuthenticationProperties
 0345        if (properties is not null)
 346        {
 0347            var dict = new Dictionary<string, string?>();
 348            // Convert each entry in the Hashtable to a string key-value pair
 0349            foreach (DictionaryEntry entry in properties)
 350            {
 0351                dict[entry.Key.ToString()!] = entry.Value?.ToString();
 352            }
 353            // Create AuthenticationProperties from the dictionary
 0354            authProps = new AuthenticationProperties(dict);
 355        }
 356        // Call SignOut with the constructed AuthenticationProperties
 0357        SignOut(scheme, authProps);
 0358    }
 359}