< Summary

Information
Class: Services.ApiServices.ArmtekApi.ArmtekDailyQuotaGuard
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/ArmtekApi/ArmtekDailyQuotaGuard.cs
Line coverage
71%
Covered lines: 15
Uncovered lines: 6
Coverable lines: 21
Total lines: 55
Line coverage: 71.4%
Branch coverage
81%
Covered branches: 13
Total branches: 16
Branch coverage: 81.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
IsBlocked(...)100%22100%
Block(...)100%11100%
IsExceeded(...)78.57%241462.5%
IsExceeded(...)100%11100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/ArmtekApi/ArmtekDailyQuotaGuard.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Services.ApiServices.ArmtekApi.Models.Envelope;
 3
 4namespace Services.ApiServices.ArmtekApi;
 5
 6/// <summary>
 7/// Не даёт повторно обращаться к Armtek одним логином после ответа о суточной квоте.
 8/// Ограничение API сбрасывается на стороне поставщика; короткое окно защищает приложение
 9/// от каскада одинаковых 401, не блокируя интеграцию на весь следующий день.
 10/// </summary>
 11public sealed class ArmtekDailyQuotaGuard
 12{
 113    private static readonly TimeSpan BlockDuration = TimeSpan.FromMinutes(30);
 15714    private readonly ConcurrentDictionary<string, DateTimeOffset> _blockedUntil = new(StringComparer.Ordinal);
 15
 16    public bool IsBlocked(string key) =>
 13117        _blockedUntil.TryGetValue(key, out var until) && until > DateTimeOffset.UtcNow;
 18
 19    public void Block(string key) =>
 420        _blockedUntil[key] = DateTimeOffset.UtcNow.Add(BlockDuration);
 21
 22    public static bool IsExceeded(string? text)
 23    {
 9124        if (string.IsNullOrWhiteSpace(text))
 325            return false;
 26
 8827        if (text.Contains("Превышено количество запросов", StringComparison.OrdinalIgnoreCase) ||
 8828            text.Contains("Превышено суточное количество запросов", StringComparison.OrdinalIgnoreCase))
 1529            return true;
 30
 31        // Armtek API часто возвращает кириллицу в виде unicode escape-последовательностей (\u041f\u0440\u0435\u0432\u04
 7332        if (text.Contains(@"\u041f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e", StringComparison.OrdinalIgnoreCase)
 7333            text.Contains(@"\u043f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e", StringComparison.OrdinalIgnoreCase)
 334            return true;
 35
 7036        if (text.Contains(@"\u", StringComparison.Ordinal))
 37        {
 38            try
 39            {
 040                var unescaped = System.Text.RegularExpressions.Regex.Unescape(text);
 041                return unescaped.Contains("Превышено количество запросов", StringComparison.OrdinalIgnoreCase) ||
 042                       unescaped.Contains("Превышено суточное количество запросов", StringComparison.OrdinalIgnoreCase);
 43            }
 044            catch
 45            {
 46                // Некорректный escape в строке
 047            }
 48        }
 49
 7050        return false;
 051    }
 52
 53    public static bool IsExceeded(IReadOnlyList<ArmtekMessage> messages) =>
 654        messages.Any(message => IsExceeded(message.Text));
 55}