Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

BaseNopTest.cs

using System.ComponentModel;
using System.Globalization;
using System.Resources;
using FluentAssertions;
using FluentMigrator;
using FluentMigrator.Runner;
using FluentMigrator.Runner.Conventions;
using FluentMigrator.Runner.Generators;
using FluentMigrator.Runner.Initialization;
using FluentMigrator.Runner.Processors;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Moq;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.ComponentModel;
using Nop.Core.Configuration;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Media;
using Nop.Core.Events;
using Nop.Core.Infrastructure;
using Nop.Data;
using Nop.Data.Configuration;
using Nop.Data.Migrations;
using Nop.Services.Affiliates;
using Nop.Services.Attributes;
using Nop.Services.Authentication.External;
using Nop.Services.Authentication.MultiFactor;
using Nop.Services.Blogs;
using Nop.Services.Caching;
using Nop.Services.Catalog;
using Nop.Services.Cms;
using Nop.Services.Common;
using Nop.Services.Configuration;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Discounts;
using Nop.Services.Events;
using Nop.Services.ExportImport;
using Nop.Services.Forums;
using Nop.Services.Gdpr;
using Nop.Services.Helpers;
using Nop.Services.Html;
using Nop.Services.Installation;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Media;
using Nop.Services.Messages;
using Nop.Services.News;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Plugins;
using Nop.Services.Polls;
using Nop.Services.ScheduleTasks;
using Nop.Services.Security;
using Nop.Services.Seo;
using Nop.Services.Shipping;
using Nop.Services.Shipping.Date;
using Nop.Services.Shipping.Pickup;
using Nop.Services.Stores;
using Nop.Services.Tax;
using Nop.Services.Themes;
using Nop.Services.Topics;
using Nop.Services.Vendors;
using Nop.Tests.Nop.Services.Tests.ScheduleTasks;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Framework;
using Nop.Web.Framework.Factories;
using Nop.Web.Framework.Models;
using Nop.Web.Framework.Mvc.Routing;
using Nop.Web.Framework.Themes;
using Nop.Web.Framework.UI;
using Nop.Web.Framework.WebOptimizer;
using Nop.Web.Infrastructure.Installation;
using SkiaSharp;
using IAuthenticationService = Nop.Services.Authentication.IAuthenticationService;
using Task = System.Threading.Tasks.Task;

namespace Nop.Tests;

public partial class BaseNopTest
{
    private static readonly ServiceProvider _serviceProvider;
    private static readonly ResourceManager _resourceManager;

    protected BaseNopTest()
    {
        SetDataProviderType(DataProviderType.Unknown);
    }

    public ServiceProvider ServiceProvider => _serviceProvider;

    private static void Init()
    {
        var dataProvider = _serviceProvider.GetService().DataProvider;

        dataProvider.CreateDatabase(null);
        dataProvider.InitializeDatabase();

        var languagePackInfo = (DownloadUrl: string.Empty, Progress: 0);

        var cultureInfo = new CultureInfo(NopCommonDefaults.DefaultLanguageCulture);
        var regionInfo = new RegionInfo(NopCommonDefaults.DefaultLanguageCulture);

        _serviceProvider.GetService()
            .InstallRequiredDataAsync(NopTestsDefaults.AdminEmail, NopTestsDefaults.AdminPassword, languagePackInfo, regionInfo, cultureInfo).Wait();
        _serviceProvider.GetService().InstallSampleDataAsync(NopTestsDefaults.AdminEmail).Wait();

        var provider = (IPermissionProvider)Activator.CreateInstance(typeof(StandardPermissionProvider));
        EngineContext.Current.Resolve().InstallPermissionsAsync(provider).Wait();
    }

