Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using Grand.Business.Core.Interfaces.Catalog.Brands;
using Grand.Business.Core.Interfaces.Catalog.Categories;
using Grand.Business.Core.Interfaces.Catalog.Collections;
using Grand.Domain;
using Grand.Domain.Admin;
using Grand.Domain.Catalog;
using Grand.Web.AdminShared.Controllers;
using Grand.Web.Common.DataSource;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Grand.Web.Admin.Tests.Controllers;

[TestClass]
public class BaseSearchControllerTests
{
// Concrete subclass exists only to instantiate the abstract base for direct-call unit tests,
// with PickerStoreId parameterized so both the Admin/Vendor shape ("") and the Store shape (a
// real store id) can be exercised without needing the 3 real host subclasses - same pattern as
// BaseTaxCategoryControllerTests.
private class TestSearchController(
ICategoryService categoryService,
IBrandService brandService,
ICollectionService collectionService,
AdminSearchSettings adminSearchSettings,
string pickerStoreId)
: BaseSearchController(categoryService, brandService, collectionService, adminSearchSettings)
{
protected override string PickerStoreId => pickerStoreId;
}

private Mock<ICategoryService> _categoryServiceMock = null!;
private Mock<IBrandService> _brandServiceMock = null!;
private Mock<ICollectionService> _collectionServiceMock = null!;
private AdminSearchSettings _settings = null!;

[TestInitialize]
public void Setup()
{
_categoryServiceMock = new Mock<ICategoryService>();
_brandServiceMock = new Mock<IBrandService>();
_collectionServiceMock = new Mock<ICollectionService>();
_settings = new AdminSearchSettings {
CategorySizeLimit = 10,
CollectionSizeLimit = 10,
BrandSizeLimit = 10
};
}

private TestSearchController CreateController(string pickerStoreId) =>
new(_categoryServiceMock.Object, _brandServiceMock.Object, _collectionServiceMock.Object, _settings, pickerStoreId);

private static DataSourceRequestFilter EmptyFilter() => new() { Filters = [] };

// Anonymous JSON payloads aren't used here (DataSourceResult is a public type with public
// members), but Data's items are instances of a private nested record - read them via
// reflection, same pattern established in BasePictureControllerTests.
private static (string Id, string Name)[] ReadRows(IActionResult result)
{
var data = (System.Collections.IEnumerable)((JsonResult)result).Value!.GetType().GetProperty("Data")!
.GetValue(((JsonResult)result).Value)!;
var rows = new List<(string, string)>();
foreach (var item in data)
{
var id = (string)item.GetType().GetProperty("Id")!.GetValue(item)!;
var name = (string)item.GetType().GetProperty("Name")!.GetValue(item)!;
rows.Add((id, name));
}
return rows.ToArray();
}

[TestMethod]
public async Task Category_DefaultPickerStoreId_PassesEmptyStoreIdToService()
{
var controller = CreateController("");
_categoryServiceMock
.Setup(s => s.GetAllCategories(null, null, "", 0, 10, false))
.ReturnsAsync(new PagedList<Category>([new Category { Id = "cat-1", Name = "Books" }], 0, 10));
_categoryServiceMock.Setup(s => s.GetFormattedBreadCrumb(It.IsAny<Category>(), ">>", ""))
.ReturnsAsync("Books");

var result = await controller.Category(null, EmptyFilter());

var rows = ReadRows(result);
Assert.AreEqual(1, rows.Length);
Assert.AreEqual("cat-1", rows[0].Id);
_categoryServiceMock.Verify(s => s.GetAllCategories(null, null, "", 0, 10, false), Times.Once);
}

[TestMethod]
public async Task Category_OverriddenPickerStoreId_PassesStoreIdToService()
{
var controller = CreateController("store-1");
_categoryServiceMock
.Setup(s => s.GetAllCategories(null, null, "store-1", 0, 10, false))
.ReturnsAsync(new PagedList<Category>([], 0, 10));

await controller.Category(null, EmptyFilter());

_categoryServiceMock.Verify(s => s.GetAllCategories(null, null, "store-1", 0, 10, false), Times.Once);
}

[TestMethod]
public async Task Collection_DefaultPickerStoreId_PassesEmptyStoreIdToService()
{
var controller = CreateController("");
_collectionServiceMock
.Setup(s => s.GetAllCollections(null, "", 0, 10, false))
.ReturnsAsync(new PagedList<Collection>([new Collection { Id = "col-1", Name = "Summer" }], 0, 10));

var result = await controller.Collection(null, EmptyFilter());

var rows = ReadRows(result);
Assert.AreEqual(1, rows.Length);
Assert.AreEqual("Summer", rows[0].Name);
_collectionServiceMock.Verify(s => s.GetAllCollections(null, "", 0, 10, false), Times.Once);
}

[TestMethod]
public async Task Collection_OverriddenPickerStoreId_PassesStoreIdToService()
{
var controller = CreateController("store-1");
_collectionServiceMock
.Setup(s => s.GetAllCollections(null, "store-1", 0, 10, false))
.ReturnsAsync(new PagedList<Collection>([], 0, 10));

await controller.Collection(null, EmptyFilter());

_collectionServiceMock.Verify(s => s.GetAllCollections(null, "store-1", 0, 10, false), Times.Once);
}

[TestMethod]
public async Task Brand_DefaultPickerStoreId_PassesEmptyStoreIdToService()
{
var controller = CreateController("");
_brandServiceMock
.Setup(s => s.GetAllBrands(null, "", 0, 10, false))
.ReturnsAsync(new PagedList<Brand>([new Brand { Id = "brand-1", Name = "Acme" }], 0, 10));

var result = await controller.Brand(null, EmptyFilter());

var rows = ReadRows(result);
Assert.AreEqual(1, rows.Length);
Assert.AreEqual("Acme", rows[0].Name);
_brandServiceMock.Verify(s => s.GetAllBrands(null, "", 0, 10, false), Times.Once);
}

[TestMethod]
public async Task Brand_OverriddenPickerStoreId_PassesStoreIdToService()
{
var controller = CreateController("store-1");
_brandServiceMock
.Setup(s => s.GetAllBrands(null, "store-1", 0, 10, false))
.ReturnsAsync(new PagedList<Brand>([], 0, 10));

await controller.Brand(null, EmptyFilter());

_brandServiceMock.Verify(s => s.GetAllBrands(null, "store-1", 0, 10, false), Times.Once);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Linq;
using Grand.Web.Admin.Controllers;
using Grand.Web.AdminShared.Controllers;
using Grand.Web.Common.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Grand.Web.Admin.Tests.Controllers;

[TestClass]
public class SearchControllerAttributeTests
{
[TestMethod]
public void IsSubclassOfBaseSearchController()
{
Assert.IsTrue(typeof(BaseSearchController).IsAssignableFrom(typeof(SearchController)));
Assert.AreEqual(typeof(BaseSearchController), typeof(SearchController).BaseType);
}

[TestMethod]
public void HasAuthorizeAdminAttribute()
{
var attr = typeof(SearchController).GetCustomAttributes(typeof(AuthorizeAdminAttribute), inherit: false);
Assert.AreEqual(1, attr.Length);
}

[TestMethod]
public void HasAreaAdminAttribute()
{
var attr = typeof(SearchController)
.GetCustomAttributes(typeof(AreaAttribute), inherit: false)
.Cast<AreaAttribute>().Single();
Assert.AreEqual("Admin", attr.RouteValue);
}

[TestMethod]
public void HasAutoValidateAntiforgeryTokenAttribute()
{
var attr = typeof(SearchController)
.GetCustomAttributes(typeof(AutoValidateAntiforgeryTokenAttribute), inherit: true);
Assert.AreEqual(1, attr.Length);
}

[TestMethod]
public void HasAuthorizeMenuAttribute()
{
var attr = typeof(SearchController).GetCustomAttributes(typeof(AuthorizeMenuAttribute), inherit: false);
Assert.AreEqual(1, attr.Length);
}

// Regression guard: Index/CustomerGroup/Stores/Vendor have no Store/Vendor equivalent and must
// stay declared directly on Admin's own concrete controller, not migrate into the shared base
// (which would incorrectly expose them to Store/Vendor's SearchController too).
[TestMethod]
public void DeclaresHostOnlyPickerMethods()
{
var declaredMethodNames = typeof(SearchController)
.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly)
.Select(m => m.Name)
.ToHashSet();

Assert.IsTrue(declaredMethodNames.Contains("Index"));
Assert.IsTrue(declaredMethodNames.Contains("CustomerGroup"));
Assert.IsTrue(declaredMethodNames.Contains("Stores"));
Assert.IsTrue(declaredMethodNames.Contains("Vendor"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Linq;
using Grand.Domain.Admin;
using Grand.Domain.Customers;
using Grand.Business.Core.Interfaces.Catalog.Brands;
using Grand.Business.Core.Interfaces.Catalog.Categories;
using Grand.Business.Core.Interfaces.Catalog.Collections;
using Grand.Infrastructure;
using Grand.Web.AdminShared.Controllers;
using Grand.Web.Common.Filters;
using Grand.Web.Store.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Grand.Web.Store.Tests.Controllers;

[TestClass]
public class SearchControllerAttributeTests
{
[TestMethod]
public void IsSubclassOfBaseSearchController()
{
Assert.IsTrue(typeof(BaseSearchController).IsAssignableFrom(typeof(SearchController)));
Assert.AreEqual(typeof(BaseSearchController), typeof(SearchController).BaseType);
}

[TestMethod]
public void HasAuthorizeStoreAttribute()
{
var attr = typeof(SearchController).GetCustomAttributes(typeof(AuthorizeStoreAttribute), inherit: false);
Assert.AreEqual(1, attr.Length);
}

[TestMethod]
public void HasAreaStoreAttribute()
{
var attr = typeof(SearchController)
.GetCustomAttributes(typeof(AreaAttribute), inherit: false)
.Cast<AreaAttribute>().Single();
Assert.AreEqual("Store", attr.RouteValue);
}

[TestMethod]
public void HasAutoValidateAntiforgeryTokenAttribute()
{
var attr = typeof(SearchController)
.GetCustomAttributes(typeof(AutoValidateAntiforgeryTokenAttribute), inherit: true);
Assert.AreEqual(1, attr.Length);
}

[TestMethod]
public void HasAuthorizeMenuAttribute()
{
var attr = typeof(SearchController).GetCustomAttributes(typeof(AuthorizeMenuAttribute), inherit: false);
Assert.AreEqual(1, attr.Length);
}

// Store is the one host whose PickerStoreId override actually matters (Admin/Vendor keep the
// base's "" default) - regression guard for the storeId source itself, not just wiring.
[TestMethod]
public async System.Threading.Tasks.Task PickerStoreId_ReflectsCurrentCustomerStaffStoreId()
{
var categoryServiceMock = new Mock<ICategoryService>();
categoryServiceMock
.Setup(s => s.GetAllCategories(null, null, "store-42", 0, It.IsAny<int>(), false))
.ReturnsAsync(new Grand.Domain.PagedList<Grand.Domain.Catalog.Category>([], 0, 10));

var workContextMock = new Mock<IWorkContext>();
workContextMock.Setup(w => w.CurrentCustomer).Returns(new Customer { StaffStoreId = "store-42" });
var contextAccessorMock = new Mock<IContextAccessor>();
contextAccessorMock.Setup(c => c.WorkContext).Returns(workContextMock.Object);

var controller = new SearchController(
categoryServiceMock.Object,
new Mock<IBrandService>().Object,
new Mock<ICollectionService>().Object,
new AdminSearchSettings { CategorySizeLimit = 10 },
contextAccessorMock.Object);

await controller.Category(null, new Grand.Web.Common.DataSource.DataSourceRequestFilter { Filters = [] });

categoryServiceMock.Verify(s => s.GetAllCategories(null, null, "store-42", 0, 10, false), Times.Once);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Grand.Web.AdminShared.Controllers;
using Grand.Web.Common.Filters;
using Grand.Web.Vendor.Controllers;
using Grand.Web.Vendor.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Grand.Web.Vendor.Tests.Controllers;

[TestClass]
public class SearchControllerSurfaceTests
{
[TestMethod]
public void VendorSearchController_IsSubclassOfBaseSearchController()
{
Assert.IsTrue(typeof(BaseSearchController).IsAssignableFrom(typeof(SearchController)));
Assert.AreEqual(typeof(BaseSearchController), typeof(SearchController).BaseType);
}

// Same defect class Task 17 (Order) and Task-level reviews on later phases caught: BaseSearchController
// can't carry a host's [Area]/[Authorize*] attributes itself (they differ per host), so each concrete
// subclass must restate its own - a missing one here would 404 or deauthorize the whole controller
// silently. Same shape as VendorReviewControllerSurfaceTests/OrderControllerSurfaceTests.
[TestMethod]
public void VendorSearchController_HasAreaAttributeWithVendorArea()
{
var areaAttr = (AreaAttribute)Attribute.GetCustomAttribute(typeof(SearchController), typeof(AreaAttribute), false);
Assert.IsNotNull(areaAttr, "Missing [Area].");
Assert.AreEqual(Constants.AreaVendor, areaAttr.RouteValue);
}

[TestMethod]
public void VendorSearchController_HasAuthorizeVendorAttribute() =>
Assert.IsTrue(typeof(SearchController).IsDefined(typeof(AuthorizeVendorAttribute), false),
"Missing [AuthorizeVendor].");

[TestMethod]
public void VendorSearchController_HasAutoValidateAntiforgeryTokenAttribute() =>
Assert.IsTrue(typeof(SearchController).IsDefined(typeof(AutoValidateAntiforgeryTokenAttribute), true),
"Missing [AutoValidateAntiforgeryToken].");

[TestMethod]
public void VendorSearchController_HasAuthorizeMenuAttribute() =>
Assert.IsTrue(typeof(SearchController).IsDefined(typeof(AuthorizeMenuAttribute), false),
"Missing [AuthorizeMenu].");
}
Loading
Loading