using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Resources; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using FluentMigrator; using FluentMigrator.Runner; using FluentMigrator.Runner.Conventions; 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.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; 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.Mapping; using Nop.Data.Migrations; using Nop.Services.Affiliates; using Nop.Services.Authentication.External; using Nop.Services.Authentication.MultiFactor; using Nop.Services.Blogs; 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.Themes; using Nop.Web.Framework.UI; 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); } private static void Init() { var dataProvider = _serviceProvider.GetService().DataProvider; dataProvider.CreateDatabase(null); dataProvider.InitializeDatabase(); var languagePackInfo = (DownloadUrl: string.Empty, Progress: 0); _serviceProvider.GetService() .InstallRequiredDataAsync(NopTestsDefaults.AdminEmail, NopTestsDefaults.AdminPassword, languagePackInfo, null, null).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(); services.AddHttpClient(); var memoryCache = new MemoryCache(new MemoryCacheOptions()); var typeFinder = new AppDomainTypeFinder(); 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); var rootPath = new DirectoryInfo( $"{Directory.GetCurrentDirectory().Split("bin")[0]}{Path.Combine(@"\..\..\Presentation\Nop.Web".Split('\\', '/').ToArray())}") .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); services.AddWebEncoders(); var httpContext = new DefaultHttpContext { Request = { Headers = { { 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; //file provider services.AddTransient(); CommonHelper.DefaultFileProvider = new NopFileProvider(webHostEnvironment.Object); //web helper services.AddTransient(); //user agent helper services.AddTransient(); //data layer services.AddTransient(); services.AddTransient(serviceProvider => serviceProvider.GetRequiredService().DataProvider); services.AddTransient(x => x.GetRequiredService()); //repositories services.AddTransient(typeof(IRepository<>), typeof(EntityRepository<>)); //plugins services.AddTransient(); services.AddSingleton(memoryCache); services.AddSingleton(); services.AddSingleton(); //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(); 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(); 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(); //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.AddTransient(); //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.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(); _serviceProvider = services.BuildServiceProvider(); EngineContext.Replace(new NopTestEngine(_serviceProvider)); Init(); } public static T GetService() { try { return _serviceProvider.GetRequiredService(); } catch (InvalidOperationException) { return (T)EngineContext.Current.ResolveUnregistered(typeof(T)); } } 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, INopFileProvider fileProvider, IProductAttributeParser productAttributeParser, IRepository pictureRepository, IRepository pictureBinaryRepository, IRepository productPictureRepository, ISettingService settingService, IUrlRecordService urlRecordService, IWebHelper webHelper, MediaSettings mediaSettings) : base( downloadService, httpContextAccessor, fileProvider, productAttributeParser, 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); } } #endregion } }