< Summary

Information
Class: Services.ApiServices.FidApi.FidApiHttpClient
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/FidApi/FidApiHttpClient.cs
Line coverage
90%
Covered lines: 29
Uncovered lines: 3
Coverable lines: 32
Total lines: 73
Line coverage: 90.6%
Branch coverage
91%
Covered branches: 11
Total branches: 12
Branch coverage: 91.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%
GetWithoutEnvelopeAsync()75%5462.5%
SendAsync()100%66100%
BuildUri(...)100%22100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/FidApi/FidApiHttpClient.cs

#LineLine coverage
 1using System.Net;
 2using Newtonsoft.Json;
 3
 4namespace Services.ApiServices.FidApi;
 5
 6/// <summary>Клиент фид-сервиса с ключом доступа.</summary>
 7public sealed class FidApiHttpClient
 8{
 9    private readonly IHttpClientFactory _factory;
 10    private readonly FidApiConfig _config;
 11
 4512    public FidApiHttpClient(IHttpClientFactory factory, FidApiConfig config)
 13    {
 4514        _factory = factory;
 4515        _config = config;
 4516    }
 17
 18    /// <summary>GET с моделью в теле ответа напрямую, без конверта. Возвращает null, если сервис ответил 404.</summary>
 19    public async Task<T?> GetWithoutEnvelopeAsync<T>(string path, IReadOnlyDictionary<string, string?>? query,
 20        CancellationToken ct = default)
 21        where T : class
 22    {
 2623        var (statusCode, text) = await SendAsync(path, query, ct);
 24
 2325        if (statusCode == HttpStatusCode.NotFound) return null;
 26
 1927        if (statusCode != HttpStatusCode.OK)
 028            throw new FidApiException($"Фид-сервис вернул HTTP {(int)statusCode}.", (int)statusCode, path);
 29
 30        try
 31        {
 1932            return JsonConvert.DeserializeObject<T>(text);
 33        }
 034        catch (Exception ex)
 35        {
 036            throw new FidApiException("Фид-сервис вернул невалидный JSON.", (int)statusCode, path, ex);
 37        }
 2138    }
 39
 40    private async Task<(HttpStatusCode StatusCode, string Body)> SendAsync(
 41        string path, IReadOnlyDictionary<string, string?>? query, CancellationToken ct)
 42    {
 2643        if (string.IsNullOrWhiteSpace(_config.ApiKey))
 144            throw new FidApiException("Не настроен FidApi:ApiKey.", requestPath: path);
 45
 2546        using var request = new HttpRequestMessage(HttpMethod.Get, BuildUri(path, query));
 2547        request.Headers.Add("X-Api-Key", _config.ApiKey);
 48
 2549        using var response = await _factory.CreateClient("FidApi").SendAsync(request, ct);
 2350        var text = await response.Content.ReadAsStringAsync(ct);
 51
 2352        if (response.StatusCode == HttpStatusCode.Unauthorized)
 153            throw new FidApiException("Фид-сервис отклонил ключ доступа (401). Проверьте FidApi:ApiKey.",
 154                (int)response.StatusCode, path);
 55
 56        // Для API переадресация означает неверный BaseUrl или прокси.
 2257        if (response.StatusCode is HttpStatusCode.Found or HttpStatusCode.Redirect)
 158            throw new FidApiException($"Фид-сервис перенаправил запрос на форму входа: путь {path} не относится к API.",
 159                (int)response.StatusCode, path);
 60
 2161        return (response.StatusCode, text);
 2162    }
 63
 64    private Uri BuildUri(string path, IReadOnlyDictionary<string, string?>? query)
 65    {
 2566        var builder = new UriBuilder(_config.BaseUrl.TrimEnd('/') + "/" + path.TrimStart('/'));
 2567        if (query != null)
 2268            builder.Query = string.Join("&", query
 2869                .Where(x => !string.IsNullOrWhiteSpace(x.Value))
 4670                .Select(x => $"{Uri.EscapeDataString(x.Key)}={Uri.EscapeDataString(x.Value!)}"));
 2571        return builder.Uri;
 72    }
 73}