test project created
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
using Distribution.Features.Auth;
|
||||
using Distribution.Features.User;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class AuthServiceTests
|
||||
{
|
||||
private readonly Mock<IUserRepository> _userRepo = new();
|
||||
private readonly Mock<IJwtTokenService> _tokenService = new();
|
||||
private readonly AuthService _sut;
|
||||
private readonly PasswordHasher<User> _hasher = new();
|
||||
|
||||
public AuthServiceTests()
|
||||
{
|
||||
_sut = new AuthService(_userRepo.Object, _tokenService.Object);
|
||||
}
|
||||
|
||||
private User ActiveUserWithPassword(string password) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Username = "testuser",
|
||||
FullName = "Test User",
|
||||
IsActive = true,
|
||||
FK_RoleId = Guid.NewGuid(),
|
||||
PasswordHash = _hasher.HashPassword(null!, password),
|
||||
Role = new Role { Name = "Sales", Level = 10 }
|
||||
};
|
||||
|
||||
// LoginAsync
|
||||
|
||||
[Fact]
|
||||
public async Task LoginAsync_ReturnsNull_WhenUserNotFound()
|
||||
{
|
||||
_userRepo.Setup(r => r.GetByUsernameAsync(It.IsAny<string>())).ReturnsAsync((User?)null);
|
||||
|
||||
var result = await _sut.LoginAsync(new LoginRequest("nobody", "pass"));
|
||||
|
||||
Assert.Null(result);
|
||||
_tokenService.Verify(t => t.GenerateToken(It.IsAny<User>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoginAsync_ReturnsNull_WhenUserIsInactive()
|
||||
{
|
||||
var user = ActiveUserWithPassword("secret");
|
||||
user.IsActive = false;
|
||||
_userRepo.Setup(r => r.GetByUsernameAsync("testuser")).ReturnsAsync(user);
|
||||
|
||||
var result = await _sut.LoginAsync(new LoginRequest("testuser", "secret"));
|
||||
|
||||
Assert.Null(result);
|
||||
_tokenService.Verify(t => t.GenerateToken(It.IsAny<User>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoginAsync_ReturnsNull_WhenPasswordIsWrong()
|
||||
{
|
||||
var user = ActiveUserWithPassword("correct");
|
||||
_userRepo.Setup(r => r.GetByUsernameAsync("testuser")).ReturnsAsync(user);
|
||||
|
||||
var result = await _sut.LoginAsync(new LoginRequest("testuser", "wrong"));
|
||||
|
||||
Assert.Null(result);
|
||||
_tokenService.Verify(t => t.GenerateToken(It.IsAny<User>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoginAsync_ReturnsLoginResponse_WhenCredentialsAreValid()
|
||||
{
|
||||
var user = ActiveUserWithPassword("secret");
|
||||
_userRepo.Setup(r => r.GetByUsernameAsync("testuser")).ReturnsAsync(user);
|
||||
_tokenService.Setup(t => t.GenerateToken(user)).Returns("jwt-token");
|
||||
|
||||
var result = await _sut.LoginAsync(new LoginRequest("testuser", "secret"));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("jwt-token", result.Token);
|
||||
Assert.Equal(user.Id, result.UserId);
|
||||
Assert.Equal("testuser", result.Username);
|
||||
Assert.Equal("Sales", result.RoleName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoginAsync_CallsGenerateToken_OnSuccess()
|
||||
{
|
||||
var user = ActiveUserWithPassword("pass");
|
||||
_userRepo.Setup(r => r.GetByUsernameAsync(It.IsAny<string>())).ReturnsAsync(user);
|
||||
_tokenService.Setup(t => t.GenerateToken(user)).Returns("token");
|
||||
|
||||
await _sut.LoginAsync(new LoginRequest("testuser", "pass"));
|
||||
|
||||
_tokenService.Verify(t => t.GenerateToken(user), Times.Once);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class ItemServiceTests
|
||||
{
|
||||
private readonly Mock<IItemRepository> _repo = new();
|
||||
private readonly Mock<IUnitOfWork> _uow = new();
|
||||
private readonly ItemService _sut;
|
||||
|
||||
public ItemServiceTests()
|
||||
{
|
||||
_sut = new ItemService(_repo.Object, _uow.Object);
|
||||
}
|
||||
|
||||
private static Item ManualItem(string sku = "SKU-001") => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Sku = sku,
|
||||
Name = "Test Item",
|
||||
IsActive = true,
|
||||
SyncSource = SyncSource.Manual,
|
||||
FK_CategoryId = Guid.NewGuid(),
|
||||
FK_BaseUomId = Guid.NewGuid(),
|
||||
Category = new Category { Name = "Cat" },
|
||||
BaseUom = new Uom { Name = "Each" },
|
||||
Conversions = []
|
||||
};
|
||||
|
||||
private static Item ErpItem() => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Sku = "ERP-001",
|
||||
Name = "Erp Item",
|
||||
IsActive = true,
|
||||
SyncSource = SyncSource.Erp,
|
||||
FK_CategoryId = Guid.NewGuid(),
|
||||
FK_BaseUomId = Guid.NewGuid(),
|
||||
Category = new Category { Name = "Cat" },
|
||||
BaseUom = new Uom { Name = "Each" },
|
||||
Conversions = []
|
||||
};
|
||||
|
||||
private static Item InactiveItem() => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Sku = "OLD-001",
|
||||
Name = "Old",
|
||||
IsActive = false,
|
||||
SyncSource = SyncSource.Manual,
|
||||
FK_CategoryId = Guid.NewGuid(),
|
||||
FK_BaseUomId = Guid.NewGuid(),
|
||||
Category = new Category { Name = "Cat" },
|
||||
BaseUom = new Uom { Name = "Each" },
|
||||
Conversions = []
|
||||
};
|
||||
|
||||
private static CreateItemRequest SampleCreateRequest(string sku = "SKU-001") =>
|
||||
new(sku, "Test Item", null, Guid.NewGuid(), null, null, Guid.NewGuid(), null, null);
|
||||
|
||||
// CreateAsync
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Throws_WhenSkuAlreadyExists()
|
||||
{
|
||||
_repo.Setup(r => r.SkuExistsAsync(It.IsAny<string>(), It.IsAny<Guid?>())).ReturnsAsync(true);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => _sut.CreateAsync(SampleCreateRequest("DUP")));
|
||||
|
||||
_repo.Verify(r => r.AddAsync(It.IsAny<Item>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_SetsManualSyncSource()
|
||||
{
|
||||
var item = ManualItem();
|
||||
_repo.Setup(r => r.SkuExistsAsync(It.IsAny<string>(), It.IsAny<Guid?>())).ReturnsAsync(false);
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Item>())).Returns(Task.CompletedTask);
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(item);
|
||||
|
||||
await _sut.CreateAsync(SampleCreateRequest());
|
||||
|
||||
_repo.Verify(r => r.AddAsync(It.Is<Item>(i => i.SyncSource == SyncSource.Manual)), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_BeginsAndCommitsTransaction()
|
||||
{
|
||||
var item = ManualItem();
|
||||
_repo.Setup(r => r.SkuExistsAsync(It.IsAny<string>(), It.IsAny<Guid?>())).ReturnsAsync(false);
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Item>())).Returns(Task.CompletedTask);
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(item);
|
||||
|
||||
await _sut.CreateAsync(SampleCreateRequest());
|
||||
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Rollbacks_OnAddException()
|
||||
{
|
||||
_repo.Setup(r => r.SkuExistsAsync(It.IsAny<string>(), It.IsAny<Guid?>())).ReturnsAsync(false);
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Item>())).ThrowsAsync(new Exception("db error"));
|
||||
|
||||
await Assert.ThrowsAsync<Exception>(() => _sut.CreateAsync(SampleCreateRequest()));
|
||||
|
||||
_uow.Verify(u => u.RollbackAsync(default), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
// UpdateAsync
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_ReturnsNull_WhenItemNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Item?)null);
|
||||
|
||||
var result = await _sut.UpdateAsync(Guid.NewGuid(),
|
||||
new UpdateItemRequest("Name", null, Guid.NewGuid(), null, null, Guid.NewGuid(), null));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_ReturnsNull_WhenItemInactive()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(InactiveItem());
|
||||
|
||||
var result = await _sut.UpdateAsync(Guid.NewGuid(),
|
||||
new UpdateItemRequest("Name", null, Guid.NewGuid(), null, null, Guid.NewGuid(), null));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_ReturnsNull_WhenItemIsErpSynced()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(ErpItem());
|
||||
|
||||
var result = await _sut.UpdateAsync(Guid.NewGuid(),
|
||||
new UpdateItemRequest("Name", null, Guid.NewGuid(), null, null, Guid.NewGuid(), null));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_UpdatesFields_AndCommits()
|
||||
{
|
||||
var item = ManualItem();
|
||||
_repo.Setup(r => r.GetByIdAsync(item.Id)).ReturnsAsync(item);
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(item);
|
||||
|
||||
var catId = Guid.NewGuid();
|
||||
var uomId = Guid.NewGuid();
|
||||
item.Category = new Category { Name = "Cat" };
|
||||
item.BaseUom = new Uom { Name = "Each" };
|
||||
|
||||
var result = await _sut.UpdateAsync(item.Id,
|
||||
new UpdateItemRequest("Updated", "desc", catId, null, null, uomId, 9.99m));
|
||||
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
// DeleteAsync
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ReturnsFalse_WhenItemNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Item?)null);
|
||||
|
||||
var result = await _sut.DeleteAsync(Guid.NewGuid());
|
||||
|
||||
Assert.False(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ReturnsFalse_WhenItemInactive()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(InactiveItem());
|
||||
|
||||
var result = await _sut.DeleteAsync(Guid.NewGuid());
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ReturnsFalse_WhenItemIsErpSynced()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(ErpItem());
|
||||
|
||||
var result = await _sut.DeleteAsync(Guid.NewGuid());
|
||||
|
||||
Assert.False(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_CallsRemove_AndCommits_ForManualItem()
|
||||
{
|
||||
var item = ManualItem();
|
||||
_repo.Setup(r => r.GetByIdAsync(item.Id)).ReturnsAsync(item);
|
||||
|
||||
var result = await _sut.DeleteAsync(item.Id);
|
||||
|
||||
Assert.True(result);
|
||||
_repo.Verify(r => r.Remove(item), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
// ReplaceConversionsAsync
|
||||
|
||||
[Fact]
|
||||
public async Task ReplaceConversionsAsync_ReturnsNull_WhenItemNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Item?)null);
|
||||
|
||||
var result = await _sut.ReplaceConversionsAsync(Guid.NewGuid(),
|
||||
new ReplaceConversionsRequest([]));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReplaceConversionsAsync_ReturnsNull_WhenItemIsErpSynced()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(ErpItem());
|
||||
|
||||
var result = await _sut.ReplaceConversionsAsync(Guid.NewGuid(),
|
||||
new ReplaceConversionsRequest([]));
|
||||
|
||||
Assert.Null(result);
|
||||
_repo.Verify(r => r.ReplaceConversionsAsync(It.IsAny<Guid>(), It.IsAny<List<UomConversion>>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReplaceConversionsAsync_CallsReplace_AndCommits()
|
||||
{
|
||||
var item = ManualItem();
|
||||
_repo.Setup(r => r.GetByIdAsync(item.Id)).ReturnsAsync(item);
|
||||
_repo.Setup(r => r.ReplaceConversionsAsync(It.IsAny<Guid>(), It.IsAny<List<UomConversion>>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(item);
|
||||
|
||||
await _sut.ReplaceConversionsAsync(item.Id, new ReplaceConversionsRequest([]));
|
||||
|
||||
_repo.Verify(r => r.ReplaceConversionsAsync(item.Id, It.IsAny<List<UomConversion>>()), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
using Distribution.Features.Order;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class OrderServiceTests
|
||||
{
|
||||
private readonly Mock<IOrderRepository> _repo = new();
|
||||
private readonly Mock<IUnitOfWork> _uow = new();
|
||||
private readonly Mock<ICurrentUserService> _currentUser = new();
|
||||
private readonly Mock<IScopeAccessService> _scopeAccess = new();
|
||||
private readonly OrderService _sut;
|
||||
|
||||
public OrderServiceTests()
|
||||
{
|
||||
_sut = new OrderService(_repo.Object, _uow.Object, _currentUser.Object, _scopeAccess.Object);
|
||||
}
|
||||
|
||||
private static Order ActiveOrder(Guid storeId) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
FK_StoreId = storeId,
|
||||
OrderDate = DateTime.Now,
|
||||
TotalAmount = 100,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
private void SetupGlobalUser(Guid userId)
|
||||
{
|
||||
_currentUser.Setup(u => u.UserId).Returns(userId);
|
||||
_currentUser.Setup(u => u.RoleLevel).Returns(50);
|
||||
}
|
||||
|
||||
private void SetupScopedUser(Guid userId)
|
||||
{
|
||||
_currentUser.Setup(u => u.UserId).Returns(userId);
|
||||
_currentUser.Setup(u => u.RoleLevel).Returns(10);
|
||||
}
|
||||
|
||||
// GetAllAsync
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllAsync_GlobalUser_ReturnsAllActiveOrders()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var storeA = Guid.NewGuid();
|
||||
var storeB = Guid.NewGuid();
|
||||
SetupGlobalUser(userId);
|
||||
var orders = new List<Order> { ActiveOrder(storeA), ActiveOrder(storeB) };
|
||||
_repo.Setup(r => r.GetAllAsync()).ReturnsAsync(orders);
|
||||
_repo.Setup(r => r.GetItemsByOrderIdAsync(It.IsAny<Guid>())).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetAllAsync();
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllAsync_ScopedUser_FiltersToAccessibleStores()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var accessibleStore = Guid.NewGuid();
|
||||
var restrictedStore = Guid.NewGuid();
|
||||
SetupScopedUser(userId);
|
||||
_scopeAccess.Setup(s => s.GetAccessibleStoreIdsAsync(userId)).ReturnsAsync([accessibleStore]);
|
||||
var orders = new List<Order> { ActiveOrder(accessibleStore), ActiveOrder(restrictedStore) };
|
||||
_repo.Setup(r => r.GetAllAsync()).ReturnsAsync(orders);
|
||||
_repo.Setup(r => r.GetItemsByOrderIdAsync(It.IsAny<Guid>())).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetAllAsync();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(accessibleStore, result[0].StoreId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllAsync_FiltersOutInactiveOrders()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
SetupGlobalUser(userId);
|
||||
var inactiveOrder = ActiveOrder(Guid.NewGuid());
|
||||
inactiveOrder.IsActive = false;
|
||||
_repo.Setup(r => r.GetAllAsync()).ReturnsAsync([inactiveOrder]);
|
||||
_repo.Setup(r => r.GetItemsByOrderIdAsync(It.IsAny<Guid>())).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetAllAsync();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
// GetByIdAsync
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsNull_WhenOrderNotFound()
|
||||
{
|
||||
SetupGlobalUser(Guid.NewGuid());
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Order?)null);
|
||||
|
||||
var result = await _sut.GetByIdAsync(Guid.NewGuid());
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsNull_WhenUserLacksStoreScope()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var storeId = Guid.NewGuid();
|
||||
SetupScopedUser(userId);
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(ActiveOrder(storeId));
|
||||
_scopeAccess.Setup(s => s.HasStoreScopeAsync(userId, storeId)).ReturnsAsync(false);
|
||||
|
||||
var result = await _sut.GetByIdAsync(Guid.NewGuid());
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsOrder_WhenGlobalUser()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var order = ActiveOrder(Guid.NewGuid());
|
||||
SetupGlobalUser(userId);
|
||||
_repo.Setup(r => r.GetByIdAsync(order.Id)).ReturnsAsync(order);
|
||||
_repo.Setup(r => r.GetItemsByOrderIdAsync(order.Id)).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetByIdAsync(order.Id);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(order.Id, result.Id);
|
||||
}
|
||||
|
||||
// CreateAsync
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_ReturnsNull_WhenUserLacksStoreScope()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var storeId = Guid.NewGuid();
|
||||
SetupScopedUser(userId);
|
||||
_scopeAccess.Setup(s => s.HasStoreScopeAsync(userId, storeId)).ReturnsAsync(false);
|
||||
|
||||
var result = await _sut.CreateAsync(
|
||||
new CreateOrderRequest(storeId, DateTime.Now, 100, []));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_CreatesOrder_WhenGlobalUser()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var storeId = Guid.NewGuid();
|
||||
SetupGlobalUser(userId);
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Order>())).Returns(Task.CompletedTask);
|
||||
_repo.Setup(r => r.AddItemsAsync(It.IsAny<IEnumerable<OrderItem>>())).Returns(Task.CompletedTask);
|
||||
|
||||
var result = await _sut.CreateAsync(
|
||||
new CreateOrderRequest(storeId, DateTime.Now, 250, []));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(storeId, result.StoreId);
|
||||
Assert.Equal(250, result.TotalAmount);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Rollbacks_OnException()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
SetupGlobalUser(userId);
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Order>())).ThrowsAsync(new Exception("db error"));
|
||||
|
||||
await Assert.ThrowsAsync<Exception>(() =>
|
||||
_sut.CreateAsync(new CreateOrderRequest(Guid.NewGuid(), DateTime.Now, 100, [])));
|
||||
|
||||
_uow.Verify(u => u.RollbackAsync(default), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
// DeleteAsync
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ReturnsFalse_WhenOrderNotFound()
|
||||
{
|
||||
SetupGlobalUser(Guid.NewGuid());
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Order?)null);
|
||||
|
||||
var result = await _sut.DeleteAsync(Guid.NewGuid());
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ReturnsFalse_WhenUserLacksStoreScope()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var storeId = Guid.NewGuid();
|
||||
SetupScopedUser(userId);
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(ActiveOrder(storeId));
|
||||
_scopeAccess.Setup(s => s.HasStoreScopeAsync(userId, storeId)).ReturnsAsync(false);
|
||||
|
||||
var result = await _sut.DeleteAsync(Guid.NewGuid());
|
||||
|
||||
Assert.False(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_RemovesOrder_WhenAuthorized()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var order = ActiveOrder(Guid.NewGuid());
|
||||
SetupGlobalUser(userId);
|
||||
_repo.Setup(r => r.GetByIdAsync(order.Id)).ReturnsAsync(order);
|
||||
|
||||
var result = await _sut.DeleteAsync(order.Id);
|
||||
|
||||
Assert.True(result);
|
||||
_repo.Verify(r => r.Remove(order), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using Distribution.Features.Region;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class RegionServiceTests
|
||||
{
|
||||
private readonly Mock<IRegionRepository> _repo = new();
|
||||
private readonly Mock<IUnitOfWork> _uow = new();
|
||||
private readonly RegionService _sut;
|
||||
|
||||
public RegionServiceTests()
|
||||
{
|
||||
_sut = new RegionService(_repo.Object, _uow.Object);
|
||||
}
|
||||
|
||||
private static Region ActiveRegion(string name = "North") =>
|
||||
new() { Id = Guid.NewGuid(), Name = name, IsActive = true };
|
||||
|
||||
private static Region InactiveRegion() =>
|
||||
new() { Id = Guid.NewGuid(), Name = "Old", IsActive = false };
|
||||
|
||||
// GetAllAsync
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllAsync_ReturnsOnlyActiveRegions()
|
||||
{
|
||||
_repo.Setup(r => r.GetAllAsync()).ReturnsAsync([ActiveRegion(), InactiveRegion()]);
|
||||
|
||||
var result = await _sut.GetAllAsync();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.True(result[0].IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllAsync_ReturnsEmpty_WhenNoneActive()
|
||||
{
|
||||
_repo.Setup(r => r.GetAllAsync()).ReturnsAsync([InactiveRegion()]);
|
||||
|
||||
var result = await _sut.GetAllAsync();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
// GetByIdAsync
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsNull_WhenNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Region?)null);
|
||||
|
||||
var result = await _sut.GetByIdAsync(Guid.NewGuid());
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsNull_WhenInactive()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(InactiveRegion());
|
||||
|
||||
var result = await _sut.GetByIdAsync(Guid.NewGuid());
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsResponse_WhenActiveRegionFound()
|
||||
{
|
||||
var region = ActiveRegion("South");
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
|
||||
var result = await _sut.GetByIdAsync(region.Id);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("South", result.Name);
|
||||
Assert.Equal(region.Id, result.Id);
|
||||
}
|
||||
|
||||
// CreateAsync
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_AddsRegion_AndCommits()
|
||||
{
|
||||
var request = new CreateRegionRequest("East");
|
||||
|
||||
var result = await _sut.CreateAsync(request);
|
||||
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Once);
|
||||
_repo.Verify(r => r.AddAsync(It.Is<Region>(x => x.Name == "East")), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
Assert.Equal("East", result.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Rollbacks_WhenAddThrows()
|
||||
{
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Region>())).ThrowsAsync(new Exception("db error"));
|
||||
|
||||
await Assert.ThrowsAsync<Exception>(() => _sut.CreateAsync(new CreateRegionRequest("X")));
|
||||
|
||||
_uow.Verify(u => u.RollbackAsync(default), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
// UpdateAsync
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_ReturnsNull_WhenNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Region?)null);
|
||||
|
||||
var result = await _sut.UpdateAsync(Guid.NewGuid(), new UpdateRegionRequest("New"));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_ReturnsNull_WhenInactive()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(InactiveRegion());
|
||||
|
||||
var result = await _sut.UpdateAsync(Guid.NewGuid(), new UpdateRegionRequest("New"));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_UpdatesName_AndCommits()
|
||||
{
|
||||
var region = ActiveRegion("Old Name");
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
|
||||
var result = await _sut.UpdateAsync(region.Id, new UpdateRegionRequest("New Name"));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("New Name", result.Name);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
// DeleteAsync
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ReturnsFalse_WhenNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Region?)null);
|
||||
|
||||
var result = await _sut.DeleteAsync(Guid.NewGuid());
|
||||
|
||||
Assert.False(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ReturnsFalse_WhenInactive()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(InactiveRegion());
|
||||
|
||||
var result = await _sut.DeleteAsync(Guid.NewGuid());
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_CallsRemove_AndCommits()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
|
||||
var result = await _sut.DeleteAsync(region.Id);
|
||||
|
||||
Assert.True(result);
|
||||
_repo.Verify(r => r.Remove(region), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_Rollbacks_WhenRemoveThrows()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
_repo.Setup(r => r.Remove(It.IsAny<Region>())).Throws(new Exception("db error"));
|
||||
|
||||
await Assert.ThrowsAsync<Exception>(() => _sut.DeleteAsync(region.Id));
|
||||
|
||||
_uow.Verify(u => u.RollbackAsync(default), Times.Once);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class ScopeAccessServiceTests
|
||||
{
|
||||
private readonly Mock<IScopeRepository> _repo = new();
|
||||
private readonly ScopeAccessService _sut;
|
||||
private readonly Guid _userId = Guid.NewGuid();
|
||||
|
||||
public ScopeAccessServiceTests()
|
||||
{
|
||||
_sut = new ScopeAccessService(_repo.Object);
|
||||
}
|
||||
|
||||
// Delegation tests
|
||||
|
||||
[Fact]
|
||||
public async Task HasStoreScopeAsync_DelegatesToRepository()
|
||||
{
|
||||
var storeId = Guid.NewGuid();
|
||||
_repo.Setup(r => r.HasStoreScopeAsync(_userId, storeId)).ReturnsAsync(true);
|
||||
|
||||
var result = await _sut.HasStoreScopeAsync(_userId, storeId);
|
||||
|
||||
Assert.True(result);
|
||||
_repo.Verify(r => r.HasStoreScopeAsync(_userId, storeId), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HasRouteScopeAsync_DelegatesToRepository()
|
||||
{
|
||||
var routeId = Guid.NewGuid();
|
||||
_repo.Setup(r => r.HasRouteScopeAsync(_userId, routeId)).ReturnsAsync(false);
|
||||
|
||||
var result = await _sut.HasRouteScopeAsync(_userId, routeId);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HasRegionScopeAsync_DelegatesToRepository()
|
||||
{
|
||||
var regionId = Guid.NewGuid();
|
||||
_repo.Setup(r => r.HasRegionScopeAsync(_userId, regionId)).ReturnsAsync(true);
|
||||
|
||||
var result = await _sut.HasRegionScopeAsync(_userId, regionId);
|
||||
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
// GetAccessibleStoreIdsAsync
|
||||
|
||||
[Fact]
|
||||
public async Task GetAccessibleStoreIdsAsync_ReturnsEmpty_WhenNoAssignments()
|
||||
{
|
||||
_repo.Setup(r => r.GetDirectStoreIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRouteIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRegionIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(It.IsAny<List<Guid>>())).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRouteIdsByRegionIdsAsync(It.IsAny<List<Guid>>())).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetAccessibleStoreIdsAsync(_userId);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAccessibleStoreIdsAsync_ReturnsDirectStores()
|
||||
{
|
||||
var storeId = Guid.NewGuid();
|
||||
_repo.Setup(r => r.GetDirectStoreIdsAsync(_userId)).ReturnsAsync([storeId]);
|
||||
_repo.Setup(r => r.GetRouteIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRegionIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(It.IsAny<List<Guid>>())).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRouteIdsByRegionIdsAsync(It.IsAny<List<Guid>>())).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetAccessibleStoreIdsAsync(_userId);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Contains(storeId, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAccessibleStoreIdsAsync_IncludesStoresViaRoute()
|
||||
{
|
||||
var routeId = Guid.NewGuid();
|
||||
var storeViaRoute = Guid.NewGuid();
|
||||
_repo.Setup(r => r.GetDirectStoreIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRouteIdsAsync(_userId)).ReturnsAsync([routeId]);
|
||||
_repo.Setup(r => r.GetRegionIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(new List<Guid> { routeId })).ReturnsAsync([storeViaRoute]);
|
||||
_repo.Setup(r => r.GetRouteIdsByRegionIdsAsync(It.IsAny<List<Guid>>())).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(new List<Guid>())).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetAccessibleStoreIdsAsync(_userId);
|
||||
|
||||
Assert.Contains(storeViaRoute, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAccessibleStoreIdsAsync_IncludesStoresViaRegion()
|
||||
{
|
||||
var regionId = Guid.NewGuid();
|
||||
var routeViaRegion = Guid.NewGuid();
|
||||
var storeViaRegion = Guid.NewGuid();
|
||||
_repo.Setup(r => r.GetDirectStoreIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRouteIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRegionIdsAsync(_userId)).ReturnsAsync([regionId]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(new List<Guid>())).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetRouteIdsByRegionIdsAsync(new List<Guid> { regionId })).ReturnsAsync([routeViaRegion]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(new List<Guid> { routeViaRegion })).ReturnsAsync([storeViaRegion]);
|
||||
|
||||
var result = await _sut.GetAccessibleStoreIdsAsync(_userId);
|
||||
|
||||
Assert.Contains(storeViaRegion, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAccessibleStoreIdsAsync_DeduplicatesStores()
|
||||
{
|
||||
var sharedStoreId = Guid.NewGuid();
|
||||
var routeId = Guid.NewGuid();
|
||||
_repo.Setup(r => r.GetDirectStoreIdsAsync(_userId)).ReturnsAsync([sharedStoreId]);
|
||||
_repo.Setup(r => r.GetRouteIdsAsync(_userId)).ReturnsAsync([routeId]);
|
||||
_repo.Setup(r => r.GetRegionIdsAsync(_userId)).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(new List<Guid> { routeId })).ReturnsAsync([sharedStoreId]);
|
||||
_repo.Setup(r => r.GetRouteIdsByRegionIdsAsync(It.IsAny<List<Guid>>())).ReturnsAsync([]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(new List<Guid>())).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetAccessibleStoreIdsAsync(_userId);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(sharedStoreId, result[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAccessibleStoreIdsAsync_CombinesAllSources()
|
||||
{
|
||||
var directStore = Guid.NewGuid();
|
||||
var routeStore = Guid.NewGuid();
|
||||
var regionStore = Guid.NewGuid();
|
||||
var routeId = Guid.NewGuid();
|
||||
var regionId = Guid.NewGuid();
|
||||
var routeViaRegion = Guid.NewGuid();
|
||||
|
||||
_repo.Setup(r => r.GetDirectStoreIdsAsync(_userId)).ReturnsAsync([directStore]);
|
||||
_repo.Setup(r => r.GetRouteIdsAsync(_userId)).ReturnsAsync([routeId]);
|
||||
_repo.Setup(r => r.GetRegionIdsAsync(_userId)).ReturnsAsync([regionId]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(new List<Guid> { routeId })).ReturnsAsync([routeStore]);
|
||||
_repo.Setup(r => r.GetRouteIdsByRegionIdsAsync(new List<Guid> { regionId })).ReturnsAsync([routeViaRegion]);
|
||||
_repo.Setup(r => r.GetStoreIdsByRouteIdsAsync(new List<Guid> { routeViaRegion })).ReturnsAsync([regionStore]);
|
||||
|
||||
var result = await _sut.GetAccessibleStoreIdsAsync(_userId);
|
||||
|
||||
Assert.Equal(3, result.Count);
|
||||
Assert.Contains(directStore, result);
|
||||
Assert.Contains(routeStore, result);
|
||||
Assert.Contains(regionStore, result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user