From 6bc0bfc8f715ac4c261e501bd9dbeb5944390a48 Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Fri, 2 Aug 2024 16:37:15 +0100 Subject: [PATCH 1/4] Fixed update project group resolved warnings fixed tests Changed docker command reverted changes Changed to v2 docker compose command updated docker compose command update docker compose yaml Removed secret Changed version Revert all docker changes fixed docker command set secret use docker command --- .../continuous-integration-image-build-test.yml | 2 +- .../Repositories/ConversionProjectRepository.cs | 7 +++++++ .../ProjectAggregate/IConversionProjectRepository.cs | 1 + .../SetProjectGroupCommandHandlerTests.cs | 12 ++++++------ .../ProjectGroup/SetProjectGroupCommandHandler.cs | 2 +- .../ProjectAggregate/LegacyProjectUpdateTests.cs | 4 ++-- 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/continuous-integration-image-build-test.yml b/.github/workflows/continuous-integration-image-build-test.yml index 24de2cd6b..2bea6db96 100644 --- a/.github/workflows/continuous-integration-image-build-test.yml +++ b/.github/workflows/continuous-integration-image-build-test.yml @@ -19,4 +19,4 @@ jobs: run: | cp .env.development.local.example .env.development.local cp .env.database.example .env.database - docker-compose -f docker-compose.yml -p app build --secrets github_token: ${{ secrets.GITHUB_TOKEN }} + docker compose -f docker-compose.yml -p app build --secret key=${{ secrets.GITHUB_TOKEN }} diff --git a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs index 807406d4a..5a344fd19 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs @@ -333,5 +333,12 @@ public async Task> GetProjectsByProjectGroupIdsAsync(IEnum .Where(p => projectGroupIds.Contains(p.ProjectGroupId.GetValueOrDefault())) .ToListAsync(cancellationToken); } + + public async Task> GetProjectsByIdsOrProjectGroupIAsync(IEnumerable projectIds, int? projectGroupId, CancellationToken cancellationToken) + { + return await dbSet + .Where(p => p.ProjectGroupId == projectGroupId || projectIds.Contains(p.ProjectGroupId.GetValueOrDefault())) + .ToListAsync(cancellationToken); + } } } diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs index 0157fc1af..8842e9a42 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs @@ -27,4 +27,5 @@ public interface IConversionProjectRepository : IRepository, IGenericRe Task> GetConversionProjectsForNewGroup(string trustReferenceNumber, CancellationToken cancellationToken); Task> GetProjectsByProjectGroupIdsAsync(IEnumerable projectGroupIds, CancellationToken cancellationToken); Task> GetConversionProjectsByProjectGroupIdAsync(int? projectGroupId, CancellationToken cancellationToken = default); + Task> GetProjectsByIdsOrProjectGroupIAsync(IEnumerable projectIds, int? projectGroupId, CancellationToken cancellationToken); } diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs index c7b57b9cd..9dd7f0f42 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs @@ -75,7 +75,7 @@ public async Task Handle_ValidRequestWithoutConversions_ReturnsSuccess() }; _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken)).ReturnsAsync(expectedProjectGroup); - _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync([]); + _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync([]); // Act var result = await _setProjectGroupCommandHandler.Handle( @@ -84,7 +84,7 @@ public async Task Handle_ValidRequestWithoutConversions_ReturnsSuccess() // Assert var commandSuccessResult = Assert.IsType(result); - _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken), Times.Once); _mockProjectGroupRepository.Verify(x => x.Update(It.IsAny()), Times.Never); _mockProjectGroupRepository.Verify(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken), Times.Once); _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Never()); @@ -105,7 +105,7 @@ public async Task Handle_ValidRequestWithNoRemovedConversions_ReturnsSuccess() }; _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken)).ReturnsAsync(expectedProjectGroup); - _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync(expectedProjects); _mockConversionProjectRepository.Setup(x => x.Update(It.IsAny())); // Act @@ -115,7 +115,7 @@ public async Task Handle_ValidRequestWithNoRemovedConversions_ReturnsSuccess() // Assert var commandSuccessResult = Assert.IsType(result); - _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken), Times.Once); _mockConversionProjectRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Once); } @@ -134,7 +134,7 @@ public async Task Handle_ValidRequestWithOneRemovedConversions_ReturnsSuccess() }; _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken)).ReturnsAsync(expectedProjectGroup); - _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync(expectedProjects); _mockConversionProjectRepository.Setup(x => x.Update(It.IsAny())); // Act @@ -144,7 +144,7 @@ public async Task Handle_ValidRequestWithOneRemovedConversions_ReturnsSuccess() // Assert var commandSuccessResult = Assert.IsType(result); - _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken), Times.Once); _mockConversionProjectRepository.Verify(x => x.Update(It.IsAny()), Times.Exactly(expectedProjects.Count)); _mockConversionProjectRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Once); } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs index 6b34782df..193e16bff 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs @@ -20,7 +20,7 @@ public async Task Handle(SetProjectGroupCommand message, Cancella return new NotFoundCommandResult(); } - var conversionProjects = await conversionProjectRepository.GetConversionProjectsByProjectGroupIdAsync(projectGroup.Id, cancellationToken).ConfigureAwait(false); + var conversionProjects = await conversionProjectRepository.GetProjectsByIdsOrProjectGroupIAsync(message.ConversionProjectIds, projectGroup.Id, cancellationToken).ConfigureAwait(false); if (conversionProjects != null && conversionProjects.Any()) { diff --git a/Dfe.Academies.Academisation.SubcutaneousTest/ProjectAggregate/LegacyProjectUpdateTests.cs b/Dfe.Academies.Academisation.SubcutaneousTest/ProjectAggregate/LegacyProjectUpdateTests.cs index 1e608f884..2ea7bbfb2 100644 --- a/Dfe.Academies.Academisation.SubcutaneousTest/ProjectAggregate/LegacyProjectUpdateTests.cs +++ b/Dfe.Academies.Academisation.SubcutaneousTest/ProjectAggregate/LegacyProjectUpdateTests.cs @@ -45,11 +45,11 @@ public ProjectUpdateTests() _legacyProjectGetQuery = new ConversionProjectQueryService(conversionProjectRepository, formAMatProjectRepository); var services = new ServiceCollection(); - services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(Assembly.GetAssembly(typeof(ConversionProjectUpdateCommandHandler)))); + services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(Assembly.GetAssembly(typeof(ConversionProjectUpdateCommandHandler))!)); services.AddScoped(x => conversionProjectRepository); services.AddScoped(x => projectUpdateDataCommand); - _mediatr = services.BuildServiceProvider().GetService(); + _mediatr = services.BuildServiceProvider().GetService()!; } From ef9c1cc4366f88baa31de62254c7aa43b6624de1 Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Tue, 6 Aug 2024 12:23:26 +0100 Subject: [PATCH 2/4] Set docker compose to v2 version --- .../workflows/continuous-integration-image-build-test.yml | 5 +++-- docker-compose.yml | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration-image-build-test.yml b/.github/workflows/continuous-integration-image-build-test.yml index 2bea6db96..77bd950a9 100644 --- a/.github/workflows/continuous-integration-image-build-test.yml +++ b/.github/workflows/continuous-integration-image-build-test.yml @@ -10,7 +10,8 @@ jobs: image-build-test: name: Image build test runs-on: ubuntu-latest - + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Check out code uses: actions/checkout@v4 @@ -19,4 +20,4 @@ jobs: run: | cp .env.development.local.example .env.development.local cp .env.database.example .env.database - docker compose -f docker-compose.yml -p app build --secret key=${{ secrets.GITHUB_TOKEN }} + docker compose -f docker-compose.yml -p app build diff --git a/docker-compose.yml b/docker-compose.yml index 8459ba68b..3f632c78a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.8" services: webapi: build: @@ -42,3 +41,7 @@ networks: volumes: sql-server-data: + +secrets: + github_token: + environment: GITHUB_TOKEN From d5869c3658eb181cf7a0401561e185e19c1ddfca Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Wed, 7 Aug 2024 10:35:51 +0100 Subject: [PATCH 3/4] t :# This is a combination of 4 commits. Added new get project group by ref endpoint --- .../ConversionProjectRepository.cs | 2 +- .../Query/IProjectGroupQueryService.cs | 2 +- .../ProjectGroup/ProjectGroupResponseModel.cs | 4 +- .../CreateProjectGroupCommandHandlerTests.cs | 10 +- .../Queries/ProjectGroupQueryServiceTests.cs | 45 ++++-- ...tProjectGroupAssignUserCommandValidator.cs | 21 +-- .../CreateProjectGroupCommandHandler.cs | 4 +- .../LegacyProjectServiceModelMapper.cs | 11 +- .../Queries/ProjectGroupQueryService.cs | 4 +- .../ApiIntegrationTestBase.cs | 144 ++++++++++++++++++ ...mies.Academisation.SubcutaneousTest.csproj | 1 + .../ProjectGroup/ProjectGroupTests.cs | 38 +++++ .../Utils/HttpResponseMessageExtensions.cs | 25 +++ .../Utils/Request.cs | 82 ++++++++++ .../Controller/ProjectGroupControllerTests.cs | 2 +- .../Controllers/ProjectGroupController.cs | 2 +- Dfe.Academies.Academisation.WebApi/Program.cs | 1 - 17 files changed, 354 insertions(+), 44 deletions(-) create mode 100644 Dfe.Academies.Academisation.SubcutaneousTest/ApiIntegrationTestBase.cs create mode 100644 Dfe.Academies.Academisation.SubcutaneousTest/ProjectGroup/ProjectGroupTests.cs create mode 100644 Dfe.Academies.Academisation.SubcutaneousTest/Utils/HttpResponseMessageExtensions.cs create mode 100644 Dfe.Academies.Academisation.SubcutaneousTest/Utils/Request.cs diff --git a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs index 5a344fd19..a038cd722 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs @@ -337,7 +337,7 @@ public async Task> GetProjectsByProjectGroupIdsAsync(IEnum public async Task> GetProjectsByIdsOrProjectGroupIAsync(IEnumerable projectIds, int? projectGroupId, CancellationToken cancellationToken) { return await dbSet - .Where(p => p.ProjectGroupId == projectGroupId || projectIds.Contains(p.ProjectGroupId.GetValueOrDefault())) + .Where(p => p.ProjectGroupId == projectGroupId || projectIds.Contains(p.Id)) .ToListAsync(cancellationToken); } } diff --git a/Dfe.Academies.Academisation.IService/Query/IProjectGroupQueryService.cs b/Dfe.Academies.Academisation.IService/Query/IProjectGroupQueryService.cs index 58c9ab4aa..f17c8d092 100644 --- a/Dfe.Academies.Academisation.IService/Query/IProjectGroupQueryService.cs +++ b/Dfe.Academies.Academisation.IService/Query/IProjectGroupQueryService.cs @@ -5,7 +5,7 @@ namespace Dfe.Academies.Academisation.IService.Query { public interface IProjectGroupQueryService { - Task GetProjectGroupById(int id, CancellationToken cancellationToken); + Task GetProjectGroupByIdAsync(int id, CancellationToken cancellationToken); Task?> GetProjectGroupsAsync(IEnumerable? states, string? title, IEnumerable? deliveryOfficers, IEnumerable? regions, IEnumerable? localAuthorities, IEnumerable? advisoryBoardDates, int page, int count, CancellationToken cancellationToken); diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs index c7c99530b..0d01f33ed 100644 --- a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs +++ b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs @@ -2,7 +2,7 @@ namespace Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup { - public class ProjectGroupResponseModel(int id, string referenceNumber, string trustReferenceNumber, string trustName, string trustUkprn, User assignedUser) + public class ProjectGroupResponseModel(int id, string referenceNumber, string trustReferenceNumber, string trustName, string trustUkprn, User assignedUser, List projects) { public int Id { get; init; } = id; public string TrustReferenceNumber { get; init; } = trustReferenceNumber; @@ -13,6 +13,6 @@ public class ProjectGroupResponseModel(int id, string referenceNumber, string tr public string? ReferenceNumber { get; init; } = referenceNumber; public User AssignedUser { get; init; } = assignedUser; - public List projects { get; init; } + public List Projects { get; init; } = projects; } } diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index 6ea72aeb1..5fc3476b9 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -69,8 +69,8 @@ public async Task Handle_ValidCommandWithoutConversions_PersistsExpectedProjectG var responseModel = Assert.IsType>(result).Payload; Assert.Equal(responseModel.TrustReferenceNumber, request.TrustReferenceNumber); Assert.Equal(responseModel.ReferenceNumber, expectedProjectGroupReference); - Assert.Equal(responseModel.projects.Count(), expectedProjects.Count); - foreach (var conversion in responseModel.projects.Select((Value, Index) => (Value, Index))) + Assert.Equal(responseModel.Projects.Count(), expectedProjects.Count); + foreach (var conversion in responseModel.Projects.Select((Value, Index) => (Value, Index))) { Assert.Equal(conversion.Value.Urn, expectedProjects[conversion.Index].Details.Urn); Assert.Equal(conversion.Value.SchoolName, expectedProjects[conversion.Index].Details.SchoolName); @@ -104,10 +104,10 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou // Assert var responseModel = Assert.IsType>(result).Payload; Assert.Equal(responseModel.TrustReferenceNumber, request.TrustReferenceNumber); - Assert.Equal(responseModel.projects.Count(), expectedProjects.Count); - Assert.NotEmpty(responseModel.ReferenceNumber); + Assert.Equal(responseModel.Projects.Count(), expectedProjects.Count); + Assert.NotEmpty(responseModel.ReferenceNumber!); Assert.StartsWith(responseModel.ReferenceNumber, "GRP_00000000"); - foreach (var conversion in responseModel.projects.Select((Value, Index) => (Value, Index))) + foreach (var conversion in responseModel.Projects.Select((Value, Index) => (Value, Index))) { Assert.Equal(conversion.Value.Urn, expectedProjects[conversion.Index].Details.Urn); Assert.Equal(conversion.Value.SchoolName, expectedProjects[conversion.Index].Details.SchoolName); diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Queries/ProjectGroupQueryServiceTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Queries/ProjectGroupQueryServiceTests.cs index e704036f3..dec759869 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Queries/ProjectGroupQueryServiceTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Queries/ProjectGroupQueryServiceTests.cs @@ -3,7 +3,6 @@ using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService; -using Microsoft.Extensions.Logging; using Moq; using Xunit; using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; @@ -14,7 +13,6 @@ public class ProjectGroupQueryServiceTests { private Mock _mockProjectGroupRepository; private Mock _mockConversionProjectRepository; - private Mock> _mockLogger; private ProjectGroupQueryService _projectGroupQueryService; private CancellationToken _cancellationToken; private readonly Fixture _fixture; @@ -23,12 +21,10 @@ public ProjectGroupQueryServiceTests() { _mockProjectGroupRepository = new Mock(); _mockConversionProjectRepository = new Mock(); - _mockLogger = new Mock>(); _fixture = new(); _projectGroupQueryService = new ProjectGroupQueryService( _mockProjectGroupRepository.Object, - _mockConversionProjectRepository.Object, - _mockLogger.Object + _mockConversionProjectRepository.Object ); _cancellationToken = CancellationToken.None; } @@ -42,7 +38,7 @@ public async Task GetProjectGroupsAsync_WithReferenceNumber_ShouldReturnResultAs var expectedProject = _fixture.Create(); expectedProject.SetProjectGroupId(expectedProjectGroup.Id); var emptyList = new List(); - _mockProjectGroupRepository.Setup(x => x.SearchProjectGroups(searchModel.ReferenceNumber, _cancellationToken)) + _mockProjectGroupRepository.Setup(x => x.SearchProjectGroups(searchModel.ReferenceNumber!, _cancellationToken)) .ReturnsAsync([expectedProjectGroup]); _mockConversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupIdsAsync(new List { expectedProjectGroup.Id }, _cancellationToken)).ReturnsAsync([expectedProject]); @@ -58,7 +54,7 @@ public async Task GetProjectGroupsAsync_WithReferenceNumber_ShouldReturnResultAs Assert.Equal(data.ReferenceNumber, expectedProjectGroup.ReferenceNumber); Assert.Equal(data.TrustReferenceNumber, expectedProjectGroup.TrustReference); } - _mockProjectGroupRepository.Verify(x => x.SearchProjectGroups(searchModel.ReferenceNumber, _cancellationToken), Times.Once()); + _mockProjectGroupRepository.Verify(x => x.SearchProjectGroups(searchModel.ReferenceNumber!, _cancellationToken), Times.Once()); _mockConversionProjectRepository.Verify(x => x.GetProjectsByProjectGroupIdsAsync(new List { expectedProjectGroup.Id }, _cancellationToken), Times.Once()); } @@ -71,7 +67,7 @@ public async Task GetProjectGroupsAsync_WithTrustReference_ShouldReturnResult() var expectedProjectGroup = _fixture.Create(); var expectedProject = _fixture.Create(); expectedProject.SetProjectGroupId(expectedProjectGroup.Id); - _mockProjectGroupRepository.Setup(x => x.SearchProjectGroups(searchModel.TrustReference, _cancellationToken)) + _mockProjectGroupRepository.Setup(x => x.SearchProjectGroups(searchModel.TrustReference!, _cancellationToken)) .ReturnsAsync([expectedProjectGroup]); _mockConversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupIdsAsync(new List { expectedProjectGroup.Id }, _cancellationToken)).ReturnsAsync([expectedProject]); var emptyList = new List(); @@ -87,7 +83,7 @@ public async Task GetProjectGroupsAsync_WithTrustReference_ShouldReturnResult() Assert.Equal(data.ReferenceNumber, expectedProjectGroup.ReferenceNumber); Assert.Equal(data.TrustReferenceNumber, expectedProjectGroup.TrustReference); } - _mockProjectGroupRepository.Verify(x => x.SearchProjectGroups(searchModel.TrustReference, _cancellationToken), Times.Once()); + _mockProjectGroupRepository.Verify(x => x.SearchProjectGroups(searchModel.TrustReference!, _cancellationToken), Times.Once()); _mockConversionProjectRepository.Verify(x => x.GetProjectsByProjectGroupIdsAsync(new List { expectedProjectGroup.Id }, _cancellationToken), Times.Once()); } @@ -100,7 +96,7 @@ public async Task GetProjectGroupsAsync_WithTitle_ShouldReturnResult() var expectedProjectGroup = _fixture.Create(); var expectedProject = _fixture.Create(); expectedProject.SetProjectGroupId(expectedProjectGroup.Id); - _mockProjectGroupRepository.Setup(x => x.SearchProjectGroups(searchModel.Title, _cancellationToken)) + _mockProjectGroupRepository.Setup(x => x.SearchProjectGroups(searchModel.Title!, _cancellationToken)) .ReturnsAsync([expectedProjectGroup]); _mockConversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupIdsAsync(new List { expectedProjectGroup.Id }, _cancellationToken)).ReturnsAsync([expectedProject]); var emptyList = new List(); @@ -116,9 +112,36 @@ public async Task GetProjectGroupsAsync_WithTitle_ShouldReturnResult() Assert.Equal(data.ReferenceNumber, expectedProjectGroup.ReferenceNumber); Assert.Equal(data.TrustReferenceNumber, expectedProjectGroup.TrustReference); } - _mockProjectGroupRepository.Verify(x => x.SearchProjectGroups(searchModel.Title, _cancellationToken), Times.Once()); + _mockProjectGroupRepository.Verify(x => x.SearchProjectGroups(searchModel.Title!, _cancellationToken), Times.Once()); _mockConversionProjectRepository.Verify(x => x.GetProjectsByProjectGroupIdsAsync(new List { expectedProjectGroup.Id }, _cancellationToken), Times.Once()); + } + + [Fact] + public async Task GetProjectGroupByIdAsync_WithId_ShouldReturnResult() + { + // Arrange + var expectedProjectGroup = _fixture.Create(); + var expectedProject = _fixture.Create(); + expectedProject.SetProjectGroupId(expectedProjectGroup.Id); + _mockProjectGroupRepository.Setup(x => x.GetById(expectedProjectGroup.Id)) + .ReturnsAsync(expectedProjectGroup); + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync([expectedProject]); + + // Action + var result = await _projectGroupQueryService.GetProjectGroupByIdAsync(expectedProjectGroup.Id, _cancellationToken); + //Assert + var responseModel = Assert.IsType(result); + Assert.Equal(responseModel.TrustName, expectedProjectGroup.TrustName); + Assert.Equal(responseModel.ReferenceNumber, expectedProjectGroup.ReferenceNumber); + Assert.Equal(responseModel.Id, expectedProjectGroup.Id); + foreach (var project in responseModel.Projects) + { + Assert.Equal(project.Id, expectedProject.Id); + Assert.Equal(project.NameOfTrust, expectedProject.Details.NameOfTrust); + } + _mockProjectGroupRepository.Verify(x => x.GetById(expectedProjectGroup.Id), Times.Once()); + _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken), Times.Once()); } } } diff --git a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupAssignUserCommandValidator.cs b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupAssignUserCommandValidator.cs index 88d2ff998..4687ed54e 100644 --- a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupAssignUserCommandValidator.cs +++ b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupAssignUserCommandValidator.cs @@ -7,22 +7,25 @@ public class SetProjectGroupAssignUserCommandValidator : AbstractValidator x.GroupReferenceNumber) .NotEmpty().WithMessage("Must specify a group reference number") - .NotNull().WithMessage("Trust Reference must not be null"); - + .NotNull().WithMessage("Group Reference number must not be null"); + /* RuleFor(x => x.FullName) - .NotEmpty().WithMessage("Full name must not be empty"); + .NotEmpty() + .When(x => x.UserId != null && !string.IsNullOrEmpty(x.EmailAddress)) + .WithMessage("Full name must not be empty"); RuleFor(x => x.UserId) - .NotEmpty().WithMessage("Full name must not be empty"); - - RuleFor(x => x.FullName) - .NotEmpty().WithMessage("Full name must not be empty"); + .NotEmpty() + .When(x => !string.IsNullOrEmpty(x.FullName) && !string.IsNullOrEmpty(x.EmailAddress)) + .WithMessage("Full name must not be empty"); RuleFor(x => x.EmailAddress) - .NotEmpty().WithMessage("Email address must not be empty") - .EmailAddress().WithMessage("Must be a valid email address."); + .NotEmpty() + .When(x => x.UserId != null && !string.IsNullOrEmpty(x.FullName)) + .EmailAddress().WithMessage("Must be a valid email address.");*/ } } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index 0bdc7ba75..d58829ceb 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -50,9 +50,7 @@ public async Task Handle(CreateProjectGroupCommand message, Cancel conversionsProjectModels = conversionProjects.Select(p => p.MapToServiceModel()).ToList(); } - var responseModel = new ProjectGroupResponseModel(projectGroup.Id, projectGroup.ReferenceNumber!, projectGroup.TrustReference, null, null, null) { - projects = conversionsProjectModels - }; + var responseModel = new ProjectGroupResponseModel(projectGroup.Id, projectGroup.ReferenceNumber!, projectGroup.TrustReference, null, null, null, conversionsProjectModels); return new CreateSuccessResult(responseModel); diff --git a/Dfe.Academies.Academisation.Service/Mappers/Legacy/ProjectAggregate/LegacyProjectServiceModelMapper.cs b/Dfe.Academies.Academisation.Service/Mappers/Legacy/ProjectAggregate/LegacyProjectServiceModelMapper.cs index f2f782812..3d650d032 100644 --- a/Dfe.Academies.Academisation.Service/Mappers/Legacy/ProjectAggregate/LegacyProjectServiceModelMapper.cs +++ b/Dfe.Academies.Academisation.Service/Mappers/Legacy/ProjectAggregate/LegacyProjectServiceModelMapper.cs @@ -164,13 +164,10 @@ internal static FormAMatProjectServiceModel MapToFormAMatServiceModel(this IForm } internal static ProjectGroupResponseModel MapToProjectGroupServiceModel(this IProjectGroup projectGroup, IEnumerable projects) - { - ProjectGroupResponseModel serviceModel = new(projectGroup.Id, projectGroup.ReferenceNumber, projectGroup.TrustReference, projectGroup.TrustName, projectGroup.TrustUkprn, new User(projectGroup.AssignedUser?.Id ?? Guid.Empty, projectGroup.AssignedUser?.FullName ?? string.Empty, projectGroup.AssignedUser?.EmailAddress ?? string.Empty)) - { - projects = projects.Where(x => x.ProjectGroupId == projectGroup.Id).Select(p => p.MapToServiceModel()).ToList() - }; - return serviceModel; - } + => new (projectGroup.Id, projectGroup.ReferenceNumber!, projectGroup.TrustReference, + projectGroup.TrustName, projectGroup.TrustUkprn, new User(projectGroup.AssignedUser?.Id ?? Guid.Empty, + projectGroup.AssignedUser?.FullName ?? string.Empty, projectGroup.AssignedUser?.EmailAddress ?? string.Empty), + projects.Where(x => x.ProjectGroupId == projectGroup.Id).Select(p => p.MapToServiceModel()).ToList()); private static IEnumerable ToProjectNoteServiceModels(this IEnumerable? notes) { diff --git a/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs b/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs index 8f496aac4..65be6d183 100644 --- a/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs +++ b/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs @@ -13,9 +13,9 @@ namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService { - public class ProjectGroupQueryService(IProjectGroupRepository projectGroupRepository, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IProjectGroupQueryService + public class ProjectGroupQueryService(IProjectGroupRepository projectGroupRepository, IConversionProjectRepository conversionProjectRepository) : IProjectGroupQueryService { - public async Task GetProjectGroupById(int id, CancellationToken cancellationToken) + public async Task GetProjectGroupByIdAsync(int id, CancellationToken cancellationToken) { var projectGroup = await projectGroupRepository.GetById(id); var relatedProjects = await conversionProjectRepository.GetConversionProjectsByProjectGroupIdAsync(projectGroup.Id, cancellationToken).ConfigureAwait(false); diff --git a/Dfe.Academies.Academisation.SubcutaneousTest/ApiIntegrationTestBase.cs b/Dfe.Academies.Academisation.SubcutaneousTest/ApiIntegrationTestBase.cs new file mode 100644 index 000000000..ba4294c1e --- /dev/null +++ b/Dfe.Academies.Academisation.SubcutaneousTest/ApiIntegrationTestBase.cs @@ -0,0 +1,144 @@ +using System.Net.Http.Headers; +using System.Reflection; +using AutoFixture; +using Dfe.Academies.Academisation.Data; +using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; +using Dfe.Academies.Academisation.WebApi; +using Dfe.Academies.Academisation.WebApi.Options; +using MediatR; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Dfe.Academies.Academisation.SubcutaneousTest +{ + public class ApiIntegrationTestBase + { + private readonly Fixture _fixture; + protected readonly string _apiKey; + private HttpClient _httpClient; + private IMediator _mediator; + private AcademisationContext _dbContext; + public ApiIntegrationTestBase() { + _fixture = new(); + _apiKey = Guid.NewGuid().ToString(); + _httpClient = Build(); + _mediator = ServiceProvider.GetRequiredService(); + _dbContext = ServiceProvider.GetRequiredService(); + } + + protected HttpClient CreateClient() { return _httpClient; } + + private WebApplicationFactory WebAppFactory { get; set; } = new(); + + protected Fixture Fixture => _fixture; + + + protected AcademisationContext GetDBContext() + { + _dbContext.Database.EnsureDeleted(); + _dbContext.Database.EnsureCreated(); + return _dbContext; + + } + protected static CancellationToken CancellationToken => CancellationToken.None; + + protected IServiceProvider ServiceProvider + { + get + { + return WebAppFactory.Services; + } + } + + protected ApiIntegrationTestBase GetHttpClient => this; + + private HttpClient Build() + { + WebAppFactory = new WebApplicationFactory() + .WithWebHostBuilder(builder => + { + builder.UseEnvironment("local"); + + builder.ConfigureLogging(x => + { + x.ClearProviders(); + x.SetMinimumLevel(LogLevel.Debug); + }); + + ConfigureAppConfiguration(builder, _apiKey); + + builder.ConfigureTestServices(services => + { + ConfigureInMemoryDatabase(services); + ConfigureServices(builder); + + services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(Assembly.GetAssembly(typeof(CreateProjectGroupCommandHandler))!)); + }); + }); + + WebAppFactory.Server.PreserveExecutionContext = true; + + return BuildHttpClient(); + } + + private HttpClient BuildHttpClient() + { + var httpClient = WebAppFactory.CreateClient(new WebApplicationFactoryClientOptions + { + AllowAutoRedirect = false + }); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test"); + httpClient.DefaultRequestHeaders.Add("x-api-key", _apiKey); + + return httpClient; + } + + private static void ConfigureServices(IWebHostBuilder builder) + { + builder.ConfigureServices((context, services) => + { + // Bind the configuration section to the AuthenticationConfig class + var configuration = context.Configuration; + services.Configure(configuration.GetSection("AuthenticationConfig")); + }); + } + + private static void ConfigureInMemoryDatabase(IServiceCollection services) + { + // Replace database context with our own Integration database + var descriptor = services.SingleOrDefault(d => + d.ServiceType == typeof(DbContextOptions)); + if (descriptor != null) + { + services.Remove(descriptor); + } + + var connection = new SqliteConnection("DataSource=:memory:"); + connection.Open(); + + services.AddDbContext(options => + { + options.UseSqlite(connection); + }); + } + private static void ConfigureAppConfiguration(IWebHostBuilder builder, string apiKey) + { + builder.ConfigureAppConfiguration((context, configBuilder) => + { + // Create an in-memory configuration with test values + var inMemorySettings = new List> + { + new ("AuthenticationConfig:ApiKeys:0", apiKey) + }; + + configBuilder.AddInMemoryCollection(inMemorySettings!); + }); + } + } +} diff --git a/Dfe.Academies.Academisation.SubcutaneousTest/Dfe.Academies.Academisation.SubcutaneousTest.csproj b/Dfe.Academies.Academisation.SubcutaneousTest/Dfe.Academies.Academisation.SubcutaneousTest.csproj index 7d4b9adac..20b9466df 100644 --- a/Dfe.Academies.Academisation.SubcutaneousTest/Dfe.Academies.Academisation.SubcutaneousTest.csproj +++ b/Dfe.Academies.Academisation.SubcutaneousTest/Dfe.Academies.Academisation.SubcutaneousTest.csproj @@ -11,6 +11,7 @@ + diff --git a/Dfe.Academies.Academisation.SubcutaneousTest/ProjectGroup/ProjectGroupTests.cs b/Dfe.Academies.Academisation.SubcutaneousTest/ProjectGroup/ProjectGroupTests.cs new file mode 100644 index 000000000..67cbe49a7 --- /dev/null +++ b/Dfe.Academies.Academisation.SubcutaneousTest/ProjectGroup/ProjectGroupTests.cs @@ -0,0 +1,38 @@ +using AutoFixture; +using Dfe.Academies.Academisation.Data; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; +using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; +using Dfe.Academies.Academisation.SubcutaneousTest.Utils; + +namespace Dfe.Academies.Academisation.SubcutaneousTest.ProjectGroup +{ + public class ProjectGroupTests : ApiIntegrationTestBase + { + private readonly HttpClient _client; + private readonly AcademisationContext _context; + + public ProjectGroupTests() + { + _client = CreateClient(); + _context = GetDBContext(); + } + + [Fact] + public async Task CreateProjectGroup_ShouldCreateSuccessfully() + { + // Arrange + var notExpectedId = 0; + var command = new CreateProjectGroupCommand(Fixture.Create()[..15], Fixture.Create()[..7], Fixture.Create()[..10], []); + + // Action + var httpResponseMessage = await _client.PostAsJsonAsync("project-group/create-project-group",command, CancellationToken); + + Assert.True(httpResponseMessage.IsSuccessStatusCode); + var response = await httpResponseMessage.ConvertResponseToTypeAsync(); + Assert.Null(response.TrustName); + Assert.NotEmpty(response.ReferenceNumber!); + Assert.NotEqual(response.Id, notExpectedId); + Assert.Equal(response.TrustReferenceNumber, command.TrustReferenceNumber); + } + } +} diff --git a/Dfe.Academies.Academisation.SubcutaneousTest/Utils/HttpResponseMessageExtensions.cs b/Dfe.Academies.Academisation.SubcutaneousTest/Utils/HttpResponseMessageExtensions.cs new file mode 100644 index 000000000..272a42a66 --- /dev/null +++ b/Dfe.Academies.Academisation.SubcutaneousTest/Utils/HttpResponseMessageExtensions.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; +using System.Text.Json; + +namespace Dfe.Academies.Academisation.SubcutaneousTest.Utils +{ + internal static class HttpResponseMessageExtensions + { + private static readonly JsonSerializerOptions Options = new() + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + internal static async Task ConvertResponseToTypeAsync(this HttpResponseMessage httpResponseMessage) + { + var content = await httpResponseMessage.Content.ReadAsStringAsync(); + if (string.IsNullOrEmpty(content)) + { + return default!; + } + return JsonSerializer.Deserialize(content, Options)!; + } + } +} diff --git a/Dfe.Academies.Academisation.SubcutaneousTest/Utils/Request.cs b/Dfe.Academies.Academisation.SubcutaneousTest/Utils/Request.cs new file mode 100644 index 000000000..9566ae10e --- /dev/null +++ b/Dfe.Academies.Academisation.SubcutaneousTest/Utils/Request.cs @@ -0,0 +1,82 @@ +using System.Globalization; +using System.Text; +using System.Text.Json; +using System.Web; + +namespace Dfe.Academies.Academisation.SubcutaneousTest.Utils +{ + internal static class Request + { + internal static StringContent ConvertRequestObjectToContent(object request) => new(JsonSerializer.Serialize(request), Encoding.Default, "application/json"); + + internal static string ComplexTypeToQueryString(object obj) + { + var encode = obj.GetType().GetProperties() + .Where(p => p.GetValue(obj) != null) + .Select(p => $"{ToCamelCase(p.Name)}={HttpUtility.UrlEncode(ToString(p.GetValue(obj)!))}"); + + return string.Join("&", encode); + } + + internal static string NestedComplexTypeToQueryString(this object obj, string prefix = "") + { + var properties = obj.GetType().GetProperties() + .Where(p => p.GetValue(obj) != null); + + return string.Join('&', + properties.Select(prop => + { + var name = HttpUtility.UrlEncode(ToCamelCase(prop.Name)); + if (prop.GetValue(obj) is System.Collections.IEnumerable enumerable) + { + return string.Join('&', + enumerable.Cast() + .Where(mem => mem != null) + .Select((mem, i) => + { + var (isBaseObject, formattedValue) = FormatValue(mem); + if (isBaseObject) + { + return prefix + name + "=" + HttpUtility.UrlEncode(formattedValue); + } + + return mem.NestedComplexTypeToQueryString($"{name}{HttpUtility.UrlEncode("[")}{i}{HttpUtility.UrlEncode("]")}."); + })); + } + + return prefix + name + '=' + HttpUtility.UrlEncode(ToString(prop.GetValue(obj)!)); + })); + } + private static string ToCamelCase(string s) => char.ToLowerInvariant(s[0]) + s[1..]; + + private static string ToString(object obj) + { + return obj switch + { + DateTimeOffset date => date.ToString("o"), + DateTime date => date.ToString("o", CultureInfo.InvariantCulture), + DateOnly date => date.ToString("o", CultureInfo.InvariantCulture), + TimeOnly time => time.ToString("o", CultureInfo.InvariantCulture), + TimeSpan time => time.ToString("o", CultureInfo.InvariantCulture), + _ => obj?.ToString() ?? "", + }; + } + + private static (bool isBaseObject, string formattedValue) FormatValue(object obj) + { + if (obj.GetType().IsPrimitive + || obj.GetType().IsEnum + || obj is string + || obj is DateTimeOffset + || obj is DateTime + || obj is DateOnly + || obj is TimeOnly + || obj is TimeSpan) + { + return (true, ToString(obj)); + } + + return (false, ""); + } + } +} diff --git a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs index b8ed6d65d..cbd08bc7c 100644 --- a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs +++ b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs @@ -43,7 +43,7 @@ public ProjectGroupControllerTests() public async Task CreateProjectGroup_ReturnsOk() { // Arrange - var response = new ProjectGroupResponseModel(1, "12312", _trustReferenceNumber, "trustName", null, null); + var response = new ProjectGroupResponseModel(1, "12312", _trustReferenceNumber, "trustName", null, null, null); var command = new CreateProjectGroupCommand("trustName", _trustReferenceNumber, _trustUkprn, []); _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) .ReturnsAsync(new CreateSuccessResult(response)); diff --git a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs index 806c54552..ff0acd2d6 100644 --- a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs +++ b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs @@ -91,7 +91,7 @@ await projectGroupQueryService.GetProjectGroupsAsync(searchModel!.StatusQueryStr [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetProjectGrouptById(int id, CancellationToken cancellationToken) { - var project = await projectGroupQueryService.GetProjectGroupById(id, cancellationToken); + var project = await projectGroupQueryService.GetProjectGroupByIdAsync(id, cancellationToken); if (project == null) { diff --git a/Dfe.Academies.Academisation.WebApi/Program.cs b/Dfe.Academies.Academisation.WebApi/Program.cs index ca92ca481..bfdfc43d3 100644 --- a/Dfe.Academies.Academisation.WebApi/Program.cs +++ b/Dfe.Academies.Academisation.WebApi/Program.cs @@ -41,7 +41,6 @@ using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; using NetEscapades.AspNetCore.SecurityHeaders; using Newtonsoft.Json; using Newtonsoft.Json.Converters; From 3562d6c043dbf325b29d1519e454c746039ba2ed Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Fri, 9 Aug 2024 09:31:34 +0100 Subject: [PATCH 4/4] Split the getting conversion projects db call into two calls --- .../ConversionProjectRepository.cs | 4 +-- .../IConversionProjectRepository.cs | 2 +- .../SetProjectGroupCommandHandlerTests.cs | 19 +++++++++----- .../SetProjectGroupCommandHandler.cs | 26 ++++++++++++++++++- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs index a038cd722..ef02d6188 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs @@ -334,10 +334,10 @@ public async Task> GetProjectsByProjectGroupIdsAsync(IEnum .ToListAsync(cancellationToken); } - public async Task> GetProjectsByIdsOrProjectGroupIAsync(IEnumerable projectIds, int? projectGroupId, CancellationToken cancellationToken) + public async Task> GetProjectsByIdsAsync(IEnumerable projectIds, CancellationToken cancellationToken) { return await dbSet - .Where(p => p.ProjectGroupId == projectGroupId || projectIds.Contains(p.Id)) + .Where(p => projectIds.Contains(p.Id)) .ToListAsync(cancellationToken); } } diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs index 8842e9a42..b7709b6fd 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs @@ -27,5 +27,5 @@ public interface IConversionProjectRepository : IRepository, IGenericRe Task> GetConversionProjectsForNewGroup(string trustReferenceNumber, CancellationToken cancellationToken); Task> GetProjectsByProjectGroupIdsAsync(IEnumerable projectGroupIds, CancellationToken cancellationToken); Task> GetConversionProjectsByProjectGroupIdAsync(int? projectGroupId, CancellationToken cancellationToken = default); - Task> GetProjectsByIdsOrProjectGroupIAsync(IEnumerable projectIds, int? projectGroupId, CancellationToken cancellationToken); + Task> GetProjectsByIdsAsync(IEnumerable projectIds, CancellationToken cancellationToken); } diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs index 9dd7f0f42..acd0ddeaf 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs @@ -75,7 +75,8 @@ public async Task Handle_ValidRequestWithoutConversions_ReturnsSuccess() }; _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken)).ReturnsAsync(expectedProjectGroup); - _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync([]); + _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsAsync(request.ConversionProjectIds, _cancellationToken)).ReturnsAsync([]); + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync([]); // Act var result = await _setProjectGroupCommandHandler.Handle( @@ -84,7 +85,8 @@ public async Task Handle_ValidRequestWithoutConversions_ReturnsSuccess() // Assert var commandSuccessResult = Assert.IsType(result); - _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsAsync(request.ConversionProjectIds, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken), Times.Once); _mockProjectGroupRepository.Verify(x => x.Update(It.IsAny()), Times.Never); _mockProjectGroupRepository.Verify(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken), Times.Once); _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Never()); @@ -105,7 +107,9 @@ public async Task Handle_ValidRequestWithNoRemovedConversions_ReturnsSuccess() }; _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken)).ReturnsAsync(expectedProjectGroup); - _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsAsync(request.ConversionProjectIds, _cancellationToken)).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync([]); + _mockConversionProjectRepository.Setup(x => x.Update(It.IsAny())); // Act @@ -115,7 +119,8 @@ public async Task Handle_ValidRequestWithNoRemovedConversions_ReturnsSuccess() // Assert var commandSuccessResult = Assert.IsType(result); - _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsAsync(request.ConversionProjectIds, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken), Times.Once); _mockConversionProjectRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Once); } @@ -134,7 +139,8 @@ public async Task Handle_ValidRequestWithOneRemovedConversions_ReturnsSuccess() }; _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken)).ReturnsAsync(expectedProjectGroup); - _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.GetProjectsByIdsAsync(request.ConversionProjectIds, _cancellationToken)).ReturnsAsync([]); + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken)).ReturnsAsync(expectedProjects); _mockConversionProjectRepository.Setup(x => x.Update(It.IsAny())); // Act @@ -144,7 +150,8 @@ public async Task Handle_ValidRequestWithOneRemovedConversions_ReturnsSuccess() // Assert var commandSuccessResult = Assert.IsType(result); - _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsOrProjectGroupIAsync(request.ConversionProjectIds, expectedProjectGroup.Id, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetProjectsByIdsAsync(request.ConversionProjectIds, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByProjectGroupIdAsync(expectedProjectGroup.Id, _cancellationToken), Times.Once); _mockConversionProjectRepository.Verify(x => x.Update(It.IsAny()), Times.Exactly(expectedProjects.Count)); _mockConversionProjectRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Once); } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs index 193e16bff..26e5ba875 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using FluentValidation; +using Dfe.Academies.Academisation.IDomain.ProjectAggregate; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { @@ -20,7 +21,9 @@ public async Task Handle(SetProjectGroupCommand message, Cancella return new NotFoundCommandResult(); } - var conversionProjects = await conversionProjectRepository.GetProjectsByIdsOrProjectGroupIAsync(message.ConversionProjectIds, projectGroup.Id, cancellationToken).ConfigureAwait(false); + var projects = await conversionProjectRepository.GetProjectsByIdsAsync(message.ConversionProjectIds, cancellationToken).ConfigureAwait(false); + var groupConversionProjects = await conversionProjectRepository.GetConversionProjectsByProjectGroupIdAsync(projectGroup.Id, cancellationToken).ConfigureAwait(false); + var conversionProjects = GetConversionProjects(projects, groupConversionProjects); if (conversionProjects != null && conversionProjects.Any()) { @@ -45,5 +48,26 @@ public async Task Handle(SetProjectGroupCommand message, Cancella } return new CommandSuccessResult(); } + + private IEnumerable GetConversionProjects(IEnumerable conversionProjects, IEnumerable groupConversionProjects) + { + var projects = new List(); + if (conversionProjects == null && !conversionProjects!.Any() && groupConversionProjects == null && !groupConversionProjects!.Any()) + { + return Enumerable.Empty(); + } + + if (conversionProjects != null && conversionProjects.Any()) + { + projects.AddRange(conversionProjects); + } + + if (groupConversionProjects != null && groupConversionProjects.Any()) + { + projects.AddRange(groupConversionProjects); + } + + return projects; + } } }