    protected static T PropertiesShouldEqual(T entity, Tm model, params string[] filter) where T : BaseEntity
        where Tm : BaseNopModel
    {
        var objectProperties = typeof(T).GetProperties();
        var modelProperties = typeof(Tm).GetProperties();

        foreach (var objectProperty in objectProperties)
        {
            var name = objectProperty.Name;

            if (filter.Contains(name))
                continue;

            var modelProperty = Array.Find(modelProperties, p => p.Name == name);

            if (modelProperty == null)
                continue;

            var objectPropertyValue = objectProperty.GetValue(entity);
            var modelPropertyValue = modelProperty.GetValue(model);

            objectPropertyValue.Should().Be(modelPropertyValue, $"The property \"{typeof(T).Name}.{objectProperty.Name}\" of these objects is not equal");
        }

        return entity;
    }

    static BaseNopTest()
    {
        _resourceManager = Connections.ResourceManager;
        SetDataProviderType(DataProviderType.Unknown);

        TypeDescriptor.AddAttributes(typeof(List),
            new TypeConverterAttribute(typeof(GenericListTypeConverter)));
        TypeDescriptor.AddAttributes(typeof(List),
            new TypeConverterAttribute(typeof(GenericListTypeConverter)));

        var services = new ServiceCollection();

        var rootPath =
            new DirectoryInfo(
                    $"{Directory.GetCurrentDirectory().Split("bin")[0]}{Path.Combine([.. @"\..\..\Presentation\Nop.Web".Split('\\', '/')])}")
                .FullName;

        //Presentation\Nop.Web\wwwroot
        var webHostEnvironment = new Mock();
        webHostEnvironment.Setup(p => p.WebRootPath).Returns(Path.Combine(rootPath, "wwwroot"));
        webHostEnvironment.Setup(p => p.ContentRootPath).Returns(rootPath);
        webHostEnvironment.Setup(p => p.EnvironmentName).Returns("test");
        webHostEnvironment.Setup(p => p.ApplicationName).Returns("nopCommerce");
        services.AddSingleton(webHostEnvironment.Object);

        //file provider
        services.AddTransient();
        CommonHelper.DefaultFileProvider = new NopFileProvider(webHostEnvironment.Object);

        services.AddHttpClient();

        var memoryCache = new MemoryCache(new MemoryCacheOptions());
        var typeFinder = new WebAppTypeFinder();
        Singleton.Instance = typeFinder;

        var mAssemblies = typeFinder.FindClassesOfType()
            .Select(t => t.Assembly)
            .Distinct()
            .ToArray();

        //create app settings
        var configurations = typeFinder
            .FindClassesOfType()
            .Select(configType => (IConfig)Activator.CreateInstance(configType))
            .ToList();

        var appSettings = new AppSettings(configurations);
        appSettings.Update(new List { Singleton.Instance });
        Singleton.Instance = appSettings;
        services.AddSingleton(appSettings);

        var hostApplicationLifetime = new Mock();
        services.AddSingleton(hostApplicationLifetime.Object);
            
        services.AddWebEncoders();

        var httpContext = new DefaultHttpContext();
        httpContext.Request.Headers.Append(HeaderNames.Host, NopTestsDefaults.HostIpAddress);

        var httpContextAccessor = new Mock();
        httpContextAccessor.Setup(p => p.HttpContext).Returns(httpContext);

        services.AddSingleton(httpContextAccessor.Object);

        var actionContextAccessor = new Mock();
        actionContextAccessor.Setup(x => x.ActionContext)
            .Returns(new ActionContext(httpContext, httpContext.GetRouteData(), new ActionDescriptor()));

        services.AddSingleton(actionContextAccessor.Object);

        var urlHelperFactory = new Mock();
        var urlHelper = new NopTestUrlHelper(actionContextAccessor.Object.ActionContext);

        urlHelperFactory.Setup(x => x.GetUrlHelper(It.IsAny()))
            .Returns(urlHelper);

        services.AddTransient(_ => actionContextAccessor.Object);

        services.AddSingleton(urlHelperFactory.Object);

        var tempDataDictionaryFactory = new Mock();
        var dataDictionary = new TempDataDictionary(httpContextAccessor.Object.HttpContext,
            new Mock().Object);
        tempDataDictionaryFactory.Setup(f => f.GetTempData(It.IsAny())).Returns(dataDictionary);
        services.AddSingleton(tempDataDictionaryFactory.Object);

        services.AddSingleton(typeFinder);
        Singleton.Instance = typeFinder;
            
        //web helper
        services.AddTransient();

        //user agent helper
        services.AddTransient();

        //data layer
        services.AddSingleton();
        services.AddSingleton(serviceProvider =>
            serviceProvider.GetRequiredService().DataProvider);

        //repositories
        services.AddTransient(typeof(IRepository<>), typeof(EntityRepository<>));

        //plugins
        services.AddTransient();

        services.AddScoped();

        services.AddSingleton();
        services.AddSingleton(memoryCache);
        services.AddSingleton();
        services.AddSingleton();
        services.AddSingleton();

        services.AddTransient(typeof(IConcurrentCollection<>), typeof(ConcurrentTrie<>));

        var memoryDistributedCache = new MemoryDistributedCache(new TestMemoryDistributedCacheoptions());
        services.AddSingleton(memoryDistributedCache);
        services.AddScoped();
        services.AddSingleton(new DistributedCacheLocker(memoryDistributedCache));

        //services
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();

        //attribute services
        services.AddScoped(typeof(IAttributeService<,>), typeof(AttributeService<,>));

        //attribute parsers
        services.AddScoped(typeof(IAttributeParser<,>), typeof(AttributeParser<,>));

        //attribute formatter
        services.AddScoped(typeof(IAttributeFormatter<,>), typeof(AttributeFormatter<,>));

        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient(typeof(Lazy));
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddScoped();
        services.AddScoped();

        services.AddScoped();

        //slug route transformer
        services.AddSingleton();
        services.AddSingleton();
        services.AddTransient();

        //plugin managers
        services.AddTransient(typeof(IPluginManager<>), typeof(PluginManager<>));
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddScoped();

        services.AddTransient();
        services.AddScoped();
        services.AddScoped();

        //register all settings
        var settings = typeFinder.FindClassesOfType(typeof(ISettings), false).ToList();
        foreach (var setting in settings)
        {
            services.AddTransient(setting,
                context => context.GetRequiredService().LoadSettingAsync(setting).Result);


        }

        //event consumers
        foreach (var consumer in typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList())
        {
            var interfaces = consumer.FindInterfaces((type, criteria) => type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition()), typeof(IConsumer<>));
            foreach (var findInterface in interfaces)
            {
                services.AddTransient(findInterface, consumer);
            }
        }

