< Summary

Information
Class: Services.ProducerAliases.ProducerBrandAliasService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ProducerAliases/ProducerBrandAliasService.cs
Line coverage
100%
Covered lines: 28
Uncovered lines: 0
Coverable lines: 28
Total lines: 65
Line coverage: 100%
Branch coverage
100%
Covered branches: 18
Total branches: 18
Branch coverage: 100%
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%
GetMapAsync()100%44100%
BuildAliasMap(...)100%1414100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ProducerAliases/ProducerBrandAliasService.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using Microsoft.Extensions.Caching.Memory;
 3using OzonRepositories.Context;
 4
 5namespace Services.ProducerAliases;
 6
 7/// <summary>
 8/// Отдаёт словарь алиасов производителей из таблицы OneCProducerAliases. Таблица заводилась для
 9/// дедупликации карточек 1С, но задача та же и в проценке: TecDoc отдаёт «Kayaba», а API
 10/// поставщика — «KYB», и без алиаса предложения отбрасываются как чужой бренд.
 11/// </summary>
 12public sealed class ProducerBrandAliasService
 13{
 14    // Таблица маленькая (десятки строк) и меняется только вручную, а проценка дёргает её
 15    // на каждый запрос цен — поэтому читаем целиком под кэшем, а не по одному алиасу.
 16    private const string CacheKey = "ProducerBrandAliases:AliasNormToCanonicalNorm";
 117    private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(30);
 18
 19    private readonly ApplicationDbContext _context;
 20    private readonly IMemoryCache _cache;
 21
 1022    public ProducerBrandAliasService(ApplicationDbContext context, IMemoryCache cache)
 23    {
 1024        _context = context;
 1025        _cache = cache;
 1026    }
 27
 28    public async Task<ProducerBrandAliasMap> GetMapAsync(CancellationToken cancellationToken = default)
 29    {
 730        if (_cache.TryGetValue(CacheKey, out ProducerBrandAliasMap? cached) && cached != null)
 231            return cached;
 32
 533        var rows = await _context.OneCProducerAliases
 534            .AsNoTracking()
 535            .Select(x => new { x.AliasNorm, x.CanonicalName })
 536            .ToListAsync(cancellationToken);
 37
 838        var map = new ProducerBrandAliasMap(BuildAliasMap(rows.Select(x => (x.AliasNorm, x.CanonicalName))));
 439        _cache.Set(CacheKey, map, CacheTtl);
 440        return map;
 641    }
 42
 43    internal static IReadOnlyDictionary<string, string> BuildAliasMap(IEnumerable<(string AliasNorm, string? CanonicalNa
 44    {
 1945        var direct = new Dictionary<string, string>(StringComparer.Ordinal);
 11846        foreach (var (aliasNorm, canonicalName) in rows)
 47        {
 4048            var alias = ProducerBrandAliasMap.Normalize(aliasNorm);
 4049            var canonical = ProducerBrandAliasMap.Normalize(canonicalName);
 4050            if (alias.Length == 0 || canonical.Length == 0 || alias == canonical)
 51                continue;
 2852            direct[alias] = canonical;
 53        }
 54
 55        // Цепочки вида A→B→C схлопываем заранее, чтобы Canonicalize оставался одним поиском.
 56        // Лимит шагов защищает от кольца, которое можно завести руками в админке.
 4657        return direct.ToDictionary(pair => pair.Key, pair =>
 1958        {
 2759            var canonical = pair.Value;
 13260            for (var step = 0; step < 5 && direct.TryGetValue(canonical, out var next) && next != canonical; step++)
 3961                canonical = next;
 2762            return canonical;
 1963        }, StringComparer.Ordinal);
 64    }
 65}