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,193 @@
using Grand.Business.Core.Interfaces.Common.Localization;
using Grand.Business.Core.Interfaces.Customers;
using Grand.Domain.Vendors;
using Grand.Web.AdminShared.Controllers;
using Grand.Web.AdminShared.Interfaces;
using Grand.Web.AdminShared.Models.Vendors;
using Grand.Web.Common.DataSource;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Grand.Web.Admin.Tests.Controllers;

[TestClass]
public class BaseVendorReviewControllerTests
{
// BaseVendorReviewController is abstract; minimal subclass so actions can be invoked directly.
private class TestVendorReviewController(
IVendorViewModelService vendorViewModelService,
IVendorService vendorService,
ITranslationService translationService,
IAdminDataScope<VendorReview> scope)
: BaseVendorReviewController(vendorViewModelService, vendorService, translationService, scope);

private TestVendorReviewController _controller;
private Mock<IVendorViewModelService> _vendorViewModelServiceMock;
private Mock<IVendorService> _vendorServiceMock;
private Mock<IAdminDataScope<VendorReview>> _scopeMock;

[TestInitialize]
public void Setup()
{
_vendorViewModelServiceMock = new Mock<IVendorViewModelService>();
_vendorServiceMock = new Mock<IVendorService>();
_scopeMock = new Mock<IAdminDataScope<VendorReview>>();
_scopeMock.Setup(s => s.DefaultVendorId).Returns((string)null);

var translationServiceMock = new Mock<ITranslationService>();
translationServiceMock.Setup(t => t.GetResource(It.IsAny<string>())).Returns("resource");

_controller = new TestVendorReviewController(
_vendorViewModelServiceMock.Object,
_vendorServiceMock.Object,
translationServiceMock.Object,
_scopeMock.Object);

var httpContext = new DefaultHttpContext();
var loggerFactoryMock = new Mock<ILoggerFactory>();
loggerFactoryMock.Setup(l => l.CreateLogger(It.IsAny<string>())).Returns(new Mock<ILogger>().Object);
var urlHelperFactoryMock = new Mock<IUrlHelperFactory>();
urlHelperFactoryMock.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>())).Returns(new Mock<IUrlHelper>().Object);
var requestServicesMock = new Mock<IServiceProvider>();
requestServicesMock.Setup(s => s.GetService(typeof(ILoggerFactory))).Returns(loggerFactoryMock.Object);
requestServicesMock.Setup(s => s.GetService(typeof(IUrlHelperFactory))).Returns(urlHelperFactoryMock.Object);
httpContext.RequestServices = requestServicesMock.Object;
_controller.ControllerContext = new ControllerContext { HttpContext = httpContext };
_controller.TempData = new TempDataDictionary(httpContext, new Mock<ITempDataProvider>().Object);
}

[TestMethod]
public void Index_RedirectsToList()
{
var result = _controller.Index() as RedirectToActionResult;
Assert.IsNotNull(result);
Assert.AreEqual("List", result.ActionName);
}

[TestMethod]
public void List_Get_ReturnsViewWithEmptyModel()
{
var result = _controller.List() as ViewResult;
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result.Model, typeof(VendorReviewListModel));
}

[TestMethod]
public async Task List_Post_GlobalScope_DoesNotForceSearchVendorId()
{
var model = new VendorReviewListModel { SearchVendorId = "caller-picked" };
_vendorViewModelServiceMock
.Setup(v => v.PrepareVendorReviewModel(model, 1, 10))
.ReturnsAsync((new List<VendorReviewModel>(), 0));

await _controller.List(new DataSourceRequest { Page = 1, PageSize = 10 }, model);

Assert.AreEqual("caller-picked", model.SearchVendorId);
_vendorViewModelServiceMock.Verify(v => v.PrepareVendorReviewModel(model, 1, 10), Times.Once);
}

[TestMethod]
public async Task List_Post_VendorScope_ForcesSearchVendorId()
{
_scopeMock.Setup(s => s.DefaultVendorId).Returns("vendor-A");
var model = new VendorReviewListModel { SearchVendorId = "caller-picked" };
_vendorViewModelServiceMock
.Setup(v => v.PrepareVendorReviewModel(model, 1, 10))
.ReturnsAsync((new List<VendorReviewModel>(), 0));

await _controller.List(new DataSourceRequest { Page = 1, PageSize = 10 }, model);

Assert.AreEqual("vendor-A", model.SearchVendorId);
}

[TestMethod]
public async Task Edit_Get_NotFound_RedirectsToList()
{
_vendorServiceMock.Setup(v => v.GetVendorReviewById("missing")).ReturnsAsync((VendorReview)null);

var result = await _controller.Edit("missing") as RedirectToActionResult;

Assert.IsNotNull(result);
Assert.AreEqual("List", result.ActionName);
}

