From 74964bf170991f56f36bffeeb19ee844a18fe588 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 24 Jun 2025 14:53:04 -0500 Subject: [PATCH] [PM-20577] OrganizationReport endpoints (#5986) * PM-20574 fixing namespaces on reporting work that got moved over from tools * PM-20574 adding tables, stored procedures, and migration files * PM-20574 adding dapper and ef repos and migrations * PM-20574 changing table and repo names as requested * PM-20574 updating sql scripts to new names * PM-20574 updating sql scripts * PM-20574 updating migration script for org delete by id * PM-20574 adding mysql migration * PM-20574 updating sql migration to fix database test * PM-20574 fixing migration script * PM-20574 fixing migration script * PM-20574 fixing table scripts * PM-20574 fixing table scripts * PM-20574 fixing migration script formatting * PM-20574 fixing syntax in migration script * PM-20574 fixing file names and extensions * PM-20574 fixing sql file * PM-20574 fixing sql * PM-20574 fixing directory for entities and removing scripts from other databases * PM-20574 generating new migration scripts * PM-20574 fixed reference to a stored proc * PM-20574 adding index in scripts and missing table * PM-20574 fixing merge conflicts * PM-20574 set OUTPUT param for Id property in create and update proc * PM-20574 add CreateDate to the update proc * PM-20574 amend update proc for OrganizationApplication by adding createDate * PM-20576 Created OrganizationReportRepo and unit tests * PM-20576 Commands and Query for OrganizationReport * PM-20576 added additional unit tests to fix CodeCoverage report * PM-20574 formatted sql and updated as per PR comments * PM-20574 updated script to fix build error * PM-20574 fixed inconsistency in db script * PM-20577 organization-reports endpoints * PM-20574 removed revisionDate, update procedures and used views * PM-20574 removed RevisionDate from designer files * PM-20574 removed revisionDate column that was missed previously * PM-20574 added revision date back into the mix * PM-20574 updated database script to fix build error * PM-20574 fixed a procedure issue * PM-20574 fix dB build error * PM-020574 fixed additional PR comments - files cleaned up * PM-20574 updated procedure was inconsistent * PM-20576 added logs and updated errors as per PR comments * PM-20576 fixed a build error * PM-20576 removed RevisionDate from Repo and tests * PM-20576 added dependency * PM-20576 removed unwanted line from csproj file --------- Co-authored-by: Graham Walker Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com> --- src/Api/Dirt/Controllers/ReportsController.cs | 79 +++++++++- .../DropOrganizationReportCommand.cs | 15 +- test/Api.Test/Dirt/ReportsControllerTests.cs | 138 ++++++++++++++++++ 3 files changed, 225 insertions(+), 7 deletions(-) diff --git a/src/Api/Dirt/Controllers/ReportsController.cs b/src/Api/Dirt/Controllers/ReportsController.cs index 4f2697dcc5..8bb8b5e487 100644 --- a/src/Api/Dirt/Controllers/ReportsController.cs +++ b/src/Api/Dirt/Controllers/ReportsController.cs @@ -23,6 +23,9 @@ public class ReportsController : Controller private readonly IAddPasswordHealthReportApplicationCommand _addPwdHealthReportAppCommand; private readonly IGetPasswordHealthReportApplicationQuery _getPwdHealthReportAppQuery; private readonly IDropPasswordHealthReportApplicationCommand _dropPwdHealthReportAppCommand; + private readonly IAddOrganizationReportCommand _addOrganizationReportCommand; + private readonly IDropOrganizationReportCommand _dropOrganizationReportCommand; + private readonly IGetOrganizationReportQuery _getOrganizationReportQuery; public ReportsController( ICurrentContext currentContext, @@ -30,7 +33,10 @@ public class ReportsController : Controller IRiskInsightsReportQuery riskInsightsReportQuery, IAddPasswordHealthReportApplicationCommand addPasswordHealthReportApplicationCommand, IGetPasswordHealthReportApplicationQuery getPasswordHealthReportApplicationQuery, - IDropPasswordHealthReportApplicationCommand dropPwdHealthReportAppCommand + IDropPasswordHealthReportApplicationCommand dropPwdHealthReportAppCommand, + IGetOrganizationReportQuery getOrganizationReportQuery, + IAddOrganizationReportCommand addOrganizationReportCommand, + IDropOrganizationReportCommand dropOrganizationReportCommand ) { _currentContext = currentContext; @@ -39,6 +45,9 @@ public class ReportsController : Controller _addPwdHealthReportAppCommand = addPasswordHealthReportApplicationCommand; _getPwdHealthReportAppQuery = getPasswordHealthReportApplicationQuery; _dropPwdHealthReportAppCommand = dropPwdHealthReportAppCommand; + _getOrganizationReportQuery = getOrganizationReportQuery; + _addOrganizationReportCommand = addOrganizationReportCommand; + _dropOrganizationReportCommand = dropOrganizationReportCommand; } /// @@ -204,4 +213,72 @@ public class ReportsController : Controller await _dropPwdHealthReportAppCommand.DropPasswordHealthReportApplicationAsync(request); } + + /// + /// Adds a new organization report + /// + /// A single instance of AddOrganizationReportRequest + /// A single instance of OrganizationReport + /// If user does not have access to the organization + /// If the organization Id is not valid + [HttpPost("organization-reports")] + public async Task AddOrganizationReport([FromBody] AddOrganizationReportRequest request) + { + if (!await _currentContext.AccessReports(request.OrganizationId)) + { + throw new NotFoundException(); + } + return await _addOrganizationReportCommand.AddOrganizationReportAsync(request); + } + + /// + /// Drops organization reports for an organization + /// + /// A single instance of DropOrganizationReportRequest + /// + /// If user does not have access to the organization + /// If the organization does not have any records + [HttpDelete("organization-reports")] + public async Task DropOrganizationReport([FromBody] DropOrganizationReportRequest request) + { + if (!await _currentContext.AccessReports(request.OrganizationId)) + { + throw new NotFoundException(); + } + await _dropOrganizationReportCommand.DropOrganizationReportAsync(request); + } + + /// + /// Gets organization reports for an organization + /// + /// A valid Organization Id + /// An Enumerable of OrganizationReport + /// If user does not have access to the organization + /// If the organization Id is not valid + [HttpGet("organization-reports/{orgId}")] + public async Task> GetOrganizationReports(Guid orgId) + { + if (!await _currentContext.AccessReports(orgId)) + { + throw new NotFoundException(); + } + return await _getOrganizationReportQuery.GetOrganizationReportAsync(orgId); + } + + /// + /// Gets the latest organization report for an organization + /// + /// A valid Organization Id + /// A single instance of OrganizationReport + /// If user does not have access to the organization + /// If the organization Id is not valid + [HttpGet("organization-reports/latest/{orgId}")] + public async Task GetLatestOrganizationReport(Guid orgId) + { + if (!await _currentContext.AccessReports(orgId)) + { + throw new NotFoundException(); + } + return await _getOrganizationReportQuery.GetLatestOrganizationReportAsync(orgId); + } } diff --git a/src/Core/Dirt/Reports/ReportFeatures/DropOrganizationReportCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/DropOrganizationReportCommand.cs index e382788273..8fe206c1f1 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/DropOrganizationReportCommand.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/DropOrganizationReportCommand.cs @@ -31,12 +31,15 @@ public class DropOrganizationReportCommand : IDropOrganizationReportCommand throw new BadRequestException("No data found."); } - data.Where(_ => request.OrganizationReportIds.Contains(_.Id)).ToList().ForEach(async _ => - { - _logger.LogInformation("Dropping organization report {organizationReportId} for organization {organizationId}", - _.Id, request.OrganizationId); + data + .Where(_ => request.OrganizationReportIds.Contains(_.Id)) + .ToList() + .ForEach(async reportId => + { + _logger.LogInformation("Dropping organization report {organizationReportId} for organization {organizationId}", + reportId, request.OrganizationId); - await _organizationReportRepo.DeleteAsync(_); - }); + await _organizationReportRepo.DeleteAsync(reportId); + }); } } diff --git a/test/Api.Test/Dirt/ReportsControllerTests.cs b/test/Api.Test/Dirt/ReportsControllerTests.cs index 37a6cb79c3..af285d8b85 100644 --- a/test/Api.Test/Dirt/ReportsControllerTests.cs +++ b/test/Api.Test/Dirt/ReportsControllerTests.cs @@ -142,4 +142,142 @@ public class ReportsControllerTests _.OrganizationId == request.OrganizationId && _.PasswordHealthReportApplicationIds == request.PasswordHealthReportApplicationIds)); } + + [Theory, BitAutoData] + public async Task AddOrganizationReportAsync_withAccess_success(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(true); + + // Act + var request = new AddOrganizationReportRequest + { + OrganizationId = Guid.NewGuid(), + ReportData = "Report Data", + Date = DateTime.UtcNow + }; + await sutProvider.Sut.AddOrganizationReport(request); + + // Assert + _ = sutProvider.GetDependency() + .Received(1) + .AddOrganizationReportAsync(Arg.Is(_ => + _.OrganizationId == request.OrganizationId && + _.ReportData == request.ReportData && + _.Date == request.Date)); + } + + [Theory, BitAutoData] + public async Task AddOrganizationReportAsync_withoutAccess(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(false); + // Act + var request = new AddOrganizationReportRequest + { + OrganizationId = Guid.NewGuid(), + ReportData = "Report Data", + Date = DateTime.UtcNow + }; + await Assert.ThrowsAsync(async () => + await sutProvider.Sut.AddOrganizationReport(request)); + // Assert + _ = sutProvider.GetDependency() + .Received(0); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withAccess_success(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(true); + // Act + var request = new DropOrganizationReportRequest + { + OrganizationId = Guid.NewGuid(), + OrganizationReportIds = new List { Guid.NewGuid(), Guid.NewGuid() } + }; + await sutProvider.Sut.DropOrganizationReport(request); + // Assert + _ = sutProvider.GetDependency() + .Received(1) + .DropOrganizationReportAsync(Arg.Is(_ => + _.OrganizationId == request.OrganizationId && + _.OrganizationReportIds.SequenceEqual(request.OrganizationReportIds))); + } + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withoutAccess(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(false); + // Act + var request = new DropOrganizationReportRequest + { + OrganizationId = Guid.NewGuid(), + OrganizationReportIds = new List { Guid.NewGuid(), Guid.NewGuid() } + }; + await Assert.ThrowsAsync(async () => + await sutProvider.Sut.DropOrganizationReport(request)); + // Assert + _ = sutProvider.GetDependency() + .Received(0); + } + [Theory, BitAutoData] + public async Task GetOrganizationReportAsync_withAccess_success(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(true); + // Act + var orgId = Guid.NewGuid(); + var result = await sutProvider.Sut.GetOrganizationReports(orgId); + // Assert + _ = sutProvider.GetDependency() + .Received(1) + .GetOrganizationReportAsync(Arg.Is(_ => _ == orgId)); + } + + [Theory, BitAutoData] + public async Task GetOrganizationReportAsync_withoutAccess(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(false); + // Act + var orgId = Guid.NewGuid(); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetOrganizationReports(orgId)); + // Assert + _ = sutProvider.GetDependency() + .Received(0); + + } + + [Theory, BitAutoData] + public async Task GetLastestOrganizationReportAsync_withAccess_success(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(true); + + // Act + var orgId = Guid.NewGuid(); + var result = await sutProvider.Sut.GetLatestOrganizationReport(orgId); + + // Assert + _ = sutProvider.GetDependency() + .Received(1) + .GetLatestOrganizationReportAsync(Arg.Is(_ => _ == orgId)); + } + + [Theory, BitAutoData] + public async Task GetLastestOrganizationReportAsync_withoutAccess(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(false); + + // Act + var orgId = Guid.NewGuid(); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetLatestOrganizationReport(orgId)); + + // Assert + _ = sutProvider.GetDependency() + .Received(0); + } }