< Summary

Information
Class: OzonOrdersWeb.WebServices.SystemAlerts.SystemAlertPinnedMessageService
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/SystemAlerts/SystemAlertPinnedMessageService.cs
Line coverage
87%
Covered lines: 36
Uncovered lines: 5
Coverable lines: 41
Total lines: 85
Line coverage: 87.8%
Branch coverage
90%
Covered branches: 29
Total branches: 32
Branch coverage: 90.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_MessageId()100%11100%
.cctor()100%11100%
SyncAsync()75%5458.33%
SyncChatAsync()96.15%2626100%
IsOurs(...)50%22100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/SystemAlerts/SystemAlertPinnedMessageService.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Services.ApiServices.TelegramBotApi;
 3
 4namespace OzonOrdersWeb.WebServices.SystemAlerts;
 5
 6/// <summary>
 7/// Держит в чатах одно закреплённое сообщение «активные алерты» и обновляет его на месте (editMessageText).
 8/// Когда алертов не осталось, текст меняется на «активных алертов нет» и сообщение откреплается.
 9/// Состояние хранится в памяти; после перезапуска сообщение находится по закрепу чата (getChat), так что
 10/// дубли не появляются и БД не нужна.
 11/// </summary>
 512public sealed class SystemAlertPinnedMessageService(ITelegramNotifier telegram, TelegramBotConfig config,
 513    ILogger<SystemAlertPinnedMessageService> logger)
 14{
 1815    private sealed record ChatState(long MessageId, string LastHtml);
 16
 117    private static readonly ConcurrentDictionary<(long ChatId, int Thread), ChatState> States = new();
 118    private static readonly ConcurrentDictionary<(long ChatId, int Thread), bool> Restored = new();
 19
 20    public async Task SyncAsync(IReadOnlyList<TelegramAlertItem> active, CancellationToken ct = default)
 21    {
 1022        if (!telegram.IsConfigured) return;
 23
 1024        var message = TelegramMessageFactory.ActiveAlerts(active, config.SiteBaseUrl);
 4025        foreach (var route in telegram.RoutesFor(TelegramChannel.Alerts))
 26        {
 27            try
 28            {
 1029                await SyncChatAsync(route, message, active.Count == 0, ct);
 1030            }
 031            catch (OperationCanceledException) when (ct.IsCancellationRequested)
 32            {
 033                throw;
 34            }
 035            catch (Exception ex)
 36            {
 037                logger.LogWarning(ex, "Не удалось обновить закреплённое сообщение алертов в чате {ChatId}", route.ChatId
 038            }
 1039        }
 1040    }
 41
 42    private async Task SyncChatAsync(TelegramRoute route, TelegramMessage message, bool nothingActive, CancellationToken
 43    {
 1044        var chatId = route.ChatId;
 1045        var key = (chatId, route.ThreadId ?? 0);
 1046        if (!States.TryGetValue(key, out var state) && Restored.TryAdd(key, true))
 47        {
 48            // Первый проход после старта: ищем своё закреплённое сообщение, чтобы не плодить новые.
 549            var pinned = await telegram.GetPinnedAsync(chatId, ct);
 550            if (pinned is { FromBot: true } && IsOurs(pinned.Text))
 51            {
 152                state = new ChatState(pinned.MessageId, string.Empty);
 153                States[key] = state;
 54            }
 55        }
 56
 1057        if (nothingActive)
 58        {
 359            if (state == null) return;
 160            if (state.LastHtml != message.Html && await telegram.EditAsync(chatId, state.MessageId, message.Html, ct))
 161                await telegram.UnpinAsync(chatId, state.MessageId, ct);
 162            States.TryRemove(key, out _);
 163            return;
 64        }
 65
 866        if (state != null)
 67        {
 568            if (state.LastHtml == message.Html) return;
 369            if (await telegram.EditAsync(chatId, state.MessageId, message.Html, ct))
 70            {
 271                States[key] = state with { LastHtml = message.Html };
 272                return;
 73            }
 74            // Сообщение удалили вручную или оно недоступно — создадим новое.
 75        }
 76
 577        var id = await telegram.SendToRouteAsync(message with { Silent = true }, route, ct);
 578        if (id is not { } messageId) return;
 579        States[key] = new ChatState(messageId, message.Html);
 580        await telegram.PinAsync(chatId, messageId, silent: true, ct);
 1081    }
 82
 83    private static bool IsOurs(string? text) =>
 184        text != null && text.Contains(TelegramMessageFactory.ActiveAlertsMarker, StringComparison.OrdinalIgnoreCase);
 85}