[TestMethod]
public async Task Edit_Get_AccessDenied_RedirectsToList()
{
var review = new VendorReview { Id = "r1", VendorId = "vendor-OTHER" };
_vendorServiceMock.Setup(v => v.GetVendorReviewById("r1")).ReturnsAsync(review);
_scopeMock.Setup(s => s.HasAccess(review)).ReturnsAsync(false);

var result = await _controller.Edit("r1") as RedirectToActionResult;

Assert.IsNotNull(result);
Assert.AreEqual("List", result.ActionName);
}

[TestMethod]
public async Task Edit_Get_AccessGranted_ReturnsView()
{
var review = new VendorReview { Id = "r1", VendorId = "vendor-A" };
_vendorServiceMock.Setup(v => v.GetVendorReviewById("r1")).ReturnsAsync(review);
_scopeMock.Setup(s => s.HasAccess(review)).ReturnsAsync(true);

var result = await _controller.Edit("r1") as ViewResult;

Assert.IsNotNull(result);
_vendorViewModelServiceMock.Verify(
v => v.PrepareVendorReviewModel(It.IsAny<VendorReviewModel>(), review, false, false), Times.Once);
}

[TestMethod]
public async Task Delete_AccessDenied_RedirectsToList()
{
var review = new VendorReview { Id = "r1", VendorId = "vendor-OTHER" };
_vendorServiceMock.Setup(v => v.GetVendorReviewById("r1")).ReturnsAsync(review);
_scopeMock.Setup(s => s.HasAccess(review)).ReturnsAsync(false);

var result = await _controller.Delete("r1") as RedirectToActionResult;

Assert.IsNotNull(result);
Assert.AreEqual("List", result.ActionName);
_vendorViewModelServiceMock.Verify(v => v.DeleteVendorReview(It.IsAny<VendorReview>()), Times.Never);
}

[TestMethod]
public async Task Delete_AccessGranted_DeletesAndRedirects()
{
var review = new VendorReview { Id = "r1", VendorId = "vendor-A" };
_vendorServiceMock.Setup(v => v.GetVendorReviewById("r1")).ReturnsAsync(review);
_scopeMock.Setup(s => s.HasAccess(review)).ReturnsAsync(true);

var result = await _controller.Delete("r1") as RedirectToActionResult;

Assert.IsNotNull(result);
Assert.AreEqual("List", result.ActionName);
_vendorViewModelServiceMock.Verify(v => v.DeleteVendorReview(review), Times.Once);
}

[TestMethod]
public async Task ApproveSelected_ForwardsSelectedIdsAndScope()
{
var ids = new List<string> { "r1:vendor-A" };

await _controller.ApproveSelected(ids);

_vendorViewModelServiceMock.Verify(v => v.ApproveVendorReviews(ids, _scopeMock.Object), Times.Once);
}

