< Summary

Line coverage
96%
Covered lines: 53
Uncovered lines: 2
Coverable lines: 55
Total lines: 235
Line coverage: 96.3%
Branch coverage
86%
Covered branches: 31
Total branches: 36
Branch coverage: 86.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: .cctor()100%11100%
File 1: .ctor(...)50%44100%
File 1: HandleAsync()87.5%242494.11%
File 1: GetUpdateType()100%22100%
File 1: IsSecretTokenAccepted(...)100%66100%
File 2: .cctor()100%11100%

File(s)

/_/src/TeleFlow.Telegram.Webhooks/Internal/TelegramRawWebhookEndpoint.cs

#LineLine coverage
 1using System.Text.Json;
 2using Microsoft.AspNetCore.Http;
 3using Microsoft.Extensions.DependencyInjection;
 4using Microsoft.Extensions.Logging;
 5using TeleFlow.Telegram.Schema.Types;
 6
 7namespace TeleFlow.Telegram.Webhooks.Internal;
 8
 9/// <summary>
 10/// Handles raw ASP.NET Core webhook requests, validates Telegram webhook security metadata,
 11/// deserializes updates, and passes accepted updates to the user-provided webhook handler.
 12/// </summary>
 13internal sealed partial class TelegramRawWebhookEndpoint
 14{
 15    private const string SecretTokenHeaderName = "X-Telegram-Bot-Api-Secret-Token";
 16
 17    private const int SecretTokenRejectedEventId = 1;
 18    private const int InvalidPayloadRejectedEventId = 2;
 19    private const int UpdateReceivedEventId = 3;
 20    private const int UpdateProcessedEventId = 4;
 21    private const int UpdateProcessingFailedEventId = 5;
 22
 123    private static readonly TelegramJsonOptions DefaultJsonOptions = TelegramJsonOptions.CreateDefault();
 24
 25    private readonly TelegramRawWebhookHandler _handler;
 26    private readonly TelegramRawWebhookOptions _options;
 27    private readonly ILogger<TelegramRawWebhookEndpoint>? _logger;
 28
 29    public TelegramRawWebhookEndpoint(
 30        TelegramRawWebhookHandler handler,
 31        TelegramRawWebhookOptions options,
 32        ILogger<TelegramRawWebhookEndpoint>? logger = null)
 33    {
 2234        _handler = handler ?? throw new ArgumentNullException(nameof(handler));
 2235        _options = options ?? throw new ArgumentNullException(nameof(options));
 2236        _logger = logger;
 2237    }
 38
 39    public async Task<IResult> HandleAsync(HttpContext context)
 40    {
 2141        ArgumentNullException.ThrowIfNull(context);
 42
 2143        if (!IsSecretTokenAccepted(context))
 44        {
 545            if (_logger is not null)
 46            {
 547                LogSecretTokenRejected(_logger, _options.SecretTokenFailureStatusCode);
 48            }
 49
 550            return Results.StatusCode(_options.SecretTokenFailureStatusCode);
 51        }
 52
 1653        var jsonOptions = context.RequestServices.GetService<TelegramJsonOptions>() ?? DefaultJsonOptions;
 54        Update? update;
 55
 56        try
 57        {
 1658            update = await JsonSerializer.DeserializeAsync<Update>(
 1659                context.Request.Body,
 1660                jsonOptions.SerializerOptions,
 1661                context.RequestAborted).ConfigureAwait(false);
 1262        }
 463        catch (JsonException)
 64        {
 465            if (_logger is not null)
 66            {
 467                LogInvalidPayloadRejected(_logger, _options.InvalidPayloadStatusCode);
 68            }
 69
 470            return Results.StatusCode(_options.InvalidPayloadStatusCode);
 71        }
 72
 1273        if (update is null)
 74        {
 175            if (_logger is not null)
 76            {
 177                LogInvalidPayloadRejected(_logger, _options.InvalidPayloadStatusCode);
 78            }
 79
 180            return Results.StatusCode(_options.InvalidPayloadStatusCode);
 81        }
 82
 1183        string? updateType = null;
 84        string GetUpdateType()
 85        {
 486            return updateType ??= TelegramWebhookUpdateLogFormatter.GetUpdateType(update);
 87        }
 88
 1189        if (_logger?.IsEnabled(LogLevel.Debug) == true)
 90        {
 191            LogUpdateReceived(_logger, update.UpdateId, GetUpdateType());
 92        }
 93
 1194        var bot = context.RequestServices.GetRequiredService<ITelegramClient>();
 95        try
 96        {
 1197            var result = await _handler(update, bot, context.RequestAborted).ConfigureAwait(false);
 98
 999            if (_logger?.IsEnabled(LogLevel.Debug) == true)
 100            {
 1101                LogUpdateProcessed(_logger, update.UpdateId, GetUpdateType());
 102            }
 103
 9104            return result;
 105        }
 0106        catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
 107        {
 0108            throw;
 109        }
 2110        catch (Exception exception)
 111        {
 2112            if (_logger?.IsEnabled(LogLevel.Error) == true)
 113            {
 2114                LogUpdateProcessingFailed(_logger, exception, update.UpdateId, GetUpdateType());
 115            }
 116
 2117            throw;
 118        }
 19119    }
 120
 121    private bool IsSecretTokenAccepted(HttpContext context)
 122    {
 21123        if (_options.SecretToken is null)
 124        {
 13125            return true;
 126        }
 127
 8128        return context.Request.Headers.TryGetValue(SecretTokenHeaderName, out var values) &&
 8129            values.Count == 1 &&
 8130            string.Equals(values[0], _options.SecretToken, StringComparison.Ordinal);
 131    }
 132
 133    [LoggerMessage(
 134        EventId = SecretTokenRejectedEventId,
 135        Level = LogLevel.Warning,
 136        Message = "Telegram webhook request rejected because secret token validation failed. status={StatusCode}.")]
 137    private static partial void LogSecretTokenRejected(
 138        ILogger logger,
 139        int statusCode);
 140
 141    [LoggerMessage(
 142        EventId = InvalidPayloadRejectedEventId,
 143        Level = LogLevel.Warning,
 144        Message = "Telegram webhook request rejected because payload was invalid. status={StatusCode}.")]
 145    private static partial void LogInvalidPayloadRejected(
 146        ILogger logger,
 147        int statusCode);
 148
 149    [LoggerMessage(
 150        EventId = UpdateReceivedEventId,
 151        Level = LogLevel.Debug,
 152        Message = "Telegram webhook update received. update_id={UpdateId}, type={UpdateType}.")]
 153    private static partial void LogUpdateReceived(
 154        ILogger logger,
 155        long updateId,
 156        string updateType);
 157
 158    [LoggerMessage(
 159        EventId = UpdateProcessedEventId,
 160        Level = LogLevel.Debug,
 161        Message = "Telegram webhook update processed. update_id={UpdateId}, type={UpdateType}.")]
 162    private static partial void LogUpdateProcessed(
 163        ILogger logger,
 164        long updateId,
 165        string updateType);
 166
 167    [LoggerMessage(
 168        EventId = UpdateProcessingFailedEventId,
 169        Level = LogLevel.Error,
 170        Message = "Telegram webhook update processing failed. update_id={UpdateId}, type={UpdateType}.")]
 171    private static partial void LogUpdateProcessingFailed(
 172        ILogger logger,
 173        Exception exception,
 174        long updateId,
 175        string updateType);
 176}

/_/src/TeleFlow.Telegram.Webhooks/obj/Release/net10.0/Microsoft.Extensions.Logging.Generators/Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator/LoggerMessage.g.cs

File '/_/src/TeleFlow.Telegram.Webhooks/obj/Release/net10.0/Microsoft.Extensions.Logging.Generators/Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator/LoggerMessage.g.cs' does not exist (any more).