diff --git a/Directory.Packages.props b/Directory.Packages.props
index 41179c3..2b48da4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -17,13 +17,13 @@
-
-
+
+
-
+
diff --git a/src/RDMG.Core/Abstractions/Repository/IUserRepository.cs b/src/RDMG.Core/Abstractions/Repository/IUserRepository.cs
index 91d9a39..74a27f6 100644
--- a/src/RDMG.Core/Abstractions/Repository/IUserRepository.cs
+++ b/src/RDMG.Core/Abstractions/Repository/IUserRepository.cs
@@ -4,10 +4,13 @@ namespace RDMG.Core.Abstractions.Repository;
public interface IUserRepository
{
- Task CreateAsync(User user);
- Task UpdateAsync(User user);
- Task GetByUsernameAsync(string username, bool? deleted = false);
- Task GetAsync(int id);
- Task> ListAsync(bool? deleted = false);
- Task DeleteAsync(int id);
+ Task CreateAsync(User user, CancellationToken cancellationToken = default);
+ Task UpdateAsync(User user, CancellationToken cancellationToken = default);
+
+ Task GetByUsernameAsync(string username, bool? deleted = false,
+ CancellationToken cancellationToken = default);
+
+ Task GetAsync(int id, CancellationToken cancellationToken = default);
+ Task ListAsync(bool? deleted = false, CancellationToken cancellationToken = default);
+ Task DeleteAsync(int id, CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/RDMG.Core/Abstractions/Services/IDungeonService.cs b/src/RDMG.Core/Abstractions/Services/IDungeonService.cs
index 0d89685..245cf61 100644
--- a/src/RDMG.Core/Abstractions/Services/IDungeonService.cs
+++ b/src/RDMG.Core/Abstractions/Services/IDungeonService.cs
@@ -4,19 +4,28 @@ namespace RDMG.Core.Abstractions.Services;
public interface IDungeonService
{
- Task> GetAllDungeonOptionsAsync(CancellationToken cancellationToken);
- Task> GetAllDungeonOptionsForUserAsync(int userId, CancellationToken cancellationToken);
+ Task GetAllDungeonOptionsAsync(CancellationToken cancellationToken);
+ Task GetAllDungeonOptionsForUserAsync(int userId, CancellationToken cancellationToken);
Task GetDungeonOptionAsync(int id, CancellationToken cancellationToken);
- Task GetDungeonOptionByNameAsync(string dungeonName, int userId, CancellationToken cancellationToken);
+
+ Task GetDungeonOptionByNameAsync(string dungeonName, int userId,
+ CancellationToken cancellationToken);
+
Task GetDungeonAsync(int id, CancellationToken cancellationToken);
- Task CreateOrUpdateDungeonAsync(DungeonOptionModel optionModel, bool addDungeon, int level, CancellationToken cancellationToken);
+
+ Task CreateOrUpdateDungeonAsync(DungeonOptionModel optionModel, bool addDungeon, int level,
+ CancellationToken cancellationToken);
+
Task UpdateDungeonAsync(DungeonModel model, CancellationToken cancellationToken);
Task CreateDungeonOptionAsync(DungeonOptionModel dungeonOption, CancellationToken cancellationToken);
- Task> ListUserDungeonsAsync(int userId, CancellationToken cancellationToken);
- Task> ListUserDungeonsByNameAsync(string dungeonName, int userId, CancellationToken cancellationToken);
+ Task ListUserDungeonsAsync(int userId, CancellationToken cancellationToken);
+
+ Task ListUserDungeonsByNameAsync(string dungeonName, int userId,
+ CancellationToken cancellationToken);
+
Task AddDungeonAsync(DungeonModel savedDungeon, CancellationToken cancellationToken);
Task DeleteDungeonOptionAsync(int id, CancellationToken cancellationToken);
Task DeleteDungeonAsync(int id, CancellationToken cancellationToken);
- Task GenerateDungeonAsync(DungeonOptionModel model);
+ Task GenerateDungeonAsync(DungeonOptionModel model, CancellationToken cancellationToken);
Task RenameDungeonAsync(int optionId, int userId, string newName, CancellationToken cancellationToken);
}
\ No newline at end of file
diff --git a/src/RDMG.Core/Abstractions/Services/IOptionService.cs b/src/RDMG.Core/Abstractions/Services/IOptionService.cs
index 40aa578..2b42d29 100644
--- a/src/RDMG.Core/Abstractions/Services/IOptionService.cs
+++ b/src/RDMG.Core/Abstractions/Services/IOptionService.cs
@@ -5,5 +5,5 @@ namespace RDMG.Core.Abstractions.Services;
public interface IOptionService
{
- Task> ListOptionsAsync(OptionKey? filter = null, CancellationToken cancellationToken = default);
+ Task ListOptionsAsync(OptionKey? filter = null, CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/RDMG.Core/Abstractions/Services/IUserService.cs b/src/RDMG.Core/Abstractions/Services/IUserService.cs
index 0d4bab2..b6c2b2d 100644
--- a/src/RDMG.Core/Abstractions/Services/IUserService.cs
+++ b/src/RDMG.Core/Abstractions/Services/IUserService.cs
@@ -4,11 +4,11 @@ namespace RDMG.Core.Abstractions.Services;
public interface IUserService
{
- Task GetAsync(int id);
- Task UpdateAsync(UserModel model);
- Task CreateAsync(UserModel model);
- Task> ListAsync(bool? deleted = false);
- Task DeleteAsync(int id);
- Task RestoreAsync(int id);
- Task ChangePasswordAsync(ChangePasswordModel model);
+ Task GetAsync(int id, CancellationToken cancellationToken = default);
+ Task UpdateAsync(UserModel model, CancellationToken cancellationToken = default);
+ Task CreateAsync(UserModel model, CancellationToken cancellationToken = default);
+ Task ListAsync(bool? deleted = false, CancellationToken cancellationToken = default);
+ Task DeleteAsync(int id, CancellationToken cancellationToken = default);
+ Task RestoreAsync(int id, CancellationToken cancellationToken = default);
+ Task ChangePasswordAsync(ChangePasswordModel model, CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/RDMG.Core/Services/DungeonService.cs b/src/RDMG.Core/Services/DungeonService.cs
index 8d507d1..15cfb5f 100644
--- a/src/RDMG.Core/Services/DungeonService.cs
+++ b/src/RDMG.Core/Services/DungeonService.cs
@@ -110,7 +110,7 @@ public async Task CreateOrUpdateDungeonAsync(DungeonOptionModel op
private async Task UpdateExistingDungeonAsync(DungeonOptionModel optionModel,
DungeonOptionModel existingDungeonOption, DungeonModel oldDungeon, CancellationToken cancellationToken)
{
- var dungeon = await GenerateDungeonAsync(optionModel, existingDungeonOption.Id);
+ var dungeon = await GenerateDungeonAsync(optionModel, existingDungeonOption.Id, cancellationToken);
dungeon.Id = oldDungeon.Id;
dungeon.Level = oldDungeon.Level;
await UpdateDungeonAsync(dungeon, cancellationToken);
@@ -121,7 +121,7 @@ private async Task CreateOptionAndAddDungeonToItAsync(DungeonOptio
CancellationToken cancellationToken)
{
var createdId = await CreateDungeonOptionAsync(optionModel, cancellationToken);
- var dungeon = await GenerateDungeonAsync(optionModel, createdId);
+ var dungeon = await GenerateDungeonAsync(optionModel, createdId, cancellationToken);
dungeon.Level = 1;
var id = await AddDungeonAsync(dungeon, cancellationToken);
dungeon.Id = id;
@@ -134,7 +134,7 @@ private async Task AddDungeonToExistingOptionAsync(DungeonOptionMo
var existingDungeons =
(await ListUserDungeonsByNameAsync(optionModel.DungeonName, optionModel.UserId, cancellationToken))
.ToList();
- var dungeon = await GenerateDungeonAsync(optionModel, optionModel.Id);
+ var dungeon = await GenerateDungeonAsync(optionModel, optionModel.Id, cancellationToken);
dungeon.Level = level;
if (existingDungeons.Exists(d => d.Level == level))
{
@@ -149,21 +149,23 @@ private async Task AddDungeonToExistingOptionAsync(DungeonOptionMo
return dungeon;
}
- private async Task GenerateDungeonAsync(DungeonOptionModel optionModel, int optionId)
+ private async Task GenerateDungeonAsync(DungeonOptionModel optionModel, int optionId,
+ CancellationToken cancellationToken)
{
- var dungeon = await GenerateDungeonAsync(optionModel);
+ var dungeon = await GenerateDungeonAsync(optionModel, cancellationToken);
dungeon.DungeonOptionId = optionId;
return dungeon;
}
- public async Task GenerateDungeonAsync(DungeonOptionModel model)
+ public async Task GenerateDungeonAsync(DungeonOptionModel model, CancellationToken cancellationToken)
{
try
{
ValidateModel(model);
if (model.Corridor)
- return await Task.FromResult(_dungeon.Generate(model));
- return await Task.FromResult(_dungeonNcDungeon.Generate(model));
+ return await Task.Run(async () => await Task.FromResult(_dungeon.Generate(model)), cancellationToken);
+ return await Task.Run(async () => await Task.FromResult(_dungeonNcDungeon.Generate(model)),
+ cancellationToken);
}
catch (Exception ex)
{
@@ -172,13 +174,13 @@ public async Task GenerateDungeonAsync(DungeonOptionModel model)
}
}
- public async Task> GetAllDungeonOptionsAsync(CancellationToken cancellationToken)
+ public async Task GetAllDungeonOptionsAsync(CancellationToken cancellationToken)
{
try
{
var options = await _dungeonOptionRepository.GetAllDungeonOptionsAsync(cancellationToken);
- return options.Select(_mapper.Map);
+ return [.. options.Select(_mapper.Map)];
}
catch (Exception ex)
{
@@ -187,14 +189,14 @@ public async Task> GetAllDungeonOptionsAsync(Can
}
}
- public async Task> GetAllDungeonOptionsForUserAsync(int userId,
+ public async Task GetAllDungeonOptionsForUserAsync(int userId,
CancellationToken cancellationToken)
{
try
{
var options = await _dungeonOptionRepository.GetAllDungeonOptionsForUserAsync(userId, cancellationToken);
- return options.Select(_mapper.Map);
+ return [.. options.Select(_mapper.Map)];
}
catch (Exception ex)
{
@@ -245,12 +247,12 @@ public async Task DeleteDungeonAsync(int id, CancellationToken cancellatio
}
}
- public async Task> ListUserDungeonsAsync(int userId, CancellationToken cancellationToken)
+ public async Task ListUserDungeonsAsync(int userId, CancellationToken cancellationToken)
{
try
{
var result = await _dungeonRepository.GetAllDungeonsForUserAsync(userId, cancellationToken);
- return result.Select(_mapper.Map);
+ return [.. result.Select(_mapper.Map)];
}
catch (Exception ex)
{
@@ -259,14 +261,14 @@ public async Task> ListUserDungeonsAsync(int userId, C
}
}
- public async Task> ListUserDungeonsByNameAsync(string dungeonName, int userId,
+ public async Task ListUserDungeonsByNameAsync(string dungeonName, int userId,
CancellationToken cancellationToken)
{
try
{
var result =
await _dungeonRepository.GetAllDungeonByOptionNameForUserAsync(dungeonName, userId, cancellationToken);
- return result.Select(_mapper.Map);
+ return [.. result.Select(_mapper.Map)];
}
catch (Exception ex)
{
diff --git a/src/RDMG.Core/Services/OptionService.cs b/src/RDMG.Core/Services/OptionService.cs
index 7483c21..302e578 100644
--- a/src/RDMG.Core/Services/OptionService.cs
+++ b/src/RDMG.Core/Services/OptionService.cs
@@ -19,13 +19,15 @@ public class OptionService(
private readonly IMapper _mapper = mapper;
private readonly ILogger _logger = logger;
- public async Task> ListOptionsAsync(OptionKey? filter = null,
+ public async Task ListOptionsAsync(OptionKey? filter = null,
CancellationToken cancellationToken = default)
{
try
{
- if (_memoryCache.TryGetValue(nameof(ListOptionsAsync), out List? cacheEntry))
- return filter.HasValue ? cacheEntry?.Where(o => o.Key == filter.Value) ?? [] : cacheEntry ?? [];
+ if (_memoryCache.TryGetValue(nameof(ListOptionsAsync), out OptionModel[]? cacheEntry))
+ return filter.HasValue
+ ? cacheEntry?.Where(o => o.Key == filter.Value).ToArray() ?? []
+ : cacheEntry ?? [];
var options = await _optionRepository.ListAsync(null, cancellationToken);
cacheEntry = [.. options.Select(_mapper.Map)];
@@ -35,7 +37,7 @@ public async Task> ListOptionsAsync(OptionKey? filter =
_memoryCache.Set(nameof(ListOptionsAsync), cacheEntry, cacheEntryOptions);
- return filter.HasValue ? cacheEntry.Where(o => o.Key == filter.Value) : cacheEntry;
+ return filter.HasValue ? [.. cacheEntry.Where(o => o.Key == filter.Value)] : cacheEntry;
}
catch (Exception ex)
{
diff --git a/src/RDMG.Core/Services/UserService.cs b/src/RDMG.Core/Services/UserService.cs
index 0b59268..bf62fe0 100644
--- a/src/RDMG.Core/Services/UserService.cs
+++ b/src/RDMG.Core/Services/UserService.cs
@@ -6,6 +6,8 @@
using RDMG.Core.Abstractions.Services.Models;
using RDMG.Core.Domain;
using RDMG.Core.Helpers;
+using RDMG.Resources;
+using User = RDMG.Core.Domain.User;
namespace RDMG.Core.Services;
@@ -15,7 +17,7 @@ public class UserService(IMapper mapper, IUserRepository userRepository, ILogger
private readonly IMapper _mapper = mapper;
private readonly ILogger _logger = logger;
- public async Task CreateAsync(UserModel model)
+ public async Task CreateAsync(UserModel model, CancellationToken cancellationToken = default)
{
ValidateModel(model);
await CheckUserExistAsync(model);
@@ -23,7 +25,7 @@ public async Task CreateAsync(UserModel model)
{
model.Password = PasswordHelper.EncryptPassword(model.Password);
var user = _mapper.Map(model);
- user = await _userRepository.CreateAsync(user);
+ user = await _userRepository.CreateAsync(user, cancellationToken);
return user.Id;
}
catch (Exception ex)
@@ -38,17 +40,17 @@ private static void ValidateModel(UserModel model)
var errors = new List();
ArgumentNullException.ThrowIfNull(model);
if (model.Password.Length < 8)
- errors.Add(new ServiceException(Resources.Error.PasswordLength));
+ errors.Add(new ServiceException(Error.PasswordLength));
if (string.IsNullOrEmpty(model.Username))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Username)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.Username))));
if (string.IsNullOrEmpty(model.FirstName))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.FirstName)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.FirstName))));
if (string.IsNullOrEmpty(model.LastName))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.LastName)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.LastName))));
if (string.IsNullOrEmpty(model.Email))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Email)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.Email))));
if (string.IsNullOrEmpty(model.Role))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Role)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.Role))));
if (errors.Count != 0)
throw new ServiceAggregateException(errors);
}
@@ -57,14 +59,14 @@ private async Task CheckUserExistAsync(UserModel model)
{
var user = await _userRepository.GetByUsernameAsync(model.Username, null);
if (user is not null)
- throw new ServiceException(string.Format(Resources.Error.UserExist, model.Username));
+ throw new ServiceException(string.Format(Error.UserExist, model.Username));
}
- public async Task DeleteAsync(int id)
+ public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
{
try
{
- return await _userRepository.DeleteAsync(id);
+ return await _userRepository.DeleteAsync(id, cancellationToken);
}
catch (Exception ex)
{
@@ -73,12 +75,12 @@ public async Task DeleteAsync(int id)
}
}
- public async Task GetAsync(int id)
+ public async Task GetAsync(int id, CancellationToken cancellationToken = default)
{
try
{
- var user = await _userRepository.GetAsync(id) ??
- throw new ServiceException(Resources.Error.NotFound);
+ var user = await _userRepository.GetAsync(id, cancellationToken) ??
+ throw new ServiceException(Error.NotFound);
return _mapper.Map(user);
}
@@ -89,11 +91,11 @@ public async Task GetAsync(int id)
}
}
- public async Task> ListAsync(bool? deleted = false)
+ public async Task ListAsync(bool? deleted = false, CancellationToken cancellationToken = default)
{
try
{
- var result = await _userRepository.ListAsync(deleted);
+ var result = await _userRepository.ListAsync(deleted, cancellationToken);
return [.. result.Select(_mapper.Map).OrderBy(um => um.Username)];
}
@@ -104,18 +106,19 @@ public async Task> ListAsync(bool? deleted = false)
}
}
- public async Task RestoreAsync(int id)
+ public async Task RestoreAsync(int id, CancellationToken cancellationToken = default)
{
try
{
- var user = await _userRepository.GetAsync(id);
+ var user = await _userRepository.GetAsync(id, cancellationToken);
if (user is not null)
{
user.IsDeleted = false;
- await _userRepository.UpdateAsync(user);
+ await _userRepository.UpdateAsync(user, cancellationToken);
+ return true;
}
- return true;
+ return false;
}
catch (Exception ex)
{
@@ -124,19 +127,19 @@ public async Task RestoreAsync(int id)
}
}
- public async Task UpdateAsync(UserModel model)
+ public async Task UpdateAsync(UserModel model, CancellationToken cancellationToken = default)
{
ValidateModelForEdit(model);
try
{
- var user = await _userRepository.GetAsync(model.Id);
+ var user = await _userRepository.GetAsync(model.Id, cancellationToken);
if (user is not null)
{
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.Email = model.Email;
user.Role = Enum.Parse(model.Role);
- await _userRepository.UpdateAsync(user);
+ await _userRepository.UpdateAsync(user, cancellationToken);
}
}
catch (Exception ex)
@@ -151,32 +154,33 @@ private static void ValidateModelForEdit(UserModel model)
var errors = new List();
ArgumentNullException.ThrowIfNull(model);
if (string.IsNullOrEmpty(model.FirstName))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.FirstName)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.FirstName))));
if (string.IsNullOrEmpty(model.LastName))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.LastName)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.LastName))));
if (string.IsNullOrEmpty(model.Email))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Email)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.Email))));
if (string.IsNullOrEmpty(model.Role))
- errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Role)));
+ errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.Role))));
if (errors.Count != 0)
throw new ServiceAggregateException(errors);
}
- public async Task ChangePasswordAsync(ChangePasswordModel model)
+ public async Task ChangePasswordAsync(ChangePasswordModel model, CancellationToken cancellationToken = default)
{
try
{
if (model.NewPassword.Length < 8)
- throw new ServiceException(Resources.Error.PasswordLength);
+ throw new ServiceException(Error.PasswordLength);
- var user = await _userRepository.GetAsync(model.Id) ?? throw new ServiceException(Resources.Error.NotFound);
+ var user = await _userRepository.GetAsync(model.Id, cancellationToken) ??
+ throw new ServiceException(Error.NotFound);
if (!PasswordHelper.CheckPassword(user.Password, model.CurrentPassword))
- throw new ServiceException(Resources.Error.PasswordMissMatch);
+ throw new ServiceException(Error.PasswordMissMatch);
user.Password = PasswordHelper.EncryptPassword(model.NewPassword);
- await _userRepository.UpdateAsync(user);
+ await _userRepository.UpdateAsync(user, cancellationToken);
}
catch (Exception ex)
{
diff --git a/src/RDMG.Infrastructure/Data/AppDbContextInitializer.cs b/src/RDMG.Infrastructure/Data/AppDbContextInitializer.cs
index 8dd5276..6e6a088 100644
--- a/src/RDMG.Infrastructure/Data/AppDbContextInitializer.cs
+++ b/src/RDMG.Infrastructure/Data/AppDbContextInitializer.cs
@@ -15,6 +15,10 @@ public class AppDbContextInitializer(IAppDbContext context, IDungeonService dung
private readonly IDungeonService _dungeonService = dungeonService;
private const string UtDungeonName1 = "Test 1";
public const string UtDungeonName2 = "Test 2";
+ public const int TestAdminUserId = 1;
+ public const int TestUserId = 2;
+ public const int TestDeletedUserId = 3;
+ public const int TestNotExistingUserId = 999;
public async Task UpdateAsync(CancellationToken cancellationToken)
{
@@ -34,7 +38,7 @@ public async Task SeedDataAsync(CancellationToken cancellationToken)
{
await SeedUsersAsync(cancellationToken);
await SeedOptionsAsync(cancellationToken);
- await SeedDungeonsAsync(1, cancellationToken);
+ await SeedDungeonsAsync(TestAdminUserId, cancellationToken);
}
}
@@ -42,10 +46,10 @@ public async Task SeedTestBaseAsync(CancellationToken cancellationToken)
{
if (!_context.Users.Any())
{
- await SeedUsersAsync(cancellationToken);
+ await SeedUsersAsync(cancellationToken, true);
await SeedOptionsAsync(cancellationToken);
- await SeedDungeonsAsync(1, cancellationToken);
- await SeedDungeonsAsync(2, cancellationToken);
+ await SeedDungeonsAsync(TestAdminUserId, cancellationToken);
+ await SeedDungeonsAsync(TestUserId, cancellationToken);
}
}
@@ -469,7 +473,7 @@ private async Task SeedDungeonsAsync(int userId, CancellationToken token)
UserId = dungeonOption.UserId
};
- var sd = await _dungeonService.GenerateDungeonAsync(model);
+ var sd = await _dungeonService.GenerateDungeonAsync(model, token);
sd.Level = 1;
await _dungeonService.AddDungeonAsync(sd, token);
@@ -516,15 +520,16 @@ private async Task SeedDungeonsAsync(int userId, CancellationToken token)
UserId = dungeonOption.UserId
};
- sd = await _dungeonService.GenerateDungeonAsync(model);
+ sd = await _dungeonService.GenerateDungeonAsync(model, token);
sd.Level = 1;
await _dungeonService.AddDungeonAsync(sd, token);
}
- private async Task SeedUsersAsync(CancellationToken token)
+ private async Task SeedUsersAsync(CancellationToken token, bool seedDeletedUtUser = false)
{
_context.Users.Add(new User
{
+ Id = TestAdminUserId,
Username = "TestAdmin",
Password = PasswordHelper.EncryptPassword(config.Value.DefaultAdminPassword),
FirstName = "Test",
@@ -535,6 +540,7 @@ private async Task SeedUsersAsync(CancellationToken token)
_context.Users.Add(new User
{
+ Id = TestUserId,
Username = "TestUser",
Password = PasswordHelper.EncryptPassword(config.Value.DefaultUserPassword),
FirstName = "Test",
@@ -542,6 +548,22 @@ private async Task SeedUsersAsync(CancellationToken token)
Email = "user@user.com",
Role = Role.User
});
+
+ if (seedDeletedUtUser)
+ {
+ _context.Users.Add(new User
+ {
+ Id = TestDeletedUserId,
+ Username = "UT Deleted User",
+ Password = PasswordHelper.EncryptPassword(config.Value.DefaultUserPassword),
+ FirstName = "Test",
+ LastName = "User",
+ Email = "user@user.com",
+ Role = Role.User,
+ IsDeleted = true
+ });
+ }
+
await _context.SaveChangesAsync(token);
}
}
\ No newline at end of file
diff --git a/src/RDMG.Infrastructure/Repository/UserRepository.cs b/src/RDMG.Infrastructure/Repository/UserRepository.cs
index 2e5ad35..c7dd1d2 100644
--- a/src/RDMG.Infrastructure/Repository/UserRepository.cs
+++ b/src/RDMG.Infrastructure/Repository/UserRepository.cs
@@ -13,37 +13,40 @@ public class UserRepository(IAppDbContext context, IMapper mapper, ILogger _logger = logger;
private readonly IMapper _mapper = mapper;
- public async Task CreateAsync(User user)
+ public async Task CreateAsync(User user, CancellationToken cancellationToken = default)
{
_context.Users.Add(user);
- await _context.SaveChangesAsync(CancellationToken.None);
+ await _context.SaveChangesAsync(cancellationToken);
return user;
}
- public async Task GetAsync(int id)
+ public async Task GetAsync(int id, CancellationToken cancellationToken = default)
{
- return await _context.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Id == id);
+ return await _context.Users.AsNoTracking()
+ .FirstOrDefaultAsync(u => u.Id == id, cancellationToken: cancellationToken);
}
- public async Task UpdateAsync(User user)
+ public async Task UpdateAsync(User user, CancellationToken cancellationToken = default)
{
- var local = await _context.Users.FirstOrDefaultAsync(u => u.Id == user.Id);
+ var local = await _context.Users.FirstOrDefaultAsync(u => u.Id == user.Id,
+ cancellationToken: cancellationToken);
if (local is null)
return null;
_mapper.Map(user, local);
- await _context.SaveChangesAsync(CancellationToken.None);
+ await _context.SaveChangesAsync(cancellationToken);
return local;
}
- public async Task DeleteAsync(int id)
+
+ public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
{
- var local = await _context.Users.FirstOrDefaultAsync(u => u.Id == id);
+ var local = await _context.Users.FirstOrDefaultAsync(u => u.Id == id, cancellationToken: cancellationToken);
if (local is not null)
{
local.IsDeleted = true;
- await _context.SaveChangesAsync(CancellationToken.None);
+ await _context.SaveChangesAsync(cancellationToken);
return true;
}
@@ -51,23 +54,24 @@ public async Task DeleteAsync(int id)
return false;
}
- public async Task GetByUsernameAsync(string username, bool? deleted = false)
+ public async Task GetByUsernameAsync(string username, bool? deleted = false,
+ CancellationToken cancellationToken = default)
{
var query = _context.Users.AsNoTracking();
if (deleted.HasValue)
query = query.Where(x => x.IsDeleted == deleted.Value);
- return await query.FirstOrDefaultAsync(u => u.Username == username);
+ return await query.FirstOrDefaultAsync(u => u.Username == username, cancellationToken: cancellationToken);
}
- public async Task> ListAsync(bool? deleted = false)
+ public async Task ListAsync(bool? deleted = false, CancellationToken cancellationToken = default)
{
var query = _context.Users.AsNoTracking();
if (deleted.HasValue)
query = query.Where(x => x.IsDeleted == deleted.Value);
- return await query.ToListAsync();
+ return await query.ToArrayAsync(cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/RDMG.Web/RDMG.Web.csproj b/src/RDMG.Web/RDMG.Web.csproj
index 3cecb34..37ee94c 100644
--- a/src/RDMG.Web/RDMG.Web.csproj
+++ b/src/RDMG.Web/RDMG.Web.csproj
@@ -2,7 +2,7 @@
true
true
- 1.0.4.1
+ 1.0.4.2
b2a492d1-90ea-4e42-b3e5-5d962495bef1
diff --git a/tests/RDMG.Core.Tests/DungeonServiceTests/Create.cs b/tests/RDMG.Core.Tests/DungeonServiceTests/Create.cs
index 9368723..fc3c137 100644
--- a/tests/RDMG.Core.Tests/DungeonServiceTests/Create.cs
+++ b/tests/RDMG.Core.Tests/DungeonServiceTests/Create.cs
@@ -45,7 +45,7 @@ public async Task AddDungeonAsync_WithDungeonModel_ReturnsNewEntityId()
{
var optionsModel =
(await _dungeonService.GetAllDungeonOptionsForUserAsync(1, TestContext.Current.CancellationToken)).First();
- var dungeon = await _dungeonService.GenerateDungeonAsync(optionsModel);
+ var dungeon = await _dungeonService.GenerateDungeonAsync(optionsModel, TestContext.Current.CancellationToken);
var result = await _dungeonService.AddDungeonAsync(dungeon, TestContext.Current.CancellationToken);
result.ShouldBeGreaterThan(1);
diff --git a/tests/RDMG.Core.Tests/DungeonServiceTests/Generate.cs b/tests/RDMG.Core.Tests/DungeonServiceTests/Generate.cs
index df25004..7e048bc 100644
--- a/tests/RDMG.Core.Tests/DungeonServiceTests/Generate.cs
+++ b/tests/RDMG.Core.Tests/DungeonServiceTests/Generate.cs
@@ -30,7 +30,7 @@ public async Task GenerateDungeonAsync_WithValidOptionModel_ReturnsDungeonModel(
RoomSize = 20,
Corridor = false,
UserId = 1
- });
+ }, TestContext.Current.CancellationToken);
result.DungeonTiles.ShouldNotBeNull();
}
diff --git a/tests/RDMG.Core.Tests/TestFixture.cs b/tests/RDMG.Core.Tests/TestFixture.cs
index 60b4cdd..77a20a2 100644
--- a/tests/RDMG.Core.Tests/TestFixture.cs
+++ b/tests/RDMG.Core.Tests/TestFixture.cs
@@ -1,4 +1,6 @@
-using RDMG.Core.Abstractions.Generator;
+using Microsoft.Extensions.Options;
+using RDMG.Core.Abstractions.Configuration;
+using RDMG.Core.Abstractions.Generator;
using RDMG.Core.Abstractions.Repository;
using RDMG.Core.Abstractions.Services;
@@ -10,6 +12,8 @@ public class TestFixture : IDisposable
public readonly IOptionService OptionService;
public readonly IOptionRepository OptionRepository;
public readonly IDungeonNoCorridor DungeonNoCorridor;
+ public readonly IUserService UserService;
+ public readonly IOptions Config;
private readonly TestEnvironment _env = new();
private bool _disposedValue;
@@ -18,6 +22,8 @@ public TestFixture()
DungeonService = _env.GetService();
OptionService = _env.GetService();
OptionRepository = _env.GetService();
+ UserService = _env.GetService();
+ Config = _env.GetService>();
DungeonNoCorridor = _env.GetNcDungeon();
}
diff --git a/tests/RDMG.Core.Tests/UserServiceTests/ChangePassword.cs b/tests/RDMG.Core.Tests/UserServiceTests/ChangePassword.cs
new file mode 100644
index 0000000..4d2c91d
--- /dev/null
+++ b/tests/RDMG.Core.Tests/UserServiceTests/ChangePassword.cs
@@ -0,0 +1,71 @@
+using Microsoft.Extensions.Options;
+using RDMG.Core.Abstractions.Configuration;
+using RDMG.Core.Abstractions.Services;
+using RDMG.Core.Abstractions.Services.Exceptions;
+using RDMG.Core.Abstractions.Services.Models;
+using RDMG.Infrastructure.Data;
+using RDMG.Resources;
+using Shouldly;
+using Xunit;
+
+namespace RDMG.Core.Tests.UserServiceTests;
+
+public class ChangePassword(TestFixture fixture) : IClassFixture
+{
+ private readonly IUserService _userService = fixture.UserService;
+ private readonly IOptions _config = fixture.Config;
+ private const string Password = "SOMENEWPASSWORD123";
+
+ [Fact]
+ public async Task ChangePasswordAsync_WithValidInput_ChangesPassword()
+ {
+ var model = new ChangePasswordModel
+ {
+ Id = AppDbContextInitializer.TestUserId,
+ CurrentPassword = _config.Value.DefaultUserPassword,
+ NewPassword = Password
+ };
+ var oldUserModel = await _userService.GetAsync(model.Id, TestContext.Current.CancellationToken);
+ var act = async () => { await _userService.ChangePasswordAsync(model, TestContext.Current.CancellationToken); };
+
+ await act.ShouldNotThrowAsync();
+ var newUserModel = await _userService.GetAsync(model.Id, TestContext.Current.CancellationToken);
+ newUserModel.Password.ShouldNotBe(oldUserModel.Password);
+ }
+
+ public static TheoryData GetModelsWithErrors()
+ {
+ return
+ [
+ (new ChangePasswordModel
+ {
+ NewPassword = "length"
+ }, Error.PasswordLength),
+ (new ChangePasswordModel
+ {
+ NewPassword = Password,
+ Id = AppDbContextInitializer.TestNotExistingUserId,
+ }, Error.NotFound),
+ (new ChangePasswordModel
+ {
+ NewPassword = Password,
+ Id = AppDbContextInitializer.TestUserId,
+ CurrentPassword = "wrong"
+ }, Error.PasswordMissMatch)
+ ];
+ }
+
+ [Theory]
+ [MemberData(nameof(GetModelsWithErrors), MemberType = typeof(ChangePassword))]
+ public async Task ChangePasswordAsync_WithInValidModel_ThrowsServiceException(ChangePasswordModel model,
+ string expectedError)
+ {
+ var act = async () =>
+ {
+ await _userService.ChangePasswordAsync(model, cancellationToken: TestContext.Current.CancellationToken);
+ };
+
+ var result = await act.ShouldThrowAsync();
+ result.Message.ShouldBe(expectedError);
+ }
+}
\ No newline at end of file
diff --git a/tests/RDMG.Core.Tests/UserServiceTests/Create.cs b/tests/RDMG.Core.Tests/UserServiceTests/Create.cs
new file mode 100644
index 0000000..d15e15d
--- /dev/null
+++ b/tests/RDMG.Core.Tests/UserServiceTests/Create.cs
@@ -0,0 +1,58 @@
+using RDMG.Core.Abstractions.Services;
+using RDMG.Core.Abstractions.Services.Exceptions;
+using RDMG.Core.Abstractions.Services.Models;
+using RDMG.Core.Domain;
+using RDMG.Infrastructure.Data;
+using RDMG.Resources;
+using Shouldly;
+using Xunit;
+
+namespace RDMG.Core.Tests.UserServiceTests;
+
+public class Create(TestFixture fixture) : IClassFixture
+{
+ private readonly IUserService _userService = fixture.UserService;
+
+ [Fact]
+ public async Task CreateAsync_WithValidModel_CreatesUser()
+ {
+ var model = new UserModel
+ {
+ Username = "ddd",
+ Password = "asdasdada+mnn!",
+ Email = "dasd@test.com",
+ FirstName = "John",
+ LastName = "Doe",
+ Role = nameof(Role.Admin)
+ };
+ var result = await _userService.CreateAsync(model, cancellationToken: TestContext.Current.CancellationToken);
+ result.ShouldBeGreaterThan(AppDbContextInitializer.TestDeletedUserId);
+ }
+
+ [Fact]
+ public async Task CreateAsync_WithInValidModel_ThrowsServiceAggregateException()
+ {
+ var model = new UserModel();
+ var expectedErrors = new List
+ {
+ string.Format(Error.RequiredValidation, nameof(model.Username)),
+ string.Format(Error.RequiredValidation, nameof(model.FirstName)),
+ string.Format(Error.RequiredValidation, nameof(model.LastName)),
+ string.Format(Error.RequiredValidation, nameof(model.Email)),
+ string.Format(Error.RequiredValidation, nameof(model.Role)),
+ Error.PasswordLength
+ };
+
+ var act = async () =>
+ {
+ await _userService.CreateAsync(model, cancellationToken: TestContext.Current.CancellationToken);
+ };
+
+ var result = await act.ShouldThrowAsync();
+ result.GetInnerExceptions()
+ .Select(se => se.Message)
+ .OrderBy(s => s)
+ .SequenceEqual(expectedErrors.OrderBy(s => s))
+ .ShouldBeTrue();
+ }
+}
\ No newline at end of file
diff --git a/tests/RDMG.Core.Tests/UserServiceTests/Delete.cs b/tests/RDMG.Core.Tests/UserServiceTests/Delete.cs
new file mode 100644
index 0000000..12a21d2
--- /dev/null
+++ b/tests/RDMG.Core.Tests/UserServiceTests/Delete.cs
@@ -0,0 +1,27 @@
+using RDMG.Core.Abstractions.Services;
+using RDMG.Infrastructure.Data;
+using Shouldly;
+using Xunit;
+
+namespace RDMG.Core.Tests.UserServiceTests;
+
+public class Delete(TestFixture fixture) : IClassFixture
+{
+ private readonly IUserService _userService = fixture.UserService;
+
+ [Fact]
+ public async Task DeleteAsync_WithValidId_ReturnsTrue()
+ {
+ var result = await _userService.DeleteAsync(AppDbContextInitializer.TestAdminUserId,
+ cancellationToken: TestContext.Current.CancellationToken);
+ result.ShouldBe(true);
+ }
+
+ [Fact]
+ public async Task DeleteAsync_WithInValidId_ReturnsFalse()
+ {
+ var result = await _userService.DeleteAsync(AppDbContextInitializer.TestNotExistingUserId,
+ cancellationToken: TestContext.Current.CancellationToken);
+ result.ShouldBe(false);
+ }
+}
\ No newline at end of file
diff --git a/tests/RDMG.Core.Tests/UserServiceTests/Get.cs b/tests/RDMG.Core.Tests/UserServiceTests/Get.cs
new file mode 100644
index 0000000..6f77331
--- /dev/null
+++ b/tests/RDMG.Core.Tests/UserServiceTests/Get.cs
@@ -0,0 +1,22 @@
+using RDMG.Core.Abstractions.Services;
+using RDMG.Infrastructure.Data;
+using Shouldly;
+using Xunit;
+
+namespace RDMG.Core.Tests.UserServiceTests;
+
+public class Get(TestFixture fixture) : IClassFixture
+{
+ private readonly IUserService _userService = fixture.UserService;
+
+ [Theory]
+ [InlineData(AppDbContextInitializer.TestAdminUserId)]
+ [InlineData(AppDbContextInitializer.TestUserId)]
+ [InlineData(AppDbContextInitializer.TestDeletedUserId)]
+ public async Task GetAsync_WithValidId_ReturnsUser(int id)
+ {
+ var result = await _userService.GetAsync(id, TestContext.Current.CancellationToken);
+ result.ShouldNotBeNull();
+ result.Id.ShouldBe(id);
+ }
+}
\ No newline at end of file
diff --git a/tests/RDMG.Core.Tests/UserServiceTests/List.cs b/tests/RDMG.Core.Tests/UserServiceTests/List.cs
new file mode 100644
index 0000000..509b665
--- /dev/null
+++ b/tests/RDMG.Core.Tests/UserServiceTests/List.cs
@@ -0,0 +1,19 @@
+using RDMG.Core.Abstractions.Services;
+using Shouldly;
+using Xunit;
+
+namespace RDMG.Core.Tests.UserServiceTests;
+
+public class List(TestFixture fixture) : IClassFixture
+{
+ private readonly IUserService _userService = fixture.UserService;
+
+ [Theory]
+ [InlineData(false, 2)]
+ [InlineData(true, 1)]
+ public async Task ListAsync_WithDeletedFlag_ReturnsAllUsers(bool deleted, int expectedCount)
+ {
+ var result = await _userService.ListAsync(deleted, cancellationToken: TestContext.Current.CancellationToken);
+ result.Length.ShouldBe(expectedCount);
+ }
+}
\ No newline at end of file
diff --git a/tests/RDMG.Core.Tests/UserServiceTests/Restore.cs b/tests/RDMG.Core.Tests/UserServiceTests/Restore.cs
new file mode 100644
index 0000000..3e92c7c
--- /dev/null
+++ b/tests/RDMG.Core.Tests/UserServiceTests/Restore.cs
@@ -0,0 +1,27 @@
+using RDMG.Core.Abstractions.Services;
+using RDMG.Infrastructure.Data;
+using Shouldly;
+using Xunit;
+
+namespace RDMG.Core.Tests.UserServiceTests;
+
+public class Restore(TestFixture fixture) : IClassFixture
+{
+ private readonly IUserService _userService = fixture.UserService;
+
+ [Fact]
+ public async Task RestoreAsync_WithValidId_ReturnsTrue()
+ {
+ var result = await _userService.RestoreAsync(AppDbContextInitializer.TestDeletedUserId,
+ cancellationToken: TestContext.Current.CancellationToken);
+ result.ShouldBe(true);
+ }
+
+ [Fact]
+ public async Task RestoreAsync_WithInValidId_ReturnsFalse()
+ {
+ var result = await _userService.RestoreAsync(AppDbContextInitializer.TestNotExistingUserId,
+ cancellationToken: TestContext.Current.CancellationToken);
+ result.ShouldBe(false);
+ }
+}
\ No newline at end of file
diff --git a/tests/RDMG.Core.Tests/UserServiceTests/Update.cs b/tests/RDMG.Core.Tests/UserServiceTests/Update.cs
new file mode 100644
index 0000000..266febe
--- /dev/null
+++ b/tests/RDMG.Core.Tests/UserServiceTests/Update.cs
@@ -0,0 +1,52 @@
+using RDMG.Core.Abstractions.Services;
+using RDMG.Core.Abstractions.Services.Exceptions;
+using RDMG.Core.Abstractions.Services.Models;
+using RDMG.Infrastructure.Data;
+using RDMG.Resources;
+using Shouldly;
+using Xunit;
+
+namespace RDMG.Core.Tests.UserServiceTests;
+
+public class Update(TestFixture fixture) : IClassFixture
+{
+ private readonly IUserService _userService = fixture.UserService;
+ private const string ModifiedText = "Modified";
+
+ [Fact]
+ public async Task UpdateAsync_WithValidModel_UpdatesUser()
+ {
+ var adminUser = await _userService.GetAsync(AppDbContextInitializer.TestAdminUserId,
+ cancellationToken: TestContext.Current.CancellationToken);
+ adminUser.FirstName = ModifiedText;
+ await _userService.UpdateAsync(adminUser, cancellationToken: TestContext.Current.CancellationToken);
+ var result =
+ await _userService.GetAsync(adminUser.Id, cancellationToken: TestContext.Current.CancellationToken);
+ result.FirstName.ShouldBe(adminUser.FirstName);
+ }
+
+ [Fact]
+ public async Task UpdateAsync_WithInValidModel_ThrowsServiceAggregateException()
+ {
+ var model = new UserModel();
+ var expectedErrors = new List
+ {
+ string.Format(Error.RequiredValidation, nameof(model.FirstName)),
+ string.Format(Error.RequiredValidation, nameof(model.LastName)),
+ string.Format(Error.RequiredValidation, nameof(model.Email)),
+ string.Format(Error.RequiredValidation, nameof(model.Role))
+ };
+
+ var act = async () =>
+ {
+ await _userService.UpdateAsync(model, cancellationToken: TestContext.Current.CancellationToken);
+ };
+
+ var result = await act.ShouldThrowAsync();
+ result.GetInnerExceptions()
+ .Select(se => se.Message)
+ .OrderBy(s => s)
+ .SequenceEqual(expectedErrors.OrderBy(s => s))
+ .ShouldBeTrue();
+ }
+}
\ No newline at end of file