[TestMethod]
public async Task DisapproveSelected_ForwardsSelectedIdsAndScope()
{
var ids = new List<string> { "r1:vendor-A" };

await _controller.DisapproveSelected(ids);

_vendorViewModelServiceMock.Verify(v => v.DisapproveVendorReviews(ids, _scopeMock.Object), Times.Once);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#nullable enable

using Grand.Domain.Vendors;
using Grand.Infrastructure;
using Grand.Web.AdminShared.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Grand.Web.Admin.Tests.Controllers;

[TestClass]
public class RoutedVendorReviewDataScopeTests
{
private const string VendorId = "vendor-1";

private GlobalAdminDataScope<VendorReview> _adminScope = null!;
private VendorVendorReviewDataScope _vendorScope = null!;

[TestInitialize]
public void Setup()
{
var workContext = new Mock<IWorkContext>();
workContext.Setup(x => x.CurrentVendor).Returns(new Vendor { Id = VendorId });
var contextAccessor = new Mock<IContextAccessor>();
contextAccessor.Setup(x => x.WorkContext).Returns(workContext.Object);

_adminScope = new GlobalAdminDataScope<VendorReview>();
_vendorScope = new VendorVendorReviewDataScope(contextAccessor.Object);
}

private RoutedVendorReviewDataScope ResolverForArea(string? area)
{
var httpContext = new DefaultHttpContext();
if (area is not null) httpContext.Request.RouteValues["area"] = area;
var httpContextAccessor = new Mock<IHttpContextAccessor>();
httpContextAccessor.Setup(x => x.HttpContext).Returns(httpContext);
return new RoutedVendorReviewDataScope(httpContextAccessor.Object, _adminScope, _vendorScope);
}

[TestMethod]
public void AdminArea_ResolvesToAdminScope()
{
var resolver = ResolverForArea("Admin");
Assert.IsNull(resolver.DefaultStoreId);
Assert.IsNull(resolver.DefaultVendorId);
Assert.AreEqual("Admin", resolver.ResourceKeyPrefix);
}

[TestMethod]
public void VendorArea_ResolvesToVendorScope()
{
var resolver = ResolverForArea("Vendor");
Assert.AreEqual("Vendor", resolver.ResourceKeyPrefix);
Assert.AreEqual(VendorId, resolver.DefaultVendorId);
Assert.IsFalse(resolver.ShowStoreSelector);
}

[TestMethod]
public void UnrecognizedOrMissingArea_ThrowsFailClosed()
{
var resolverStore = ResolverForArea("Store");
Assert.Throws<InvalidOperationException>(() => _ = resolverStore.ResourceKeyPrefix);

var resolverTypo = ResolverForArea("Vendorr");
Assert.Throws<InvalidOperationException>(() => _ = resolverTypo.ResourceKeyPrefix);

var resolverNoArea = ResolverForArea(null);
Assert.Throws<InvalidOperationException>(() => _ = resolverNoArea.ResourceKeyPrefix);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Grand.Web.Admin.Controllers;
using Grand.Web.Admin.Extensions;
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 VendorReviewControllerRoutingTests
{
[TestMethod]
public void AdminVendorReviewController_InheritsBaseVendorReviewController() =>
Assert.IsTrue(typeof(BaseVendorReviewController).IsAssignableFrom(typeof(VendorReviewController)));

[TestMethod]
public void AdminVendorReviewController_HasAutoValidateAntiforgeryToken() =>
Assert.IsTrue(typeof(VendorReviewController)
.GetCustomAttributes(typeof(Microsoft.AspNetCore.Mvc.AutoValidateAntiforgeryTokenAttribute), false)
.Length > 0);

[TestMethod]
public void AdminVendorReviewController_HasAreaAttributeWithAdminArea()
{
var areaAttr = (AreaAttribute)Attribute.GetCustomAttribute(typeof(VendorReviewController), typeof(AreaAttribute), false);
Assert.IsNotNull(areaAttr, "Missing [Area].");
Assert.AreEqual(Constants.AreaAdmin, areaAttr.RouteValue);
}

[TestMethod]
public void AdminVendorReviewController_HasAuthorizeAdminAttribute() =>
Assert.IsTrue(typeof(VendorReviewController).IsDefined(typeof(AuthorizeAdminAttribute), false),
"Missing [AuthorizeAdmin].");

// Regression guard: VendorSearchAutoComplete leaks other vendors' names/ids and must remain
// Admin-only - never silently dropped from Admin, never promoted to the shared base controller.
[TestMethod]
public void AdminVendorReviewController_DeclaresVendorSearchAutoComplete()
{
var declaredMethodNames = typeof(VendorReviewController)
.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Select(m => m.Name)
.ToHashSet();

Assert.IsTrue(declaredMethodNames.Contains("VendorSearchAutoComplete"),
"Admin's VendorReviewController is missing VendorSearchAutoComplete.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Grand.Domain.Vendors;
using Grand.Infrastructure;
using Grand.Web.AdminShared.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Grand.Web.Admin.Tests.Controllers;

[TestClass]
public class VendorVendorReviewDataScopeTests
{
private static VendorVendorReviewDataScope Build(string currentVendorId)
{
var workContextMock = new Mock<IWorkContext>();
workContextMock.Setup(w => w.CurrentVendor).Returns(new Vendor { Id = currentVendorId });
var contextAccessorMock = new Mock<IContextAccessor>();
contextAccessorMock.Setup(c => c.WorkContext).Returns(workContextMock.Object);
return new VendorVendorReviewDataScope(contextAccessorMock.Object);
}

[TestMethod]
public async Task HasAccess_MatchingVendorId_True()
{
var scope = Build("vendor-A");
Assert.IsTrue(await scope.HasAccess(new VendorReview { VendorId = "vendor-A" }));
}

[TestMethod]
public async Task HasAccess_MismatchedVendorId_False()
{
var scope = Build("vendor-A");
Assert.IsFalse(await scope.HasAccess(new VendorReview { VendorId = "vendor-B" }));
}

[TestMethod]
public async Task HasAccess_NullEntity_False()
{
var scope = Build("vendor-A");
Assert.IsFalse(await scope.HasAccess(null));
}

[TestMethod]
public async Task HasAccess_EmptyCurrentVendorIdAndEmptyEntityVendorId_False()
{
var scope = Build(string.Empty);
Assert.IsFalse(await scope.HasAccess(new VendorReview { VendorId = string.Empty }));
}

[TestMethod]
public async Task HasAccess_NullCurrentVendorIdAndNullEntityVendorId_False()
{
var scope = Build(null);
Assert.IsFalse(await scope.HasAccess(new VendorReview { VendorId = null }));
}

[TestMethod]
public void ScopeDefaults_VendorScoped()
{
var scope = Build("vendor-A");
Assert.IsNull(scope.DefaultStoreId);
Assert.AreEqual("vendor-A", scope.DefaultVendorId);
Assert.AreEqual("Vendor", scope.ResourceKeyPrefix);
Assert.IsFalse(scope.ShowStoreSelector);
Assert.IsFalse(scope.CanFeatureOnHomepage);
}
}
Loading
Loading