diff --git a/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetHomepageBrandsHandlerTests.cs b/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetHomepageBrandsHandlerTests.cs new file mode 100644 index 0000000000..7714288a9b --- /dev/null +++ b/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetHomepageBrandsHandlerTests.cs @@ -0,0 +1,96 @@ +using Grand.Business.Core.Interfaces.Catalog.Brands; +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; +using Grand.Domain; +using Grand.Domain.Catalog; +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Media; +using Grand.Infrastructure.Caching; +using Grand.Infrastructure.Configuration; +using Grand.Mediator; +using Grand.Web.Features.Handlers.Catalog; +using Grand.Web.Features.Models.Catalog; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Grand.Web.Tests.Features.Handlers.Catalog; + +[TestClass] +public class GetHomepageBrandsHandlerTests +{ + private GetHomepageBrandsHandler _handler; + private Mock _brandServiceMock; + + [TestInitialize] + public void Init() + { + _brandServiceMock = new Mock(); + _brandServiceMock.Setup(s => s.GetAllBrands(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync((string _, CatalogAccess access, int _, int _) => { + var brands = new List { new() { Id = "public", ShowOnHomePage = true } }; + if (access.Customer.Groups.Contains("vip")) + brands.Add(new Brand { Id = "vip-only", ShowOnHomePage = true }); + return new PagedList(brands, 0, int.MaxValue); + }); + + var translationServiceMock = new Mock(); + translationServiceMock.Setup(s => s.GetResource(It.IsAny())).Returns("{0}"); + + var cacheBase = new MemoryCacheBase(new MemoryCache(new MemoryCacheOptions()), new Mock().Object, + new CacheConfig { DefaultCacheTimeMinutes = 1 }); + + _handler = new GetHomepageBrandsHandler(cacheBase, _brandServiceMock.Object, + new Mock().Object, translationServiceMock.Object, new MediaSettings()); + } + + private Task> Handle(params string[] groups) + { + return HandleIn("store-1", "lang-1", groups); + } + + private Task> HandleIn(string storeId, string languageId, params string[] groups) + { + var customer = new Customer(); + foreach (var group in groups) customer.Groups.Add(group); + return _handler.Handle(new GetHomepageBrands { + Customer = customer, + Store = new Domain.Stores.Store { Id = storeId }, + Language = new Language { Id = languageId } + }, CancellationToken.None); + } + + [TestMethod] + public async Task Handle_GuestAfterGroupMember_DoesNotSeeGroupLimitedBrand() + { + var vip = await Handle("registered", "vip"); + var guest = await Handle("guests"); + + CollectionAssert.AreEquivalent(new[] { "public", "vip-only" }, vip.Select(x => x.Id).ToArray()); + CollectionAssert.AreEquivalent(new[] { "public" }, guest.Select(x => x.Id).ToArray()); + } + + [TestMethod] + public async Task Handle_GroupMemberAfterGuest_SeesGroupLimitedBrand() + { + await Handle("guests"); + var vip = await Handle("registered", "vip"); + + CollectionAssert.AreEquivalent(new[] { "public", "vip-only" }, vip.Select(x => x.Id).ToArray()); + } + + [TestMethod] + public async Task Handle_OtherStoreOrLanguage_DoesNotReuseCachedEntry() + { + await HandleIn("store-1", "lang-1", "guests"); + await HandleIn("store-2", "lang-1", "guests"); + await HandleIn("store-1", "lang-2", "guests"); + await HandleIn("store-1", "lang-1", "guests"); + + _brandServiceMock.Verify(s => s.GetAllBrands(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(3)); + } +} diff --git a/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetHomepageCollectionsHandlerTests.cs b/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetHomepageCollectionsHandlerTests.cs new file mode 100644 index 0000000000..bf3ad4cfd1 --- /dev/null +++ b/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetHomepageCollectionsHandlerTests.cs @@ -0,0 +1,96 @@ +using Grand.Business.Core.Interfaces.Catalog.Collections; +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; +using Grand.Domain; +using Grand.Domain.Catalog; +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Media; +using Grand.Infrastructure.Caching; +using Grand.Infrastructure.Configuration; +using Grand.Mediator; +using Grand.Web.Features.Handlers.Catalog; +using Grand.Web.Features.Models.Catalog; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Grand.Web.Tests.Features.Handlers.Catalog; + +[TestClass] +public class GetHomepageCollectionsHandlerTests +{ + private GetHomepageCollectionsHandler _handler; + private Mock _collectionServiceMock; + + [TestInitialize] + public void Init() + { + _collectionServiceMock = new Mock(); + _collectionServiceMock.Setup(s => s.GetAllCollections(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync((string _, CatalogAccess access, int _, int _) => { + var collections = new List { new() { Id = "public", ShowOnHomePage = true } }; + if (access.Customer.Groups.Contains("vip")) + collections.Add(new Collection { Id = "vip-only", ShowOnHomePage = true }); + return new PagedList(collections, 0, int.MaxValue); + }); + + var translationServiceMock = new Mock(); + translationServiceMock.Setup(s => s.GetResource(It.IsAny())).Returns("{0}"); + + var cacheBase = new MemoryCacheBase(new MemoryCache(new MemoryCacheOptions()), new Mock().Object, + new CacheConfig { DefaultCacheTimeMinutes = 1 }); + + _handler = new GetHomepageCollectionsHandler(cacheBase, _collectionServiceMock.Object, + new Mock().Object, translationServiceMock.Object, new MediaSettings()); + } + + private Task> Handle(params string[] groups) + { + return HandleIn("store-1", "lang-1", groups); + } + + private Task> HandleIn(string storeId, string languageId, params string[] groups) + { + var customer = new Customer(); + foreach (var group in groups) customer.Groups.Add(group); + return _handler.Handle(new GetHomepageCollections { + Customer = customer, + Store = new Domain.Stores.Store { Id = storeId }, + Language = new Language { Id = languageId } + }, CancellationToken.None); + } + + [TestMethod] + public async Task Handle_GuestAfterGroupMember_DoesNotSeeGroupLimitedCollection() + { + var vip = await Handle("registered", "vip"); + var guest = await Handle("guests"); + + CollectionAssert.AreEquivalent(new[] { "public", "vip-only" }, vip.Select(x => x.Id).ToArray()); + CollectionAssert.AreEquivalent(new[] { "public" }, guest.Select(x => x.Id).ToArray()); + } + + [TestMethod] + public async Task Handle_GroupMemberAfterGuest_SeesGroupLimitedCollection() + { + await Handle("guests"); + var vip = await Handle("registered", "vip"); + + CollectionAssert.AreEquivalent(new[] { "public", "vip-only" }, vip.Select(x => x.Id).ToArray()); + } + + [TestMethod] + public async Task Handle_OtherStoreOrLanguage_DoesNotReuseCachedEntry() + { + await HandleIn("store-1", "lang-1", "guests"); + await HandleIn("store-2", "lang-1", "guests"); + await HandleIn("store-1", "lang-2", "guests"); + await HandleIn("store-1", "lang-1", "guests"); + + _collectionServiceMock.Verify(s => s.GetAllCollections(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(3)); + } +} diff --git a/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetMenuHandlerTests.cs b/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetMenuHandlerTests.cs new file mode 100644 index 0000000000..a141a9b587 --- /dev/null +++ b/src/Tests/Grand.Web.Tests/Features/Handlers/Catalog/GetMenuHandlerTests.cs @@ -0,0 +1,122 @@ +using Grand.Business.Core.Interfaces.Catalog.Brands; +using Grand.Business.Core.Interfaces.Catalog.Categories; +using Grand.Business.Core.Interfaces.Catalog.Collections; +using Grand.Business.Core.Interfaces.Cms; +using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; +using Grand.Domain; +using Grand.Domain.Blogs; +using Grand.Domain.Catalog; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Media; +using Grand.Domain.Pages; +using Grand.Infrastructure.Caching; +using Grand.Infrastructure.Configuration; +using Grand.Mediator; +using Grand.Web.Features.Handlers.Catalog; +using Grand.Web.Features.Models.Catalog; +using Grand.Web.Models.Catalog; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Grand.Web.Tests.Features.Handlers.Catalog; + +[TestClass] +public class GetMenuHandlerTests +{ + private GetMenuHandler _handler; + private Mock _brandServiceMock; + private Mock _collectionServiceMock; + + [TestInitialize] + public void Init() + { + _brandServiceMock = new Mock(); + _brandServiceMock.Setup(s => s.GetAllBrands(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync((string _, CatalogAccess access, int _, int _) => { + var brands = new List { new() { Id = "public", IncludeInMenu = true } }; + if (access.Customer.Groups.Contains("vip")) + brands.Add(new Brand { Id = "vip-only", IncludeInMenu = true }); + return new PagedList(brands, 0, int.MaxValue); + }); + + _collectionServiceMock = new Mock(); + _collectionServiceMock.Setup(s => s.GetAllCollections(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync((string _, CatalogAccess access, int _, int _) => { + var collections = new List { new() { Id = "public", IncludeInMenu = true } }; + if (access.Customer.Groups.Contains("vip")) + collections.Add(new Collection { Id = "vip-only", IncludeInMenu = true }); + return new PagedList(collections, 0, int.MaxValue); + }); + + var categoryServiceMock = new Mock(); + categoryServiceMock.Setup(s => s.GetMenuCategories(It.IsAny(), It.IsAny())) + .ReturnsAsync(new List()); + + var pageServiceMock = new Mock(); + pageServiceMock.Setup(s => s.GetAllPages(It.IsAny(), It.IsAny())) + .ReturnsAsync(new List()); + + var cacheBase = new MemoryCacheBase(new MemoryCache(new MemoryCacheOptions()), new Mock().Object, + new CacheConfig { DefaultCacheTimeMinutes = 1 }); + + _handler = new GetMenuHandler(categoryServiceMock.Object, _brandServiceMock.Object, pageServiceMock.Object, + cacheBase, _collectionServiceMock.Object, new Mock().Object, new CatalogSettings(), + new BlogSettings(), new MenuItemSettings(), new MediaSettings()); + } + + private Task Handle(params string[] groups) + { + return HandleIn("store-1", "lang-1", groups); + } + + private Task HandleIn(string storeId, string languageId, params string[] groups) + { + var customer = new Customer(); + foreach (var group in groups) customer.Groups.Add(group); + return _handler.Handle(new GetMenu { + Customer = customer, + Store = new Domain.Stores.Store { Id = storeId }, + Language = new Language { Id = languageId } + }, CancellationToken.None); + } + + [TestMethod] + public async Task Handle_GuestAfterGroupMember_DoesNotSeeGroupLimitedBrandsAndCollections() + { + await Handle("registered", "vip"); + var guest = await Handle("guests"); + + CollectionAssert.AreEquivalent(new[] { "public" }, guest.Brands.Select(x => x.Id).ToArray()); + CollectionAssert.AreEquivalent(new[] { "public" }, guest.Collections.Select(x => x.Id).ToArray()); + } + + [TestMethod] + public async Task Handle_GroupMemberAfterGuest_SeesGroupLimitedBrandsAndCollections() + { + await Handle("guests"); + var vip = await Handle("registered", "vip"); + + CollectionAssert.AreEquivalent(new[] { "public", "vip-only" }, vip.Brands.Select(x => x.Id).ToArray()); + CollectionAssert.AreEquivalent(new[] { "public", "vip-only" }, vip.Collections.Select(x => x.Id).ToArray()); + } + + [TestMethod] + public async Task Handle_OtherStoreOrLanguage_DoesNotReuseCachedEntry() + { + await HandleIn("store-1", "lang-1", "guests"); + await HandleIn("store-2", "lang-1", "guests"); + await HandleIn("store-1", "lang-2", "guests"); + await HandleIn("store-1", "lang-1", "guests"); + + _brandServiceMock.Verify(s => s.GetAllBrands(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(3)); + _collectionServiceMock.Verify(s => s.GetAllCollections(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(3)); + } +} diff --git a/src/Web/Grand.Web/Events/Cache/CacheKeyConst.cs b/src/Web/Grand.Web/Events/Cache/CacheKeyConst.cs index bd4a079f12..222cb9ea21 100644 --- a/src/Web/Grand.Web/Events/Cache/CacheKeyConst.cs +++ b/src/Web/Grand.Web/Events/Cache/CacheKeyConst.cs @@ -18,8 +18,9 @@ public static class CacheKeyConst /// /// {0} : current store ID /// {1} : language ID + /// {2} : groups of the current user /// - public const string BRAND_HOMEPAGE_KEY = "Grand.brand.navigation.homepage-{0}-{1}.pres"; + public const string BRAND_HOMEPAGE_KEY = "Grand.brand.navigation.homepage-{0}-{1}-{2}.pres"; /// /// Key for CollectionNavigationModel caching @@ -27,8 +28,9 @@ public static class CacheKeyConst /// /// {0} : language id /// {1} : current store ID + /// {2} : groups of the current user /// - public const string BRAND_NAVIGATION_MENU = "Grand.brand.navigation.menu-{0}-{1}.pres"; + public const string BRAND_NAVIGATION_MENU = "Grand.brand.navigation.menu-{0}-{1}-{2}.pres"; /// /// Key for CollectionNavigationModel caching @@ -47,8 +49,9 @@ public static class CacheKeyConst /// /// {0} : language id /// {1} : current store ID + /// {2} : groups of the current user /// - public const string COLLECTION_NAVIGATION_MENU = "Grand.collection.navigation.menu-{0}-{1}.pres"; + public const string COLLECTION_NAVIGATION_MENU = "Grand.collection.navigation.menu-{0}-{1}-{2}.pres"; /// /// Key for caching of collection displayed on home page @@ -56,8 +59,9 @@ public static class CacheKeyConst /// /// {0} : current store ID /// {1} : language ID + /// {2} : groups of the current user /// - public const string COLLECTION_HOMEPAGE_KEY = "Grand.collection.navigation.homepage-{0}-{1}.pres"; + public const string COLLECTION_HOMEPAGE_KEY = "Grand.collection.navigation.homepage-{0}-{1}-{2}.pres"; /// diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageBrandsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageBrandsHandler.cs index 36f1c0590e..2220fb8f3d 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageBrandsHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageBrandsHandler.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; using Grand.Business.Core.Utilities.Catalog; +using Grand.Domain.Customers; using Grand.Domain.Media; using Grand.Infrastructure.Caching; using Grand.Web.Events.Cache; @@ -37,7 +38,8 @@ public GetHomepageBrandsHandler( public async Task> Handle(GetHomepageBrands request, CancellationToken cancellationToken) { - var brandsCacheKey = string.Format(CacheKeyConst.BRAND_HOMEPAGE_KEY, request.Store.Id, request.Language.Id); + var brandsCacheKey = string.Format(CacheKeyConst.BRAND_HOMEPAGE_KEY, request.Store.Id, request.Language.Id, + string.Join(",", request.Customer.GetCustomerGroupIds())); var model = await _cacheBase.GetAsync(brandsCacheKey, async () => { diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCollectionsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCollectionsHandler.cs index 6c78822489..01a619acc6 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCollectionsHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCollectionsHandler.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; using Grand.Business.Core.Utilities.Catalog; +using Grand.Domain.Customers; using Grand.Domain.Media; using Grand.Infrastructure.Caching; using Grand.Web.Events.Cache; @@ -38,8 +39,8 @@ public GetHomepageCollectionsHandler( public async Task> Handle(GetHomepageCollections request, CancellationToken cancellationToken) { - var collectionsCacheKey = - string.Format(CacheKeyConst.COLLECTION_HOMEPAGE_KEY, request.Store.Id, request.Language.Id); + var collectionsCacheKey = string.Format(CacheKeyConst.COLLECTION_HOMEPAGE_KEY, request.Store.Id, + request.Language.Id, string.Join(",", request.Customer.GetCustomerGroupIds())); var model = await _cacheBase.GetAsync(collectionsCacheKey, async () => { diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetMenuHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetMenuHandler.cs index 09bb4ac65f..b3e07505e1 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetMenuHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetMenuHandler.cs @@ -58,9 +58,10 @@ public GetMenuHandler( public async Task Handle(GetMenu request, CancellationToken cancellationToken) { + var customerGroups = string.Join(",", request.Customer.GetCustomerGroupIds()); var cacheKey = string.Format(CacheKeyConst.CATEGORIES_BY_MENU, request.Language.Id, - request.Store.Id, string.Join(",", request.Customer.GetCustomerGroupIds())); + request.Store.Id, customerGroups); var cachedCategoriesModel = await _cacheBase.GetAsync(cacheKey, async () => await PrepareCategorySimpleModels( request.Language, await _categoryService.GetMenuCategories(customer: request.Customer, storeId: request.Store.Id))); @@ -78,7 +79,7 @@ public async Task Handle(GetMenu request, CancellationToken cancellat }).ToList(); var brandCacheKey = string.Format(CacheKeyConst.BRAND_NAVIGATION_MENU, - request.Language.Id, request.Store.Id); + request.Language.Id, request.Store.Id, customerGroups); var cachedBrandModel = await _cacheBase.GetAsync(brandCacheKey, async () => (await _brandService.GetAllBrands(brandName: "", @@ -94,7 +95,7 @@ public async Task Handle(GetMenu request, CancellationToken cancellat ); var collectionCacheKey = string.Format(CacheKeyConst.COLLECTION_NAVIGATION_MENU, - request.Language.Id, request.Store.Id); + request.Language.Id, request.Store.Id, customerGroups); var cachedCollectionModel = await _cacheBase.GetAsync(collectionCacheKey, async () => (await _collectionService.GetAllCollections(collectionName: "",