diff --git a/Open5ETools.slnx b/Open5ETools.slnx index b192923..98d020d 100644 --- a/Open5ETools.slnx +++ b/Open5ETools.slnx @@ -1,4 +1,15 @@ + + + + + + + + + + + diff --git a/src/Open5ETools.Core/Common/Configurations/AppConfig.cs b/src/Open5ETools.Core/Common/Configurations/AppConfig.cs index 2ca4b45..7e8c936 100644 --- a/src/Open5ETools.Core/Common/Configurations/AppConfig.cs +++ b/src/Open5ETools.Core/Common/Configurations/AppConfig.cs @@ -5,8 +5,6 @@ namespace Open5ETools.Core.Common.Configurations; public class AppConfigOptions { public const string AppConfig = "Config"; - [Required] - public string DefaultAdminPassword { get; set; } = string.Empty; - [Required] - public string DefaultUserPassword { get; set; } = string.Empty; + [Required] public required string DefaultAdminPassword { get; set; } + [Required] public required string DefaultUserPassword { get; set; } } \ No newline at end of file diff --git a/src/Open5ETools.Core/Common/Helpers/DungeonHelper.cs b/src/Open5ETools.Core/Common/Helpers/DungeonHelper.cs index 47a55a4..4b59ff1 100644 --- a/src/Open5ETools.Core/Common/Helpers/DungeonHelper.cs +++ b/src/Open5ETools.Core/Common/Helpers/DungeonHelper.cs @@ -15,7 +15,7 @@ public class DungeonHelper(IAppDbContext context) : IDungeonHelper private readonly IAppDbContext _context = context; // encounter - private IList _filteredMonsters = []; + private List _filteredMonsters = []; private int _sumXp; private readonly int[] _difficulty = @@ -31,20 +31,19 @@ public class DungeonHelper(IAppDbContext context) : IDungeonHelper // door private DungeonTile[][] DungeonTiles { get; set; } = []; - private ICollection DoorList { get; set; } = []; + private List DoorList { get; set; } = []; private int _westCount; private int _southCount; private int _eastCount; private int _northCount; - private IList TreasureList { get; set; } = []; - private IList _monsterList = []; + private List TreasureList { get; set; } = []; - private IList MonsterList + private List MonsterList { - get => _monsterList; - set => _monsterList = GetMonsters(value); // get monsters for party level - } + get; + set => field = GetMonsters(value); // get monsters for party level + } = []; private int PartyLevel { get; set; } private int PartySize { get; set; } @@ -85,7 +84,7 @@ public static int GetRandomInt(int min, int max) } public void AddRoomDescription(DungeonTile[][] dungeonTiles, int x, int y, - ICollection roomDescription, ICollection currentDoors) + List roomDescription, List currentDoors) { dungeonTiles[x][y].Index = roomDescription.Count; DungeonTiles = dungeonTiles; @@ -98,7 +97,7 @@ public void AddRoomDescription(DungeonTile[][] dungeonTiles, int x, int y, dungeonTiles[x][y].Description = Convert.ToString(roomDescription.Count); } - public void AddTrapDescription(DungeonTile dungeonTile, ICollection trapDescription) + public void AddTrapDescription(DungeonTile dungeonTile, List trapDescription) { dungeonTile.Index = trapDescription.Count; trapDescription.Add(new TrapDescription( @@ -108,7 +107,7 @@ public void AddTrapDescription(DungeonTile dungeonTile, ICollection roamingMonsterDescription) + List roamingMonsterDescription) { dungeonTile.Index = roamingMonsterDescription.Count; roamingMonsterDescription.Add(new RoamingMonsterDescription( @@ -127,7 +126,7 @@ private static string GetRoomName(int x) return "#ROOM" + x + "#"; } - public void AddNcRoomDescription(DungeonTile dungeonTile, ICollection roomDescription, + public void AddNcRoomDescription(DungeonTile dungeonTile, List roomDescription, string doors) { dungeonTile.Index = roomDescription.Count; @@ -480,7 +479,7 @@ public bool CheckNcDoor(DungeonTile dungeonTile) or Texture.NoCorridorDoorTrapped; } - public string GetNcDoorDescription(DungeonTile[][] dungeonTiles, IEnumerable closedList) + public string GetNcDoorDescription(DungeonTile[][] dungeonTiles, List closedList) { _westCount = 1; _southCount = 1; diff --git a/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeon.cs b/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeon.cs index b10ebaf..5f2bc60 100644 --- a/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeon.cs +++ b/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeon.cs @@ -6,7 +6,7 @@ namespace Open5ETools.Core.Common.Interfaces.Services.DM.Generator; public interface IDungeon { DungeonTile[][] DungeonTiles { get; set; } - ICollection RoomDescription { get; set; } + List RoomDescription { get; set; } DungeonModel Generate(DungeonOptionModel model); void AddEntryPoint(); void Init(DungeonOptionModel optionModel); diff --git a/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeonHelper.cs b/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeonHelper.cs index 7a7c7a8..c17ac43 100644 --- a/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeonHelper.cs +++ b/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeonHelper.cs @@ -5,14 +5,14 @@ namespace Open5ETools.Core.Common.Interfaces.Services.DM.Generator; public interface IDungeonHelper { - void AddRoomDescription(DungeonTile[][] dungeonTiles, int x, int y, ICollection roomDescription, ICollection currentDoors); - void AddTrapDescription(DungeonTile dungeonTile, ICollection trapDescription); - void AddRoamingMonsterDescription(DungeonTile dungeonTile, ICollection roamingMonsterDescription); + void AddRoomDescription(DungeonTile[][] dungeonTiles, int x, int y, List roomDescription, List currentDoors); + void AddTrapDescription(DungeonTile dungeonTile, List trapDescription); + void AddRoamingMonsterDescription(DungeonTile dungeonTile, List roamingMonsterDescription); int Manhattan(int dx, int dy); - void AddNcRoomDescription(DungeonTile dungeonTile, ICollection roomDescription, string doors); + void AddNcRoomDescription(DungeonTile dungeonTile, List roomDescription, string doors); void Init(DungeonOptionModel model); bool CheckNcDoor(DungeonTile dungeonTile); - string GetNcDoorDescription(DungeonTile[][] dungeonTiles, IEnumerable closedList); + string GetNcDoorDescription(DungeonTile[][] dungeonTiles, List closedList); string GetNcDoor(DungeonTile door); string GetTreasure(); string GetMonsterDescription(); diff --git a/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeonNoCorridor.cs b/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeonNoCorridor.cs index 4469264..eef898a 100644 --- a/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeonNoCorridor.cs +++ b/src/Open5ETools.Core/Common/Interfaces/Services/DM/Generator/IDungeonNoCorridor.cs @@ -5,8 +5,8 @@ namespace Open5ETools.Core.Common.Interfaces.Services.DM.Generator; public interface IDungeonNoCorridor { DungeonTile[][] DungeonTiles { get; set; } - ICollection RoomDescription { get; set; } - IList OpenDoorList { get; set; } + List RoomDescription { get; set; } + List OpenDoorList { get; set; } DungeonModel Generate(DungeonOptionModel model); void AddEntryPoint(); void Init(DungeonOptionModel optionModel); diff --git a/src/Open5ETools.Core/Common/Interfaces/Services/DM/IDungeonService.cs b/src/Open5ETools.Core/Common/Interfaces/Services/DM/IDungeonService.cs index ec65b23..b44fc52 100644 --- a/src/Open5ETools.Core/Common/Interfaces/Services/DM/IDungeonService.cs +++ b/src/Open5ETools.Core/Common/Interfaces/Services/DM/IDungeonService.cs @@ -4,16 +4,16 @@ namespace Open5ETools.Core.Common.Interfaces.Services.DM; 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 GetDungeonAsync(int id, 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); diff --git a/src/Open5ETools.Core/Common/Interfaces/Services/DM/IOptionService.cs b/src/Open5ETools.Core/Common/Interfaces/Services/DM/IOptionService.cs index 344a13f..392400e 100644 --- a/src/Open5ETools.Core/Common/Interfaces/Services/DM/IOptionService.cs +++ b/src/Open5ETools.Core/Common/Interfaces/Services/DM/IOptionService.cs @@ -5,5 +5,5 @@ namespace Open5ETools.Core.Common.Interfaces.Services.DM; 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/Open5ETools.Core/Common/Interfaces/Services/IAuthService.cs b/src/Open5ETools.Core/Common/Interfaces/Services/IAuthService.cs index 442ac11..e6b9851 100644 --- a/src/Open5ETools.Core/Common/Interfaces/Services/IAuthService.cs +++ b/src/Open5ETools.Core/Common/Interfaces/Services/IAuthService.cs @@ -4,5 +4,5 @@ namespace Open5ETools.Core.Common.Interfaces.Services; public interface IAuthService { - Task LoginAsync(UserModel model, CancellationToken cancellationToken); + Task LoginAsync(UserModel model, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Open5ETools.Core/Common/Interfaces/Services/IUserService.cs b/src/Open5ETools.Core/Common/Interfaces/Services/IUserService.cs index c470336..61b26e1 100644 --- a/src/Open5ETools.Core/Common/Interfaces/Services/IUserService.cs +++ b/src/Open5ETools.Core/Common/Interfaces/Services/IUserService.cs @@ -4,11 +4,11 @@ namespace Open5ETools.Core.Common.Interfaces.Services; public interface IUserService { - Task GetAsync(int id, CancellationToken cancellationToken); - Task UpdateAsync(UserModel model, CancellationToken cancellationToken); - Task CreateAsync(UserModel model, CancellationToken cancellationToken); - Task> ListAsync(bool? deleted = false, CancellationToken cancellationToken = default); - Task DeleteAsync(int id, CancellationToken cancellationToken); - Task RestoreAsync(int id, CancellationToken cancellationToken); - Task ChangePasswordAsync(ChangePasswordModel model, CancellationToken cancellationToken); + 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/Open5ETools.Core/ConfigureServices.cs b/src/Open5ETools.Core/ConfigureServices.cs index 16bfe69..cc48fee 100644 --- a/src/Open5ETools.Core/ConfigureServices.cs +++ b/src/Open5ETools.Core/ConfigureServices.cs @@ -19,49 +19,49 @@ namespace Open5ETools.Core; public static class ConfigureServices { - public static IServiceCollection AddApplicationServices( - this IServiceCollection services - ) + extension(IServiceCollection services) { - services - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped(); - - services.ConfigureMapster(); - return services; - } + public IServiceCollection AddApplicationServices() + { + services + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); + services.ConfigureMapster(); + return services; + } - private static IServiceCollection ConfigureMapster(this IServiceCollection services) - { - services.AddMapster(); - TypeAdapterConfig - .NewConfig() - .Map(dest => dest.Concentration, - src => string.IsNullOrWhiteSpace(src.Concentration) || - !src.Concentration.Equals("no", StringComparison.InvariantCultureIgnoreCase)) - .Map(dest => dest.Ritual, - src => string.IsNullOrWhiteSpace(src.Ritual) || - !src.Ritual.Equals("no", StringComparison.InvariantCultureIgnoreCase)) - .Map(dest => dest.School, - src => Enum.Parse(src.School ?? string.Empty)); + private IServiceCollection ConfigureMapster() + { + services.AddMapster(); + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.Concentration, + src => string.IsNullOrWhiteSpace(src.Concentration) || + !src.Concentration.Equals("no", StringComparison.InvariantCultureIgnoreCase)) + .Map(dest => dest.Ritual, + src => string.IsNullOrWhiteSpace(src.Ritual) || + !src.Ritual.Equals("no", StringComparison.InvariantCultureIgnoreCase)) + .Map(dest => dest.School, + src => Enum.Parse(src.School ?? string.Empty)); - TypeAdapterConfig - .NewConfig() - .Map(dest => dest.Hp, src => src.HitPoints) - .Map(dest => dest.Ac, src => src.ArmorClass); + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.Hp, src => src.HitPoints) + .Map(dest => dest.Ac, src => src.ArmorClass); - TypeAdapterConfig - .NewConfig() - .Map(dest => dest.JsonMonsterModel, src => src.JsonMonster); + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.JsonMonsterModel, src => src.JsonMonster); - return services; + return services; + } } } \ No newline at end of file diff --git a/src/Open5ETools.Core/Services/AuthService.cs b/src/Open5ETools.Core/Services/AuthService.cs index 53504b6..3060b31 100644 --- a/src/Open5ETools.Core/Services/AuthService.cs +++ b/src/Open5ETools.Core/Services/AuthService.cs @@ -14,7 +14,7 @@ public class AuthService(IMapper mapper, IAppDbContext context, ILogger LoginAsync(UserModel model, CancellationToken cancellationToken) + public async Task LoginAsync(UserModel model, CancellationToken cancellationToken = default) { try { diff --git a/src/Open5ETools.Core/Services/DM/DungeonService.cs b/src/Open5ETools.Core/Services/DM/DungeonService.cs index b5b85fe..3f1782a 100644 --- a/src/Open5ETools.Core/Services/DM/DungeonService.cs +++ b/src/Open5ETools.Core/Services/DM/DungeonService.cs @@ -173,7 +173,7 @@ public async Task GenerateDungeonAsync(DungeonOptionModel model) } } - public async Task> GetAllDungeonOptionsAsync(CancellationToken cancellationToken) + public async Task GetAllDungeonOptionsAsync(CancellationToken cancellationToken) { try { @@ -181,9 +181,9 @@ public async Task> GetAllDungeonOptionsAsync(Can .Include(d => d.Dungeons) .AsNoTracking() .OrderBy(d => d.Created) - .ToListAsync(cancellationToken); + .ToArrayAsync(cancellationToken); - return options.Select(_mapper.Map); + return [.. options.Select(_mapper.Map)]; } catch (Exception ex) { @@ -192,7 +192,7 @@ public async Task> GetAllDungeonOptionsAsync(Can } } - public async Task> GetAllDungeonOptionsForUserAsync(int userId, + public async Task GetAllDungeonOptionsForUserAsync(int userId, CancellationToken cancellationToken) { try @@ -202,9 +202,9 @@ public async Task> GetAllDungeonOptionsForUserAs .Include(d => d.Dungeons) .Where(d => d.UserId == userId) .OrderBy(d => d.Created) - .ToListAsync(cancellationToken); + .ToArrayAsync(cancellationToken); - return options.Select(_mapper.Map); + return [.. options.Select(_mapper.Map)]; } catch (Exception ex) { @@ -296,7 +296,7 @@ private async Task DeleteDungeonOptionIfNeededAsync(int dungeonOptionId, Cancell } } - public async Task> ListUserDungeonsAsync(int userId, CancellationToken cancellationToken) + public async Task ListUserDungeonsAsync(int userId, CancellationToken cancellationToken) { try { @@ -306,8 +306,8 @@ public async Task> ListUserDungeonsAsync(int userId, C .Where(d => d.UserId == userId) .OrderBy(d => d.Created) .SelectMany(d => d.Dungeons) - .ToListAsync(cancellationToken); - return result.Select(_mapper.Map); + .ToArrayAsync(cancellationToken); + return [.. result.Select(_mapper.Map)]; } catch (Exception ex) { @@ -316,7 +316,7 @@ 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 @@ -327,8 +327,8 @@ public async Task> ListUserDungeonsByNameAsync(string .Where(d => d.DungeonName.Equals(dungeonName) && d.UserId == userId) .OrderBy(d => d.Created) .SelectMany(d => d.Dungeons) - .ToListAsync(cancellationToken); - return result.Select(_mapper.Map); + .ToArrayAsync(cancellationToken); + return [.. result.Select(_mapper.Map)]; } catch (Exception ex) { diff --git a/src/Open5ETools.Core/Services/DM/Generator/Dungeon.cs b/src/Open5ETools.Core/Services/DM/Generator/Dungeon.cs index ea8dd09..6db3d37 100644 --- a/src/Open5ETools.Core/Services/DM/Generator/Dungeon.cs +++ b/src/Open5ETools.Core/Services/DM/Generator/Dungeon.cs @@ -13,9 +13,9 @@ public class Dungeon(IDungeonHelper dungeonHelper) : IDungeon private const int Movement = 10; internal readonly List Rooms = []; internal List Doors = []; - public ICollection RoomDescription { get; set; } = []; - private ICollection TrapDescription { get; set; } = []; - private ICollection RoamingMonsterDescription { get; set; } = []; + public List RoomDescription { get; set; } = []; + private List TrapDescription { get; set; } = []; + private List RoamingMonsterDescription { get; set; } = []; public DungeonTile[][] DungeonTiles { get; set; } = []; private List _result = []; private List _corridors = []; diff --git a/src/Open5ETools.Core/Services/DM/Generator/DungeonNoCorridor.cs b/src/Open5ETools.Core/Services/DM/Generator/DungeonNoCorridor.cs index b3762fa..a3f2fff 100644 --- a/src/Open5ETools.Core/Services/DM/Generator/DungeonNoCorridor.cs +++ b/src/Open5ETools.Core/Services/DM/Generator/DungeonNoCorridor.cs @@ -11,7 +11,7 @@ namespace Open5ETools.Core.Services.DM.Generator; public class DungeonNoCorridor(IDungeonHelper dungeonHelper) : Dungeon(dungeonHelper), IDungeonNoCorridor { private readonly IDungeonHelper _dungeonHelper = dungeonHelper; - public IList OpenDoorList { get; set; } = []; + public List OpenDoorList { get; set; } = []; private List _edgeTileList = []; private List _roomStart = []; @@ -57,13 +57,14 @@ public override void AddEntryPoint() x = DungeonHelper.GetRandomInt(1, DungeonTiles.Length - 1); y = DungeonHelper.GetRandomInt(1, DungeonTiles.Length - 1); RoomPosition.CheckRoomPosition(DungeonTiles, x, y); - entryIsOk = DungeonTiles[x][y].Texture == Texture.RoomEdge && CheckPos() && CheckNearbyDoor(DungeonTiles[x][y]) && CheckEdges(DungeonTiles, x, y); - } - while (!entryIsOk); + entryIsOk = DungeonTiles[x][y].Texture == Texture.RoomEdge && CheckPos() && + CheckNearbyDoor(DungeonTiles[x][y]) && CheckEdges(DungeonTiles, x, y); + } while (!entryIsOk); + DungeonTiles[x][y].Texture = Texture.Entry; } - private static bool CheckEdges(IReadOnlyList dungeonTiles, int x, int y) + private static bool CheckEdges(DungeonTile[][] dungeonTiles, int x, int y) { return dungeonTiles[x][y - 1].Texture == Texture.Room || dungeonTiles[x][y + 1].Texture == Texture.Room || @@ -89,7 +90,9 @@ public void AddDescription() RemoveFromOpen(openList, start); // remove from open list this node AddToOpen(start, openList, closedList); // add open list the nearby nodes } - _dungeonHelper.AddNcRoomDescription(DungeonTiles[room.I][room.J], RoomDescription, _dungeonHelper.GetNcDoorDescription(DungeonTiles, closedList)); + + _dungeonHelper.AddNcRoomDescription(DungeonTiles[room.I][room.J], RoomDescription, + _dungeonHelper.GetNcDoorDescription(DungeonTiles, closedList)); } } @@ -103,7 +106,8 @@ private void AddToOpen(DungeonTile node, ICollection openList, ICol private void AddToOpenList(int x, int y, ICollection openList, ICollection closedList) { - if (CheckTileForOpenList(x, y) && !closedList.Contains(DungeonTiles[x][y]) && !openList.Contains(DungeonTiles[x][y])) // not in openlist/closedlist + if (CheckTileForOpenList(x, y) && !closedList.Contains(DungeonTiles[x][y]) && + !openList.Contains(DungeonTiles[x][y])) // not in openlist/closedlist openList.Add(DungeonTiles[x][y]); } @@ -138,6 +142,7 @@ public void FillRoomToDoor() else if (RoomPosition.Left) RandomFillLeftRight(OpenDoorList[i].I, OpenDoorList[i].J + 1, OpenDoorList[i]); } + OpenDoorList.Remove(OpenDoorList[i]); } } @@ -166,6 +171,7 @@ private void FillLeftRight(int x, int y, int down, int right) FillVertical(x, y + i, down); } } + SetVerticalEdge(x, y, right, down); SetVerticalEdge(x, y, right < 0 ? 1 : -1, down); FillDoor(down, right); @@ -199,6 +205,7 @@ private void FillVertical(int x, int y, int down) { SetRoomTiles(x + i, y); // set room } + SetTextureToRoomEdge(x + 1, y); // bottom edge AddEdgeTileList(x + 1, y); } @@ -208,9 +215,11 @@ private void FillVertical(int x, int y, int down) { SetRoomTiles(x + i, y); // set room } + SetTextureToRoomEdge(x - 1, y); // top edge AddEdgeTileList(x - 1, y); } + SetTextureToRoomEdge(x + down, y); AddEdgeTileList(x + down, y); } @@ -239,6 +248,7 @@ private void FillUpDown(int x, int y, int down, int right) FillHorizontal(x + i, y, right); } } + SetHorizontalEdge(x, y, right, down); SetHorizontalEdge(x, y, right, down < 0 ? 1 : -1); FillDoor(down, right); @@ -258,9 +268,9 @@ private void FillDoor(int down, int right) SetDoor(_edgeTileList[random].I, _edgeTileList[random].J); doorCount--; } + maxTryNumber--; - } - while (doorCount > 0 && maxTryNumber > 0); + } while (doorCount > 0 && maxTryNumber > 0); } private bool CheckNearbyDoor(DungeonTile node) @@ -273,6 +283,7 @@ private bool CheckNearbyDoor(DungeonTile node) return false; } } + return true; } @@ -330,6 +341,7 @@ private void FillHorizontal(int x, int y, int right) { SetRoomTiles(x, y + i); // set room } + SetTextureToRoomEdge(x, y + 1); // right edge AddEdgeTileList(x, y + 1); } @@ -339,9 +351,11 @@ private void FillHorizontal(int x, int y, int right) { SetRoomTiles(x, y + i); // set room } + SetTextureToRoomEdge(x, y - 1); // left edge AddEdgeTileList(x, y - 1); } + SetTextureToRoomEdge(x, y + right); AddEdgeTileList(x, y + right); } @@ -375,10 +389,12 @@ private bool CheckPossibleEnd(int vertical, int horizontal, DungeonTile door, in if (vertical < 0 != down < 0 || horizontal < 0 != right < 0 || Math.Abs(vertical) < Math.Abs(down) || Math.Abs(horizontal) < Math.Abs(right)) - { // it would overlap with another room + { + // it would overlap with another room DungeonTiles[door.I][door.J].Texture = Texture.RoomEdge; // change the door to a room_edge return false; } + return true; } @@ -409,7 +425,8 @@ private bool CheckPossible(int vertical, int horizontal, DungeonTile door) { if (vertical != 0 && horizontal != 0) return true; // its possible to add room - DungeonTiles[door.I][door.J].Texture = Texture.RoomEdge; // impossible to add room, change the door to a room_edge + DungeonTiles[door.I][door.J].Texture = + Texture.RoomEdge; // impossible to add room, change the door to a room_edge return false; } @@ -430,8 +447,8 @@ private int CheckLeft(int x, int y) edge = CheckDungeonTilesEdge(x, temp); temp--; count--; - } - while (!tile && !edge); + } while (!tile && !edge); + if (edge) count++; return count; @@ -449,8 +466,8 @@ private int CheckRight(int x, int y) edge = CheckDungeonTilesEdge(x, temp); temp++; count++; - } - while (!tile && !edge); + } while (!tile && !edge); + if (edge) count--; return count; @@ -482,8 +499,8 @@ private int CheckUp(int x, int y) edge = CheckDungeonTilesEdge(temp, y); temp--; count--; - } - while (!tile && !edge); + } while (!tile && !edge); + if (edge) count++; return count; @@ -501,8 +518,8 @@ private int CheckDown(int x, int y) edge = CheckDungeonTilesEdge(temp, y); temp++; count++; - } - while (!tile && !edge); + } while (!tile && !edge); + if (edge) count--; return count; @@ -552,6 +569,7 @@ protected override void FillRoom(int x, int y, int right, int down) DungeonTiles[x + i - 1][y + j - 1].Texture = Texture.RoomEdge; } } + for (var i = 0; i < down; i++) // fill room texture { for (var j = 0; j < right; j++) @@ -560,6 +578,7 @@ protected override void FillRoom(int x, int y, int right, int down) Rooms.Add(DungeonTiles[x + i][y + j]); } } + for (var d = 0; d < doorCount; d++) { AddDoor(x, y, down, right); @@ -588,6 +607,7 @@ protected override bool CheckDoor(int x, int y) return false; } } + return CheckEnvironment(x, y); } diff --git a/src/Open5ETools.Core/Services/DM/Generator/RoomPosition.cs b/src/Open5ETools.Core/Services/DM/Generator/RoomPosition.cs index c2bdeb5..adfcb8a 100644 --- a/src/Open5ETools.Core/Services/DM/Generator/RoomPosition.cs +++ b/src/Open5ETools.Core/Services/DM/Generator/RoomPosition.cs @@ -5,10 +5,11 @@ namespace Open5ETools.Core.Services.DM.Generator; public static class RoomPosition { - public static bool Up { get; set; } - public static bool Down { get; set; } - public static bool Left { get; set; } - public static bool Right { get; set; } + public static bool Up { get; private set; } + public static bool Down { get; private set; } + public static bool Left { get; private set; } + public static bool Right { get; private set; } + internal static void CheckRoomPosition(DungeonTile[][] dungeonTiles, int x, int y) { Up = false; diff --git a/src/Open5ETools.Core/Services/DM/OptionService.cs b/src/Open5ETools.Core/Services/DM/OptionService.cs index 52ae052..c0775ed 100644 --- a/src/Open5ETools.Core/Services/DM/OptionService.cs +++ b/src/Open5ETools.Core/Services/DM/OptionService.cs @@ -20,17 +20,17 @@ 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 _context.Options - .AsNoTracking() - .ToListAsync(cancellationToken); + var options = await _context.Options.AsNoTracking().ToArrayAsync(cancellationToken); cacheEntry = [.. options.Select(_mapper.Map)]; @@ -39,7 +39,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/Open5ETools.Core/Services/UserService.cs b/src/Open5ETools.Core/Services/UserService.cs index b60736a..4da9e3c 100644 --- a/src/Open5ETools.Core/Services/UserService.cs +++ b/src/Open5ETools.Core/Services/UserService.cs @@ -19,7 +19,7 @@ public class UserService(IMapper mapper, IAppDbContext context, ILogger CreateAsync(UserModel model, CancellationToken cancellationToken) + public async Task CreateAsync(UserModel model, CancellationToken cancellationToken = default) { ValidateModel(model); await CheckUserExistAsync(model, cancellationToken); @@ -45,20 +45,20 @@ private static void ValidateModel(UserModel model) if (model.Password.Length < 8) errors.Add(new ServiceException(Error.PasswordLength)); if (string.IsNullOrEmpty(model.Username)) - errors.Add(new ServiceException(string.Format(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(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(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(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(Error.RequiredValidation, model.Role))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, nameof(model.Role)))); if (errors.Count != 0) throw new ServiceAggregateException(errors); } - private async Task CheckUserExistAsync(UserModel model, CancellationToken cancellationToken) + private async Task CheckUserExistAsync(UserModel model, CancellationToken cancellationToken = default) { var user = await _context.Users .AsNoTracking() @@ -67,11 +67,11 @@ private async Task CheckUserExistAsync(UserModel model, CancellationToken cancel throw new ServiceException(string.Format(Error.UserExist, model.Username)); } - public async Task DeleteAsync(int id, CancellationToken cancellationToken) + public async Task DeleteAsync(int id, CancellationToken cancellationToken = default) { try { - var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == id, cancellationToken); + var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == id && !u.IsDeleted, cancellationToken); if (user is not null) { @@ -90,7 +90,7 @@ public async Task DeleteAsync(int id, CancellationToken cancellationToken) } } - public async Task GetAsync(int id, CancellationToken cancellationToken) + public async Task GetAsync(int id, CancellationToken cancellationToken = default) { try { @@ -108,8 +108,7 @@ public async Task GetAsync(int id, CancellationToken cancellationToke } } - public async Task> ListAsync(bool? deleted = false, - CancellationToken cancellationToken = default) + public async Task ListAsync(bool? deleted = false, CancellationToken cancellationToken = default) { try { @@ -132,18 +131,19 @@ public async Task> ListAsync(bool? deleted = false, } } - public async Task RestoreAsync(int id, CancellationToken cancellationToken) + public async Task RestoreAsync(int id, CancellationToken cancellationToken = default) { try { - var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == id, cancellationToken); + var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == id && u.IsDeleted, cancellationToken); if (user is not null) { user.IsDeleted = false; await _context.SaveChangesAsync(cancellationToken); + return true; } - return true; + return false; } catch (Exception ex) { @@ -152,7 +152,7 @@ public async Task RestoreAsync(int id, CancellationToken cancellationToken } } - public async Task UpdateAsync(UserModel model, CancellationToken cancellationToken) + public async Task UpdateAsync(UserModel model, CancellationToken cancellationToken = default) { ValidateModelForEdit(model); try @@ -163,7 +163,7 @@ public async Task UpdateAsync(UserModel model, CancellationToken cancellationTok user.FirstName = model.FirstName; user.LastName = model.LastName; user.Email = model.Email; - user.Role = (Role)Enum.Parse(typeof(Role), model.Role); + user.Role = Enum.Parse(model.Role); await _context.SaveChangesAsync(cancellationToken); } } @@ -179,18 +179,18 @@ private static void ValidateModelForEdit(UserModel model) var errors = new List(); ArgumentNullException.ThrowIfNull(model); if (string.IsNullOrEmpty(model.FirstName)) - errors.Add(new ServiceException(string.Format(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(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(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(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, CancellationToken cancellationToken) + public async Task ChangePasswordAsync(ChangePasswordModel model, CancellationToken cancellationToken = default) { try { diff --git a/src/Open5ETools.Infrastructure/Data/AppDbContextInitializer.cs b/src/Open5ETools.Infrastructure/Data/AppDbContextInitializer.cs index d451b7f..299bfbf 100644 --- a/src/Open5ETools.Infrastructure/Data/AppDbContextInitializer.cs +++ b/src/Open5ETools.Infrastructure/Data/AppDbContextInitializer.cs @@ -25,6 +25,10 @@ public class AppDbContextInitializer( private readonly IMapper _mapper = mapper; private readonly IAppDbContext _context = context; private readonly IDungeonService _dungeonService = dungeonService; + 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) { @@ -568,6 +572,7 @@ private async Task SeedUsersAsync(CancellationToken token) { _context.Users.Add(new User { + Id = TestAdminUserId, Username = "TestAdmin", Password = PasswordHelper.EncryptPassword(config.Value.DefaultAdminPassword), FirstName = "Test", @@ -578,6 +583,7 @@ private async Task SeedUsersAsync(CancellationToken token) _context.Users.Add(new User { + Id = TestUserId, Username = "TestUser", Password = PasswordHelper.EncryptPassword(config.Value.DefaultUserPassword), FirstName = "Test", @@ -585,6 +591,19 @@ private async Task SeedUsersAsync(CancellationToken token) Email = "user@user.com", Role = Role.User }); + + _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/Open5ETools.Web/Controllers/Web/ProfileController.cs b/src/Open5ETools.Web/Controllers/Web/ProfileController.cs index aee4621..71a7232 100644 --- a/src/Open5ETools.Web/Controllers/Web/ProfileController.cs +++ b/src/Open5ETools.Web/Controllers/Web/ProfileController.cs @@ -52,6 +52,7 @@ public async Task ChangePassword(ProfileChangePasswordModel model } } + ViewData["ReturnUrl"] = Url.Action(nameof(Index)); return View(model); } } \ No newline at end of file diff --git a/src/Open5ETools.Web/Extensions/ControllerExtensions.cs b/src/Open5ETools.Web/Extensions/ControllerExtensions.cs index d550ffd..e6e4d14 100644 --- a/src/Open5ETools.Web/Extensions/ControllerExtensions.cs +++ b/src/Open5ETools.Web/Extensions/ControllerExtensions.cs @@ -24,8 +24,12 @@ public static void HandleException(this Controller controller, Exception ex, ILo controller.ModelState.AddModelError(e.Field, e.Message); break; default: - logger.LogError(ex, "{GetDisplayUrl}: {DefaultError}", controller.Request.GetDisplayUrl(), - defaultError ?? ServiceException.GeneralError); + if (logger.IsEnabled(LogLevel.Error)) + { + logger.LogError(ex, "{GetDisplayUrl}: {DefaultError}", controller.Request.GetDisplayUrl(), + defaultError ?? ServiceException.GeneralError); + } + controller.ModelState.AddModelError(string.Empty, defaultError ?? ServiceException.GeneralError); break; } diff --git a/src/Open5ETools.Web/Open5ETools.Web.csproj b/src/Open5ETools.Web/Open5ETools.Web.csproj index 4406d08..a657c20 100644 --- a/src/Open5ETools.Web/Open5ETools.Web.csproj +++ b/src/Open5ETools.Web/Open5ETools.Web.csproj @@ -2,7 +2,7 @@ true true - 1.0.4.0 + 1.0.4.1 diff --git a/tests/Open5ETools.Core.Tests/DungeonHelperTests/Get.cs b/tests/Open5ETools.Core.Tests/DungeonHelperTests/Get.cs index 6f15db1..b58e3c7 100644 --- a/tests/Open5ETools.Core.Tests/DungeonHelperTests/Get.cs +++ b/tests/Open5ETools.Core.Tests/DungeonHelperTests/Get.cs @@ -54,7 +54,7 @@ public void GetNcDoorDescription_WithValidParameters_ReturnsNcDoorDescription() { _dungeonNoCorridor.AddFirstRoom(); var list = _dungeonNoCorridor.DungeonTiles.SelectMany(T => T); - var match = list.Where(x => x.Texture == Texture.Room); + var match = list.Where(x => x.Texture == Texture.Room).ToList(); var result = _dungeonHelper.GetNcDoorDescription(_dungeonNoCorridor.DungeonTiles, match); result.ShouldNotBeNull(); } diff --git a/tests/Open5ETools.Core.Tests/TestFixture.cs b/tests/Open5ETools.Core.Tests/TestFixture.cs index 50e0540..bbe0fb8 100644 --- a/tests/Open5ETools.Core.Tests/TestFixture.cs +++ b/tests/Open5ETools.Core.Tests/TestFixture.cs @@ -1,4 +1,7 @@ -using Open5ETools.Core.Common.Interfaces.Data; +using Microsoft.Extensions.Options; +using Open5ETools.Core.Common.Configurations; +using Open5ETools.Core.Common.Interfaces.Data; +using Open5ETools.Core.Common.Interfaces.Services; using Open5ETools.Core.Common.Interfaces.Services.DM; using Open5ETools.Core.Common.Interfaces.Services.DM.Generator; using Open5ETools.Core.Common.Interfaces.Services.EG; @@ -14,6 +17,8 @@ public class TestFixture : IDisposable public readonly IDungeonNoCorridor DungeonNoCorridor; public readonly IEncounterService EncounterService; public readonly ISpellService SpellService; + public readonly IUserService UserService; + public readonly IOptions Config; private readonly TestEnvironment _env = new(); private bool _disposedValue; @@ -23,7 +28,9 @@ public TestFixture() OptionService = _env.GetService(); EncounterService = _env.GetService(); SpellService = _env.GetService(); + UserService = _env.GetService(); Context = _env.GetService(); + Config = _env.GetService>(); DungeonNoCorridor = _env.GetNcDungeon(); } diff --git a/tests/Open5ETools.Core.Tests/UserServiceTests/ChangePassword.cs b/tests/Open5ETools.Core.Tests/UserServiceTests/ChangePassword.cs new file mode 100644 index 0000000..53ad7f7 --- /dev/null +++ b/tests/Open5ETools.Core.Tests/UserServiceTests/ChangePassword.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.Options; +using Open5ETools.Core.Common.Configurations; +using Open5ETools.Core.Common.Exceptions; +using Open5ETools.Core.Common.Interfaces.Services; +using Open5ETools.Core.Common.Models.Services; +using Open5ETools.Infrastructure.Data; +using Open5ETools.Resources; +using Shouldly; + +namespace Open5ETools.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/Open5ETools.Core.Tests/UserServiceTests/Create.cs b/tests/Open5ETools.Core.Tests/UserServiceTests/Create.cs new file mode 100644 index 0000000..89e9cbc --- /dev/null +++ b/tests/Open5ETools.Core.Tests/UserServiceTests/Create.cs @@ -0,0 +1,57 @@ +using Open5ETools.Core.Common.Enums; +using Open5ETools.Core.Common.Exceptions; +using Open5ETools.Core.Common.Interfaces.Services; +using Open5ETools.Core.Common.Models.Services; +using Open5ETools.Infrastructure.Data; +using Open5ETools.Resources; +using Shouldly; + +namespace Open5ETools.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/Open5ETools.Core.Tests/UserServiceTests/Delete.cs b/tests/Open5ETools.Core.Tests/UserServiceTests/Delete.cs new file mode 100644 index 0000000..32a55d8 --- /dev/null +++ b/tests/Open5ETools.Core.Tests/UserServiceTests/Delete.cs @@ -0,0 +1,26 @@ +using Open5ETools.Core.Common.Interfaces.Services; +using Open5ETools.Infrastructure.Data; +using Shouldly; + +namespace Open5ETools.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/Open5ETools.Core.Tests/UserServiceTests/Get.cs b/tests/Open5ETools.Core.Tests/UserServiceTests/Get.cs new file mode 100644 index 0000000..e59513c --- /dev/null +++ b/tests/Open5ETools.Core.Tests/UserServiceTests/Get.cs @@ -0,0 +1,21 @@ +using Open5ETools.Core.Common.Interfaces.Services; +using Open5ETools.Infrastructure.Data; +using Shouldly; + +namespace Open5ETools.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/Open5ETools.Core.Tests/UserServiceTests/List.cs b/tests/Open5ETools.Core.Tests/UserServiceTests/List.cs new file mode 100644 index 0000000..01bffd1 --- /dev/null +++ b/tests/Open5ETools.Core.Tests/UserServiceTests/List.cs @@ -0,0 +1,18 @@ +using Open5ETools.Core.Common.Interfaces.Services; +using Shouldly; + +namespace Open5ETools.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/Open5ETools.Core.Tests/UserServiceTests/Restore.cs b/tests/Open5ETools.Core.Tests/UserServiceTests/Restore.cs new file mode 100644 index 0000000..4a2aae4 --- /dev/null +++ b/tests/Open5ETools.Core.Tests/UserServiceTests/Restore.cs @@ -0,0 +1,26 @@ +using Open5ETools.Core.Common.Interfaces.Services; +using Open5ETools.Infrastructure.Data; +using Shouldly; + +namespace Open5ETools.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/Open5ETools.Core.Tests/UserServiceTests/Update.cs b/tests/Open5ETools.Core.Tests/UserServiceTests/Update.cs new file mode 100644 index 0000000..e9e8802 --- /dev/null +++ b/tests/Open5ETools.Core.Tests/UserServiceTests/Update.cs @@ -0,0 +1,51 @@ +using Open5ETools.Core.Common.Exceptions; +using Open5ETools.Core.Common.Interfaces.Services; +using Open5ETools.Core.Common.Models.Services; +using Open5ETools.Infrastructure.Data; +using Open5ETools.Resources; +using Shouldly; + +namespace Open5ETools.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