        services.AddSingleton();
        services.AddTransient(p => new Lazy(p.GetRequiredService()));

        services
            // add common FluentMigrator services
            .AddFluentMigratorCore()
            .AddScoped()
            // set accessor for the connection string
            .AddScoped(_ => DataSettingsManager.LoadSettings())
            .AddScoped()
            .AddSingleton()
            .ConfigureRunner(rb =>
                rb.WithVersionTable(new MigrationVersionInfo()).AddSqlServer().AddMySql5().AddPostgres().AddSQLite()
                    // define the assembly containing the migrations
                    .ScanIn(mAssemblies).For.Migrations());

        services.AddOptions().Configure(go => go.CompatibilityMode = CompatibilityMode.LOOSE);

        services.AddTransient();
        services.AddTransient>();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient>();
        services.AddTransient();

        //schedule tasks
        services.AddSingleton();
        services.AddTransient();

        //WebOptimizer
        services.AddWebOptimizer();

        //common factories
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();

        //admin factories
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services
            .AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();

        //factories
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services
            .AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services
            .AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();

        _serviceProvider = services.BuildServiceProvider();

        EngineContext.Replace(new NopTestEngine(_serviceProvider));

        Init();
    }

    protected static T GetService()
    {
        return _serviceProvider.GetRequiredService();
    }

    protected static T GetService(IServiceScope scope)
    {
        return scope.ServiceProvider.GetService();
    }

    public async Task TestCrud(TEntity baseEntity, Func insert, TEntity updateEntity, Func update, Func> getById, Func equals, Func delete) where TEntity : BaseEntity
    {
        baseEntity.Id = 0;

        await insert(baseEntity);
        baseEntity.Id.Should().BeGreaterThan(0);

        updateEntity.Id = baseEntity.Id;
        await update(updateEntity);

        var item = await getById(baseEntity.Id);
        item.Should().NotBeNull();
        equals(updateEntity, item).Should().BeTrue();

        await delete(baseEntity);
        item = await getById(baseEntity.Id);
        item.Should().BeNull();
    }

    public static bool SetDataProviderType(DataProviderType type)
    {
        var dataConfig = Singleton.Instance ?? new DataConfig();

        dataConfig.DataProvider = type;
        dataConfig.ConnectionString = string.Empty;

        try
        {
            switch (type)
            {
                case DataProviderType.SqlServer:
                    dataConfig.ConnectionString = _resourceManager.GetString("sql server connection string");
                    break;
                case DataProviderType.MySql:
                    dataConfig.ConnectionString = _resourceManager.GetString("MySql server connection string");
                    break;
                case DataProviderType.PostgreSQL:
                    dataConfig.ConnectionString = _resourceManager.GetString("PostgreSql server connection string");
                    break;
                case DataProviderType.Unknown:
                    dataConfig.ConnectionString = "Data Source=nopCommerceTest.sqlite;Mode=Memory;Cache=Shared";
                    break;
            }
        }
        catch (MissingManifestResourceException)
        {
            //ignore
        }

        Singleton.Instance = dataConfig;
        var flag = !string.IsNullOrEmpty(dataConfig.ConnectionString);

        if (Singleton.Instance == null)
            return flag;

        Singleton.Instance.Update(new List { Singleton.Instance });

        return flag;
    }

    #region Nested classes

    protected class NopTestUrlHelper : UrlHelperBase
    {
        public NopTestUrlHelper(ActionContext actionContext) : base(actionContext)
        {
        }

        public override string Action(UrlActionContext actionContext)
        {
            return string.Empty;
        }

        public override string RouteUrl(UrlRouteContext routeContext)
        {
            return string.Empty;
        }
    }

    protected class NopTestConventionSet : NopConventionSet
    {
        public NopTestConventionSet(INopDataProvider dataProvider) : base(dataProvider)
        {
        }
    }

    public partial class NopTestEngine : NopEngine
    {
        protected readonly IServiceProvider _internalServiceProvider;

        public NopTestEngine(IServiceProvider serviceProvider)
        {
            _internalServiceProvider = serviceProvider;
        }

        public override IServiceProvider ServiceProvider => _internalServiceProvider;
    }

    public class TestAuthenticationService : IAuthenticationService
    {
        public Task SignInAsync(Customer customer, bool isPersistent)
        {
            return Task.CompletedTask;
        }

        public Task SignOutAsync()
        {
            return Task.CompletedTask;
        }

        public async Task GetAuthenticatedCustomerAsync()
        {
            return await _serviceProvider.GetService().GetCustomerByEmailAsync(NopTestsDefaults.AdminEmail);
        }
    }

    protected class TestPictureService : PictureService
    {
        public TestPictureService(IDownloadService downloadService,
            IHttpContextAccessor httpContextAccessor, ILogger logger, INopFileProvider fileProvider,
            IProductAttributeParser productAttributeParser, IProductAttributeService productAttributeService,
            IRepository pictureRepository, IRepository pictureBinaryRepository,
            IRepository productPictureRepository, ISettingService settingService,
            IUrlRecordService urlRecordService, IWebHelper webHelper, MediaSettings mediaSettings) : base(
            downloadService, httpContextAccessor, logger, fileProvider, productAttributeParser, productAttributeService,
            pictureRepository, pictureBinaryRepository, productPictureRepository, settingService, urlRecordService,
            webHelper, mediaSettings)
        {
        }

        // Travis doesn't support named semaphore, that's why we use implementation without it 
        public override async Task<(string Url, Picture Picture)> GetPictureUrlAsync(Picture picture,
            int targetSize = 0,
            bool showDefaultPicture = true,
            string storeLocation = null,
            PictureType defaultPictureType = PictureType.Entity)
        {
            if (picture == null)
            {
                return showDefaultPicture
                    ? (await GetDefaultPictureUrlAsync(targetSize, defaultPictureType, storeLocation), null)
                    : (string.Empty, (Picture)null);
            }

            byte[] pictureBinary = null;
            if (picture.IsNew)
            {
                await DeletePictureThumbsAsync(picture);
                pictureBinary = await LoadPictureBinaryAsync(picture);

                if ((pictureBinary?.Length ?? 0) == 0)
                {
                    return showDefaultPicture
                        ? (await GetDefaultPictureUrlAsync(targetSize, defaultPictureType, storeLocation), picture)
                        : (string.Empty, picture);
                }

                //we do not validate picture binary here to ensure that no exception ("Parameter is not valid") will be thrown
                picture = await UpdatePictureAsync(picture.Id,
                    pictureBinary,
                    picture.MimeType,
                    picture.SeoFilename,
                    picture.AltAttribute,
                    picture.TitleAttribute,
                    false,
                    false);
            }

            var seoFileName = picture.SeoFilename; // = GetPictureSeName(picture.SeoFilename); //just for sure

            var lastPart = await GetFileExtensionFromMimeTypeAsync(picture.MimeType);
            string thumbFileName;
            if (targetSize == 0)
            {
                thumbFileName = !string.IsNullOrEmpty(seoFileName)
                    ? $"{picture.Id:0000000}_{seoFileName}.{lastPart}"
                    : $"{picture.Id:0000000}.{lastPart}";

                var thumbFilePath = await GetThumbLocalPathAsync(thumbFileName);
                if (await GeneratedThumbExistsAsync(thumbFilePath, thumbFileName))
                    return (await GetThumbUrlAsync(thumbFileName, storeLocation), picture);

                pictureBinary ??= await LoadPictureBinaryAsync(picture);

                //the named mutex helps to avoid creating the same files in different threads,
                //and does not decrease performance significantly, because the code is blocked only for the specific file.
                //you should be very careful, mutexes cannot be used in with the await operation
                //we can't use semaphore here, because it produces PlatformNotSupportedException exception on UNIX based systems
                using var mutex = new Mutex(false, thumbFileName);
                mutex.WaitOne();
                try
                {
                    SaveThumbAsync(thumbFilePath, thumbFileName, string.Empty, pictureBinary).Wait();
                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            }
            else
            {
                thumbFileName = !string.IsNullOrEmpty(seoFileName)
                    ? $"{picture.Id:0000000}_{seoFileName}_{targetSize}.{lastPart}"
                    : $"{picture.Id:0000000}_{targetSize}.{lastPart}";

                var thumbFilePath = await GetThumbLocalPathAsync(thumbFileName);
                if (await GeneratedThumbExistsAsync(thumbFilePath, thumbFileName))
                    return (await GetThumbUrlAsync(thumbFileName, storeLocation), picture);

                pictureBinary ??= await LoadPictureBinaryAsync(picture);

                //the named mutex helps to avoid creating the same files in different threads,
                //and does not decrease performance significantly, because the code is blocked only for the specific file.
                //you should be very careful, mutexes cannot be used in with the await operation
                //we can't use semaphore here, because it produces PlatformNotSupportedException exception on UNIX based systems
                using var mutex = new Mutex(false, thumbFileName);
                mutex.WaitOne();
                try
                {
                    if (pictureBinary != null)
                    {
                        try
                        {
                            using var image = SKBitmap.Decode(pictureBinary);
                            var format = GetImageFormatByMimeType(picture.MimeType);
                            pictureBinary = ImageResize(image, format, targetSize);
                        }
                        catch
                        {
                        }
                    }

                    SaveThumbAsync(thumbFilePath, thumbFileName, string.Empty, pictureBinary).Wait();
                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            }

            return (await GetThumbUrlAsync(thumbFileName, storeLocation), picture);
        }
    }

    private class TestMemoryDistributedCache
    {
        public TestMemoryDistributedCache()
        {
        }
    }

    private class TestMemoryDistributedCacheoptions : IOptions
    {
        public MemoryDistributedCacheOptions Value => new();
    }

    #endregion
}