diff --git a/src/Application/Contracts/ContractService.cs b/src/Application/Contracts/ContractService.cs index 566c9e4b..29728f4a 100644 --- a/src/Application/Contracts/ContractService.cs +++ b/src/Application/Contracts/ContractService.cs @@ -32,4 +32,10 @@ public void Add(Contract contract) _repo.Add(contract); } + + /// + public bool Remove(Guid id) + { + return _repo.Remove(id); + } } diff --git a/src/Application/Contracts/IContractRepository.cs b/src/Application/Contracts/IContractRepository.cs index 6d32d25f..1de3cda6 100644 --- a/src/Application/Contracts/IContractRepository.cs +++ b/src/Application/Contracts/IContractRepository.cs @@ -18,4 +18,11 @@ public interface IContractRepository /// /// The new contract instance. void Add(Contract contract); + + /// + /// Removes the contract with the given ID. + /// + /// The id of the contract to be removed. + /// If the removal was successful. + public bool Remove(Guid id); } diff --git a/src/Application/Contracts/IContractService.cs b/src/Application/Contracts/IContractService.cs index 0b96f6ed..3af8e0ab 100644 --- a/src/Application/Contracts/IContractService.cs +++ b/src/Application/Contracts/IContractService.cs @@ -18,4 +18,11 @@ public interface IContractService /// /// The new contract. void Add(Contract contract); + + /// + /// Removes the specified contract. + /// + /// The id of the contract to be removed. + /// Whether the removal was successful. + bool Remove(Guid id); } diff --git a/src/Client/Pages/Admin/AdminPage.razor b/src/Client/Pages/Admin/AdminPage.razor index d6bcaf2c..cb0c9a49 100644 --- a/src/Client/Pages/Admin/AdminPage.razor +++ b/src/Client/Pages/Admin/AdminPage.razor @@ -15,5 +15,4 @@ { _contractList.Add(contract); } - } diff --git a/src/Client/Pages/Admin/ContractList.razor b/src/Client/Pages/Admin/ContractList.razor index 7c447928..60e14fd1 100644 --- a/src/Client/Pages/Admin/ContractList.razor +++ b/src/Client/Pages/Admin/ContractList.razor @@ -12,7 +12,7 @@ else
@foreach (var contract in _contracts) { - + }
} @@ -39,6 +39,16 @@ else InvokeAsync(StateHasChanged); } - + /// + /// Removes a contract from the list. + /// + /// The contract to remove. + private void OnContractRemoved(Contract contract) + { + // Using null forgiving operator because because _contracts + // can not be null when remove button is pressed. + _contracts!.Remove(contract); + InvokeAsync(StateHasChanged); + } } diff --git a/src/Client/Pages/Admin/ContractListItem.razor b/src/Client/Pages/Admin/ContractListItem.razor index f87d2f3d..6200f626 100644 --- a/src/Client/Pages/Admin/ContractListItem.razor +++ b/src/Client/Pages/Admin/ContractListItem.razor @@ -1,12 +1,35 @@ -
@Name
+@using Domain.Contracts +@inject HttpClient _http + +
+ @Contract.Name + + +
@code { + /// + /// Called when a contract has been removed successfully. + /// + [Parameter] + public EventCallback OnContractRemoved { get; set; } = EventCallback.Empty; + /// - /// Name of the company. + /// The contract. /// [Parameter, EditorRequired,] - public string? Name { get; set; } + public Contract Contract { get; set; } = new(); + private async Task RemoveContract() + { + HttpResponseMessage response = await _http.DeleteAsync($"api/v1/Contracts/{Contract.Id}"); + if (response.IsSuccessStatusCode) + { + await OnContractRemoved.InvokeAsync(Contract); + } + } } diff --git a/src/Domain/Contracts/Contract.cs b/src/Domain/Contracts/Contract.cs index 4a81f7e8..51a182f0 100644 --- a/src/Domain/Contracts/Contract.cs +++ b/src/Domain/Contracts/Contract.cs @@ -8,7 +8,7 @@ public class Contract /// /// Gets the unique identifier. /// - public Guid Id { get; } = Guid.NewGuid(); + public Guid Id { get; init; } = Guid.NewGuid(); /// /// Gets or sets the name of the contract supplier. diff --git a/src/Infrastructure/Contracts/FakeContractRepository.cs b/src/Infrastructure/Contracts/FakeContractRepository.cs index 6136a63e..5991830a 100644 --- a/src/Infrastructure/Contracts/FakeContractRepository.cs +++ b/src/Infrastructure/Contracts/FakeContractRepository.cs @@ -9,7 +9,7 @@ namespace Infrastructure.Contracts; /// public class FakeContractRepository : IContractRepository { - private readonly ICollection _contracts; + private readonly List _contracts; /// /// Creates a fake contract for SJ. @@ -27,4 +27,10 @@ public void Add(Contract contract) { _contracts.Add(contract); } + + /// + public bool Remove(Guid id) + { + return _contracts.RemoveAll(o => o.Id == id) > 0; + } } diff --git a/src/Server/Controllers/ContractsController.cs b/src/Server/Controllers/ContractsController.cs index a0c02c31..a9f62c4f 100644 --- a/src/Server/Controllers/ContractsController.cs +++ b/src/Server/Controllers/ContractsController.cs @@ -59,4 +59,17 @@ public IActionResult CreateContract(Contract contract) return Ok(); } + + /// + /// Removes the specified contract. + /// + /// Id of the contract to be removed. + /// If the contract was successfully removed. + [HttpDelete("{id:guid}")] + public IActionResult Remove(Guid id) + { + return _contracts.Remove(id) ? + Ok() : + NotFound(); + } } diff --git a/tests/Application.Tests/Contracts/ContractServiceTests.cs b/tests/Application.Tests/Contracts/ContractServiceTests.cs index 17600a7a..b1aa8125 100644 --- a/tests/Application.Tests/Contracts/ContractServiceTests.cs +++ b/tests/Application.Tests/Contracts/ContractServiceTests.cs @@ -59,4 +59,33 @@ public void AddingContract_DoesNotThrow_IfIDIsUnique() // Assert add.Should().NotThrow(); } + + [Fact] + public void RemovingContract_DoesReturnTrue_WhenAContractExists() + { + // Arrange + var contract = new Contract(); + _mockRepo.Setup(repository => repository.Remove(contract.Id)).Returns(true); + + // Act + bool actual = _cut.Remove(contract.Id); + + // // Assert + actual.Should().BeTrue(); + } + + [Fact] + public void RemovingContract_DoesReturnFalse_WhenNoContractsExists() + { + // Arrange + _mockRepo.Setup(repository => repository.All).Returns(new List()); + + var id = Guid.NewGuid(); + + // Act + bool actual = _cut.Remove(id); + + // Assert + actual.Should().BeFalse(); + } } diff --git a/tests/Infrastructure.Tests/Contracts/FakeContractRepositoryTests.cs b/tests/Infrastructure.Tests/Contracts/FakeContractRepositoryTests.cs new file mode 100644 index 00000000..6cb3005a --- /dev/null +++ b/tests/Infrastructure.Tests/Contracts/FakeContractRepositoryTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using Infrastructure.Contracts; + +namespace Infrastructure.Tests.Contracts; +public class FakeContractRepositoryTests +{ + private readonly FakeContractRepository _cut; + + public FakeContractRepositoryTests() + { + _cut = new FakeContractRepository(); + } + + [Fact] + public void RemoveContract_ReturnsFalse_WhenContractDoesNotExist() + { + // Arrange + var id = Guid.NewGuid(); + + // Act + bool actual = _cut.Remove(id); + + // Assert + actual.Should().BeFalse(); + } + + [Fact] + public void RemoveContract_ReturnTrue_WhenContractExists() + { + // Arrange + Guid id = _cut.All.First().Id; + + // Act + bool actual = _cut.Remove(id); + + // Assert + actual.Should().BeTrue(); + } +} diff --git a/tests/Presentation.Tests/Client/Pages/Admin/ContractListItemTests.cs b/tests/Presentation.Tests/Client/Pages/Admin/ContractListItemTests.cs index a9f994b2..518e7fcb 100644 --- a/tests/Presentation.Tests/Client/Pages/Admin/ContractListItemTests.cs +++ b/tests/Presentation.Tests/Client/Pages/Admin/ContractListItemTests.cs @@ -1,4 +1,5 @@ using Client.Pages.Admin; +using Domain.Contracts; namespace Presentation.Tests.Client.Pages.Admin; @@ -21,15 +22,16 @@ public void ContractListItem_ContainsTitle() { // Arrange const string name = "SJ"; + var contract = new Contract() { Name = name }; - static void ParameterBuilder(ComponentParameterCollectionBuilder parameters) => - parameters.Add(property => property.Name, name); + void ParameterBuilder(ComponentParameterCollectionBuilder parameters) => + parameters.Add(property => property.Contract, contract); // Act IRenderedComponent cut = _context.RenderComponent(ParameterBuilder); // Assert - cut.Find($"#{name}").TextContent.Should().Contain(name); + cut.Find($"#{contract.Name}").TextContent.Should().Contain(name); } } diff --git a/tests/Presentation.Tests/Client/Pages/Admin/ContractListTests.cs b/tests/Presentation.Tests/Client/Pages/Admin/ContractListTests.cs index ce991c33..b90df187 100644 --- a/tests/Presentation.Tests/Client/Pages/Admin/ContractListTests.cs +++ b/tests/Presentation.Tests/Client/Pages/Admin/ContractListTests.cs @@ -8,6 +8,7 @@ using Client.Pages.Admin; using Domain.Contracts; +using Microsoft.AspNetCore.Components.Web; namespace Presentation.Tests.Client.Pages.Admin; @@ -58,4 +59,27 @@ public void AddingContract_DoesNotThrow_BeforeContractsAreFetched() // Assert add.Should().NotThrow(); } + + [Fact] + public async Task RemovingContract_RendersWithoutTheContractAsync() + { + // Arrange + var firstContract = new Contract() { Name = "first", }; + Contract[] contracts = { firstContract, new Contract() { Name = "Second", }, }; + MockHttp.When("/api/v1/Contracts/All").RespondJson(contracts); + MockHttp.When(HttpMethod.Delete, $"/api/v1/Contracts/{firstContract.Id}").Respond(req => new HttpResponseMessage(HttpStatusCode.OK)); + + IRenderedComponent cut = Context.RenderComponent(); + const string removeButton = ".btn.btn-danger"; + cut.WaitForElement(removeButton); + + // Act + await cut.Find(removeButton).ClickAsync(new MouseEventArgs()).ConfigureAwait(false); + cut.WaitForState(() => cut.FindAll(removeButton).Count == 1); + + // Assert + Expression> + elementWithNewName = contract => contract.TextContent.Contains(firstContract.Name); + cut.FindAll(".list-group-item").Should().NotContain(elementWithNewName); + } } diff --git a/tests/Presentation.Tests/Server/Controllers/ContractsControllerTests.cs b/tests/Presentation.Tests/Server/Controllers/ContractsControllerTests.cs index 225bd16b..4798fbf7 100644 --- a/tests/Presentation.Tests/Server/Controllers/ContractsControllerTests.cs +++ b/tests/Presentation.Tests/Server/Controllers/ContractsControllerTests.cs @@ -1,6 +1,7 @@ using Application.Contracts; using Domain.Contracts; +using Microsoft.AspNetCore.Mvc; namespace Presentation.Tests.Server.Controllers; @@ -28,4 +29,46 @@ public void Get_AllContracts() // Assert actualWeather.Should().BeEquivalentTo(fakeContracts); } + + [Fact] + public void Remove_ReturnsOk_WhenIDExists() + { + // Arrange + var id = Guid.NewGuid(); + _mockContracts.Setup(service => service.Remove(id)).Returns(true); + + // Act + IActionResult actual = _cut.Remove(id); + + // Assert + actual.Should().BeOfType(); + } + + [Fact] + public void Remove_CallsContractService_WhenIDExists() + { + // Arrange + var id = Guid.NewGuid(); + _mockContracts.Setup(service => service.Remove(id)).Returns(true); + + // Act + IActionResult actual = _cut.Remove(id); + + // Assert + _mockContracts.Verify(o => o.Remove(id), Times.Once); + } + + [Fact] + public void Remove_ReturnsNotFound_WhenIDDoesNotExist() + { + // Arrange + var id = Guid.NewGuid(); + _mockContracts.Setup(service => service.Remove(id)).Returns(false); + + // Act + IActionResult actual = _cut.Remove(id); + + // Assert + actual.Should().BeOfType(); + } }