diff --git a/API/Controllers/TacticsController.cs b/API/Controllers/TacticsController.cs index 9452f11..fadb2d2 100644 --- a/API/Controllers/TacticsController.cs +++ b/API/Controllers/TacticsController.cs @@ -16,7 +16,8 @@ public class TacticController(ITacticService service, IContextAccessor accessor) public record UpdateNameRequest( [StringLength(50, MinimumLength = 1)] [Name] - string Name); + string Name + ); [HttpPut("/tactics/{tacticId:int}/name")] [Authorize] @@ -81,12 +82,11 @@ public class TacticController(ITacticService service, IContextAccessor accessor) { var userId = accessor.CurrentUserId(HttpContext); - var courtType = req.CourtType switch + if (!Enum.TryParse(req.CourtType, true, out var courtType)) { - "PLAIN" => CourtType.Plain, - "HALF" => CourtType.Half, - _ => throw new ArgumentOutOfRangeException() //unreachable - }; + // unreachable if called by ASP + throw new ArgumentOutOfRangeException("for req.CourtType"); + } var id = await service.AddTactic(userId, req.Name, courtType); return new CreateNewResponse(id); @@ -118,10 +118,10 @@ public class TacticController(ITacticService service, IContextAccessor accessor) var json = await service.GetTacticStepContent(tacticId, stepId); return json != null ? Ok(JsonSerializer.Deserialize(json)) : NotFound(); } - + [HttpDelete("/tactics/{tacticId:int}/steps/{stepId:int}")] [Authorize] - public async Task RemoveStepContent(int tacticId, int stepId) + public async Task RemoveStep(int tacticId, int stepId) { var userId = accessor.CurrentUserId(HttpContext); @@ -140,15 +140,14 @@ public class TacticController(ITacticService service, IContextAccessor accessor) [HttpPut("/tactics/{tacticId:int}/steps/{stepId:int}")] [Authorize] public async Task SaveStepContent(int tacticId, int stepId, [FromBody] SaveStepContentRequest req) - { + { var userId = accessor.CurrentUserId(HttpContext); - if (!await service.HasAnyRights(userId, tacticId)) { return Unauthorized(); } - await service.SetTacticStepContent(tacticId, stepId, JsonSerializer.Serialize(req.Content)); - return Ok(); + var found = await service.SetTacticStepContent(tacticId, stepId, JsonSerializer.Serialize(req.Content)); + return found ? Ok() : NotFound(); } } \ No newline at end of file diff --git a/AppContext/Entities/TacticStepEntity.cs b/AppContext/Entities/TacticStepEntity.cs index c28b88b..a440b7b 100644 --- a/AppContext/Entities/TacticStepEntity.cs +++ b/AppContext/Entities/TacticStepEntity.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using Model; namespace AppContext.Entities; @@ -15,5 +16,6 @@ public class TacticStepEntity public required int? ParentId { get; set; } public TacticStepEntity? Parent { get; set; } + [MaxLength(2_000_000)] public required string JsonContent { get; set; } } \ No newline at end of file diff --git a/DbServices/DbTacticService.cs b/DbServices/DbTacticService.cs index 1fcb1b3..f56cc2b 100644 --- a/DbServices/DbTacticService.cs +++ b/DbServices/DbTacticService.cs @@ -1,4 +1,3 @@ -using System.Collections; using AppContext.Entities; using Converters; using Microsoft.EntityFrameworkCore; @@ -59,7 +58,8 @@ public class DbTacticService(AppContext.AppContext context) : ITacticService return false; entity.Name = name; - return await context.SaveChangesAsync() > 0; + await context.SaveChangesAsync(); + return true; } public async Task SetTacticStepContent(int tacticId, int stepId, string json) @@ -70,7 +70,8 @@ public class DbTacticService(AppContext.AppContext context) : ITacticService return false; entity.JsonContent = json; - return await context.SaveChangesAsync() > 0; + await context.SaveChangesAsync(); + return true; } public async Task GetTacticStepContent(int tacticId, int stepId) @@ -108,6 +109,11 @@ public class DbTacticService(AppContext.AppContext context) : ITacticService public async Task AddTacticStep(int tacticId, int parentStepId, string initialJson) { + + var parentExists = context.TacticSteps.Any(t => t.TacticId == tacticId && t.Id == parentStepId); + if (!parentExists) + return null; + var tactic = await context.Tactics.FirstOrDefaultAsync(t => t.Id == tacticId); if (tactic == null) { diff --git a/StubContext/StubAppContext.cs b/StubContext/StubAppContext.cs index f740de9..989b46c 100644 --- a/StubContext/StubAppContext.cs +++ b/StubContext/StubAppContext.cs @@ -39,7 +39,7 @@ public class StubAppContext(DbContextOptions options) : AppContext(o .HasKey("TeamId", "UserId"); builder.Entity() - .HasData(new TacticEntity() + .HasData(new TacticEntity { Id = 1, Name = "New tactic", @@ -52,7 +52,7 @@ public class StubAppContext(DbContextOptions options) : AppContext(o .HasData(new TacticStepEntity { Id = 1, - JsonContent = "{}", + JsonContent = "{\"components\": []}", TacticId = 1, ParentId = null }); diff --git a/UnitTests/TacticsControllerTest.cs b/UnitTests/TacticsControllerTest.cs index 1886b56..894fa60 100644 --- a/UnitTests/TacticsControllerTest.cs +++ b/UnitTests/TacticsControllerTest.cs @@ -1,8 +1,8 @@ using API.Controllers; using API.DTO; -using AppContext.Entities; using DbServices; using FluentAssertions; +using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Model; @@ -39,7 +39,7 @@ public class TacticsControllerTest var tactic = await context.Tactics.FindAsync(1); tactic.Name.Should().BeEquivalentTo("Stade de France"); - + result = await controller.UpdateName(-1, new("Stade de France")); result.Should().BeEquivalentTo(controller.Unauthorized()); } @@ -49,18 +49,92 @@ public class TacticsControllerTest { var (controller, context) = GetController(1); var result = await controller.GetTacticInfo(1); - result.Should().BeEquivalentTo(controller.Ok(new Tactic(1, "New tactic", 1, CourtType.Plain, new DateTime(2024, 5, 31)).ToDto())); + result.Should() + .BeEquivalentTo(controller.Ok(new Tactic(1, "New tactic", 1, CourtType.Plain, new DateTime(2024, 5, 31)) + .ToDto())); result = await controller.GetTacticInfo(100); result.Should().BeEquivalentTo(controller.Unauthorized()); } [Fact] - public async void GetTacticStepsRoot() + public async void GetTacticStepsRootTest() { var (controller, context) = GetController(1); var result = await controller.GetTacticStepsRoot(1); - result.Should().BeEquivalentTo(controller.Ok(new TacticController.GetTacticStepsTreeResponse(new TacticStep(1, null, [], "{}").ToDto()))); + result.Should() + .BeEquivalentTo(controller.Ok( + new TacticController.GetTacticStepsTreeResponse(new TacticStep(1, null, [], "{}").ToDto()))); + } + + [Fact] + public async void CreateNewTest() + { + var (controller, context) = GetController(1); + var result = await controller.CreateNew(new("Test Tactic", "pLaIn")); + result.Should().BeEquivalentTo(new TacticController.CreateNewResponse(2)); + var tactic = await context.Tactics.FirstOrDefaultAsync(e => e.Id == 2); + tactic.Should().NotBeNull(); + tactic!.Name.Should().BeEquivalentTo("Test Tactic"); + tactic.OwnerId.Should().Be(1); + tactic.Type.Should().Be(CourtType.Plain); + } + + + [Fact] + public async void AddStepTest() + { + var (controller, context) = GetController(1); + var result = await controller.AddStep(1, new(1, "{components: []}")); + result.Should().BeEquivalentTo(controller.Ok(new TacticController.AddStepResponse(2))); + var tactic = await context.TacticSteps.FirstOrDefaultAsync(e => e.Id == 2); + tactic.Should().NotBeNull(); + tactic!.Id.Should().Be(2); + tactic.ParentId.Should().Be(1); + tactic.TacticId.Should().Be(1); + + // if tactic does not exists + result = await controller.AddStep(100, new(1, "hello")); + result.Should().BeEquivalentTo(controller.NotFound()); + + // if step does not exists + result = await controller.AddStep(1, new(10, "hello")); + result.Should().BeEquivalentTo(controller.NotFound()); + } + + [Fact] + public async void RemoveStepTest() + { + var (controller, context) = GetController(1); + var result = await controller.RemoveStep(1, 1); + result.Should().BeEquivalentTo(controller.Ok()); + var tactic = await context.TacticSteps.FirstOrDefaultAsync(e => e.Id == 1); + tactic.Should().BeNull(); + + // if tactic does not exists + result = await controller.RemoveStep(100, 1); + result.Should().BeEquivalentTo(controller.Unauthorized()); + + // if step does not exists + result = await controller.RemoveStep(1, 10); + result.Should().BeEquivalentTo(controller.NotFound()); + } + + [Fact] + public async void GetStepContentTest() + { + var (controller, context) = GetController(1); + (await controller.GetStepContent(1, 1)).Should().BeAssignableTo(controller.Ok("").GetType()); + (await controller.GetStepContent(10, 1)).Should().BeEquivalentTo(controller.Unauthorized()); + (await controller.GetStepContent(1, 10)).Should().BeEquivalentTo(controller.NotFound()); + } + + [Fact] + public async void SaveStepContentTest() + { + var (controller, context) = GetController(1); + (await controller.SaveStepContent(1, 1, new(new object()))).Should().BeEquivalentTo(controller.Ok()); + (await controller.SaveStepContent(10, 1, new (new object()))).Should().BeEquivalentTo(controller.Unauthorized()); + (await controller.SaveStepContent(1, 10, new (new object()))).Should().BeEquivalentTo(controller.NotFound()); } - } \ No newline at end of file diff --git a/UnitTests/UserControllerTest.cs b/UnitTests/UserControllerTest.cs index cb98c44..cac0a09 100644 --- a/UnitTests/UserControllerTest.cs +++ b/UnitTests/UserControllerTest.cs @@ -1,4 +1,5 @@ using API.Controllers; +using API.DTO; using DbServices; using FluentAssertions; using Microsoft.Data.Sqlite; @@ -26,7 +27,7 @@ public class UsersControllerTest new DbTacticService(context), new ManualContextAccessor(userId) ); - + return controller; } @@ -44,6 +45,9 @@ public class UsersControllerTest { var controller = GetUserController(1); var result = await controller.GetUserData(); - result.Should().BeEquivalentTo(new UsersController.GetUserDataResponse([], [])); + result.Should().BeEquivalentTo(new UsersController.GetUserDataResponse( + [], + [new TacticDto(1, "New tactic", 1, "PLAIN", 1717106400000L)] + )); } } \ No newline at end of file