< Summary

Information
Class: Services.OzonWebhookServices.Monitoring.OzonWebhookEndpointState
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/OzonWebhookServices/Monitoring/OzonWebhookAvailabilityModels.cs
Line coverage
100%
Covered lines: 15
Uncovered lines: 0
Coverable lines: 15
Total lines: 147
Line coverage: 100%
Branch coverage
80%
Covered branches: 8
Total branches: 10
Branch coverage: 80%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Id()100%11100%
get_Url()100%11100%
get_Availability()100%11100%
get_RawStatus()100%11100%
get_StatusDateUtc()100%11100%
get_Enabled()100%11100%
get_DisableReason()100%11100%
get_ReasonDetails()100%11100%
get_ProblematicType()100%11100%
get_Types()100%11100%
get_Monitored()100%11100%
get_IsChecking()50%22100%
get_IsProblem()87.5%88100%
get_DisplayUrl()100%11100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/OzonWebhookServices/Monitoring/OzonWebhookAvailabilityModels.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4
 5namespace Services.OzonWebhookServices.Monitoring
 6{
 7    /// <summary>Статус доступности URL пуш-уведомлений по версии мониторинга Ozon.</summary>
 8    public enum OzonWebhookAvailability
 9    {
 10        /// <summary>Нет данных или адрес «На проверке» — не считаем проблемой.</summary>
 11        Unknown,
 12        /// <summary>GREEN: отвечает до 1500 мс.</summary>
 13        Available,
 14        /// <summary>YELLOW: отвечает 1500–2500 мс.</summary>
 15        Unstable,
 16        /// <summary>RED: отвечает дольше 2500 мс или не отвечает.</summary>
 17        Unavailable
 18    }
 19
 20    /// <summary>Подключённый URL пуш-уведомлений кабинета.</summary>
 21    public sealed class OzonWebhookEndpointState
 22    {
 1823        public long Id { get; init; }
 4924        public string Url { get; init; } = string.Empty;
 7725        public OzonWebhookAvailability Availability { get; init; }
 26        /// <summary>Сырое значение availability_status из Ozon.</summary>
 1627        public string? RawStatus { get; init; }
 1628        public DateTime? StatusDateUtc { get; init; }
 29        /// <summary>false — Ozon (или мы) отключили отправку на этот адрес.</summary>
 6130        public bool Enabled { get; init; }
 2831        public string? DisableReason { get; init; }
 2732        public string? ReasonDetails { get; init; }
 1633        public string? ProblematicType { get; init; }
 3534        public IReadOnlyList<string> Types { get; init; } = Array.Empty<string>();
 35
 36        /// <summary>
 37        /// Адрес принадлежит этому приложению (хост из <c>OzonWebhook:MonitoredHosts</c>). Чужие адреса кабинета
 38        /// (другие сервисы, другие сайты) показываем, но проблемой не считаем — исправить их здесь нельзя.
 39        /// </summary>
 14040        public bool Monitored { get; init; } = true;
 41
 42        /// <summary>
 43        /// Адрес «На проверке»: недавно менялся или подключался, статус появится после ближайшей проверки Ozon.
 44        /// Это не сбой — показываем жёлтым, пока не станет известен результат.
 45        /// </summary>
 5146        public bool IsChecking => Monitored && Availability == OzonWebhookAvailability.Unknown;
 47
 48        /// <summary>Наш адрес с известным плохим состоянием: отключён, нестабилен или недоступен.</summary>
 4849        public bool IsProblem => Monitored && !IsChecking &&
 4850            (!Enabled || Availability is OzonWebhookAvailability.Unstable or OzonWebhookAvailability.Unavailable);
 51
 52        /// <summary>Адрес без query-строки: в нём лежит секретный <c>token</c>, в интерфейс и Telegram его не выводим.<
 1353        public string DisplayUrl => OzonWebhookAvailabilitySnapshot.StripQuery(Url);
 54    }
 55
 56    /// <summary>Состояние пуш-уведомлений одного кабинета Ozon.</summary>
 57    public sealed class OzonWebhookCabinetState
 58    {
 59        public int ClientId { get; init; }
 60        public string ClientName { get; init; } = string.Empty;
 61        public IReadOnlyList<OzonWebhookEndpointState> Endpoints { get; init; } = Array.Empty<OzonWebhookEndpointState>(
 62        /// <summary>Не удалось получить список адресов (ошибка API) — состояние неизвестно.</summary>
 63        public string? Error { get; init; }
 64
 65        /// <summary>У кабинета нет нашего адреса (информативно; проблемой не считается — пуши могут быть не нужны).</su
 66        public bool NoMonitoredEndpoint => Error == null && !Endpoints.Any(e => e.Monitored);
 67
 68        public bool HasProblem => Endpoints.Any(e => e.IsProblem);
 69
 70        public bool HasChecking => Endpoints.Any(e => e.IsChecking);
 71    }
 72
 73    /// <summary>Результат последней проверки всех кабинетов.</summary>
 74    public sealed class OzonWebhookAvailabilitySnapshot
 75    {
 76        public static readonly OzonWebhookAvailabilitySnapshot Empty = new();
 77
 78        public DateTime? CheckedAtUtc { get; init; }
 79        public IReadOnlyList<OzonWebhookCabinetState> Cabinets { get; init; } = Array.Empty<OzonWebhookCabinetState>();
 80
 81        public bool HasProblem => Cabinets.Any(c => c.HasProblem);
 82
 83        /// <summary>Есть адреса «На проверке» (жёлтое предупреждение).</summary>
 84        public bool HasChecking => Cabinets.Any(c => c.HasChecking);
 85
 86        /// <summary>Короткие строки для баннера и Telegram: что именно не так.</summary>
 87        public IReadOnlyList<string> DescribeProblems()
 88        {
 89            var lines = new List<string>();
 90            foreach (var cabinet in Cabinets.Where(c => c.HasProblem))
 91            {
 92                foreach (var endpoint in cabinet.Endpoints.Where(e => e.IsProblem))
 93                    lines.Add($"{cabinet.ClientName}: {DescribeEndpoint(endpoint)}");
 94            }
 95
 96            return lines;
 97        }
 98
 99        /// <summary>Адреса «На проверке» — для жёлтого предупреждения.</summary>
 100        public IReadOnlyList<string> DescribeChecking()
 101        {
 102            var lines = new List<string>();
 103            foreach (var cabinet in Cabinets.Where(c => c.HasChecking))
 104            {
 105                foreach (var endpoint in cabinet.Endpoints.Where(e => e.IsChecking))
 106                {
 107                    var disabled = !endpoint.Enabled
 108                        ? $", отправка отключена ({ReasonText(endpoint.DisableReason)}) — включите адрес в кабинете Ozon
 109                        : string.Empty;
 110                    lines.Add($"{cabinet.ClientName}: {endpoint.DisplayUrl} — на проверке{disabled}");
 111                }
 112            }
 113
 114            return lines;
 115        }
 116
 117        /// <summary>Стабильная подпись набора проблем — чтобы слать Telegram только при изменении.</summary>
 118        public string ProblemSignature() => string.Join("|", DescribeProblems().OrderBy(x => x, StringComparer.Ordinal))
 119
 120        public static string DescribeEndpoint(OzonWebhookEndpointState e)
 121        {
 122            var state = !e.Enabled
 123                ? $"отключён ({ReasonText(e.DisableReason)})"
 124                : e.Availability == OzonWebhookAvailability.Unavailable ? "недоступен (ответ дольше 2500 мс)" : "нестаби
 125            var details = string.IsNullOrWhiteSpace(e.ReasonDetails) ? string.Empty : $": {e.ReasonDetails}";
 126            return $"{e.DisplayUrl} — {state}{details}";
 127        }
 128
 129        /// <summary>Человекочитаемая причина отключения из disable_reason Ozon.</summary>
 130        public static string ReasonText(string? reason) => reason?.Trim().ToUpperInvariant() switch
 131        {
 132            "AVAILABILITY" => "Ozon отключил из-за недоступности",
 133            "SELLER" => "отключено продавцом",
 134            null or "" or "UNSPECIFIED" => "причина не указана",
 135            _ => reason!
 136        };
 137
 138        /// <summary>Убирает query-строку и фрагмент (секретный token) из адреса.</summary>
 139        public static string StripQuery(string? url)
 140        {
 141            if (string.IsNullOrEmpty(url))
 142                return string.Empty;
 143            var cut = url.IndexOfAny(['?', '#']);
 144            return cut < 0 ? url : url[..cut];
 145        }
 146    }
 147}