< Summary

Information
Class: Services.CacheServices.CommercialCategoryLookup.CommercialCategoryLookupService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/CacheServices/CommercialCategoryLookup/CommercialCategoryLookupService.cs
Line coverage
89%
Covered lines: 34
Uncovered lines: 4
Coverable lines: 38
Total lines: 87
Line coverage: 89.4%
Branch coverage
72%
Covered branches: 13
Total branches: 18
Branch coverage: 72.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%
SearchAsync()70%111078.57%
GetAllDistinctAsync()75%8894.11%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/CacheServices/CommercialCategoryLookup/CommercialCategoryLookupService.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using Microsoft.Extensions.Caching.Memory;
 3using OzonRepositories.Context;
 4
 5namespace Services.CacheServices.CommercialCategoryLookup;
 6
 7public sealed class CommercialCategoryLookupService : ICommercialCategoryLookupService
 8{
 9    private const string CacheKey = "dict_commercial_categories_distinct_v1";
 110    private static readonly TimeSpan CacheTtl = TimeSpan.FromHours(24);
 111    private static readonly SemaphoreSlim LoadGate = new(1, 1);
 12
 13    private readonly ApplicationDbContext _context;
 14    private readonly IMemoryCache _memoryCache;
 15
 216    public CommercialCategoryLookupService(ApplicationDbContext context, IMemoryCache memoryCache)
 17    {
 218        _context = context;
 219        _memoryCache = memoryCache;
 220    }
 21
 22    public async Task<IReadOnlyList<string>> SearchAsync(
 23        string? query,
 24        int limit,
 25        CancellationToken cancellationToken = default)
 26    {
 327        if (limit < 1)
 28        {
 029            limit = 1;
 30        }
 31
 332        if (limit > 200)
 33        {
 034            limit = 200;
 35        }
 36
 337        var all = await GetAllDistinctAsync(cancellationToken);
 338        if (all.Count == 0)
 39        {
 040            return Array.Empty<string>();
 41        }
 42
 343        var normalizedQuery = (query ?? string.Empty).Trim();
 344        if (normalizedQuery.Length == 0)
 45        {
 246            return all.Take(limit).ToList();
 47        }
 48
 149        return all
 350            .Where(c => c.Contains(normalizedQuery, StringComparison.OrdinalIgnoreCase))
 151            .Take(limit)
 152            .ToList();
 353    }
 54
 55    private async Task<IReadOnlyList<string>> GetAllDistinctAsync(CancellationToken cancellationToken)
 56    {
 357        if (_memoryCache.TryGetValue(CacheKey, out IReadOnlyList<string>? cached) && cached is not null)
 58        {
 159            return cached;
 60        }
 61
 262        await LoadGate.WaitAsync(cancellationToken);
 63        try
 64        {
 265            if (_memoryCache.TryGetValue(CacheKey, out cached) && cached is not null)
 66            {
 067                return cached;
 68            }
 69
 270            var categories = await _context.Products
 271                .AsNoTracking()
 272                .Where(p => p.CommercialCategory != null && p.CommercialCategory != "")
 273                .Select(p => p.CommercialCategory!)
 274                .Distinct()
 275                .OrderBy(c => c)
 276                .ToListAsync(cancellationToken);
 77
 278            IReadOnlyList<string> snapshot = categories;
 279            _memoryCache.Set(CacheKey, snapshot, CacheTtl);
 280            return snapshot;
 81        }
 82        finally
 83        {
 284            LoadGate.Release();
 85        }
 386    }
 87}