< Summary

Information
Class: Services.ApiServices.ForumAutoApi.ForumAutoHttpClient
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/ForumAutoApi/ForumAutoHttpClient.cs
Line coverage
87%
Covered lines: 36
Uncovered lines: 5
Coverable lines: 41
Total lines: 82
Line coverage: 87.8%
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
.ctor(...)100%11100%
GetAsync()81.25%171682.75%
BuildUri(...)100%11100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/ForumAutoApi/ForumAutoHttpClient.cs

#LineLine coverage
 1using Newtonsoft.Json.Linq;
 2using Services.ApiServices.ApiSupplierRequestLog;
 3
 4namespace Services.ApiServices.ForumAutoApi;
 5
 6/// <summary>Клиент read-only API Forum-Auto v2. Аутентификация — query login/pass в каждом запросе.
 7/// Один и тот же API обслуживает несколько независимых аккаунтов (см. ForumAutoInterpartsHttpClient) —
 8/// httpClientName/providerKey разводят именованный HttpClient и ключ лога между ними.</summary>
 9public class ForumAutoHttpClient
 10{
 11    private readonly IHttpClientFactory _factory;
 12    private readonly ForumAutoConfig _config;
 13    private readonly ApiSupplierRequestLogStore _logs;
 14    private readonly string _httpClientName;
 15    private readonly string _providerKey;
 16
 7817    public ForumAutoHttpClient(IHttpClientFactory factory, ForumAutoConfig config, ApiSupplierRequestLogStore logs,
 7818        string httpClientName = "ForumAutoApi", string providerKey = "ForumAuto")
 19    {
 7820        _factory = factory;
 7821        _config = config;
 7822        _logs = logs;
 7823        _httpClientName = httpClientName;
 7824        _providerKey = providerKey;
 7825    }
 26
 27    public async Task<JToken> GetAsync(string path, IReadOnlyDictionary<string, string?>? query = null, CancellationToke
 28    {
 2329        if (string.IsNullOrWhiteSpace(_config.Login) || string.IsNullOrWhiteSpace(_config.Password))
 030            throw new ForumAutoApiException($"Не настроены {_providerKey}:Login и/или {_providerKey}:Password.");
 31
 2332        var merged = new Dictionary<string, string?>(query ?? new Dictionary<string, string?>())
 2333        {
 2334            ["login"] = _config.Login,
 2335            ["pass"] = _config.Password
 2336        };
 2337        var uri = BuildUri(path, merged);
 38
 2339        var timer = System.Diagnostics.Stopwatch.StartNew();
 40        try
 41        {
 2342            using var response = await _factory.CreateClient(_httpClientName).GetAsync(uri, ct);
 2343            var text = await response.Content.ReadAsStringAsync(ct);
 2344            _logs.Add(_providerKey, HttpMethod.Get.Method, path, (int)response.StatusCode, timer, uri.Query, text);
 45
 2346            if (!response.IsSuccessStatusCode)
 247                throw new ForumAutoApiException($"Forum-Auto вернул HTTP {(int)response.StatusCode}.", (int)response.Sta
 48
 49            JToken token;
 4250            try { token = JToken.Parse(text); }
 051            catch (Exception ex) { throw new ForumAutoApiException("Forum-Auto вернул невалидный JSON.", requestPath: pa
 52
 53            // Forum-Auto возвращает SOAP-Fault-подобную ошибку в теле даже при HTTP 200 —
 54            // проверяем "errors" независимо от статус-кода ответа.
 2155            if (token is JObject obj && obj["errors"] is JObject errors)
 56            {
 257                var faultCode = errors.Value<int?>("FaultCode");
 258                var faultString = errors.Value<string>("FaultString");
 259                var faultDetail = errors.Value<string>("FaultDetail");
 260                var message = string.IsNullOrWhiteSpace(faultString) ? "Forum-Auto вернул ошибку." : faultString;
 461                if (!string.IsNullOrWhiteSpace(faultDetail)) message += $" ({faultDetail})";
 262                throw new ForumAutoApiException(message, (int)response.StatusCode, faultCode, path);
 63            }
 64
 1965            return token;
 66        }
 867        catch (ForumAutoApiException) { throw; }
 068        catch (Exception ex)
 69        {
 070            _logs.Add(_providerKey, HttpMethod.Get.Method, path, null, timer, uri.Query, ex.Message);
 071            throw new ForumAutoApiException("Не удалось выполнить запрос к Forum-Auto.", requestPath: path, innerExcepti
 72        }
 1973    }
 74
 75    private Uri BuildUri(string path, IReadOnlyDictionary<string, string?> query)
 76    {
 2377        var builder = new UriBuilder(_config.BaseUrl.TrimEnd('/') + "/" + path.TrimStart('/'));
 13178        builder.Query = string.Join("&", query.Where(x => !string.IsNullOrWhiteSpace(x.Value))
 11779            .Select(x => $"{Uri.EscapeDataString(x.Key)}={Uri.EscapeDataString(x.Value!)}"));
 2380        return builder.Uri;
 81    }
 82}