mirror of
https://github.com/bitwarden/server
synced 2025-12-23 11:43:23 +00:00
Organization report tables, repos, services, and endpoints (#6158)
* PM-23754 initial commit * pm-23754 fixing controller tests * pm-23754 adding commands and queries * pm-23754 adding endpoints, command/queries, repositories, and sql migrations * pm-23754 add new sql scripts * PM-23754 adding sql scripts * pm-23754 * PM-23754 fixing migration script * PM-23754 fixing migration script again * PM-23754 fixing migration script validation * PM-23754 fixing db validation script issue * PM-23754 fixing endpoint and db validation * PM-23754 fixing unit tests * PM-23754 fixing implementation based on comments and tests * PM-23754 updating logging statements * PM-23754 making changes based on PR comments. * updating migration scripts * removing old migration files * update code based testing for whole data object for OrganizationReport and add a stored procedure. * updating services, unit tests, repository tests * fixing unit tests * fixing migration script * fixing migration script again * fixing migration script * another fix * fixing sql file, updating controller to account for different orgIds in the url and body. * updating error message in controllers without a body * making a change to the command * Refactor ReportsController by removing organization reports The IDropOrganizationReportCommand is no longer needed * will code based on PR comments. * fixing unit test * fixing migration script based on last changes. * adding another check in endpoint and adding unit tests * fixing route parameter. * PM-23754 updating data fields to return just the column * PM-23754 fixing repository method signatures * PM-23754 making change to orgId parameter through out code to align with api naming --------- Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com>
This commit is contained in:
@@ -42,8 +42,8 @@ public class OrganizationReportRepositoryTests
|
||||
var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organization);
|
||||
|
||||
report.OrganizationId = sqlOrganization.Id;
|
||||
var sqlOrgnizationReportRecord = await sqlOrganizationReportRepo.CreateAsync(report);
|
||||
var savedSqlOrganizationReport = await sqlOrganizationReportRepo.GetByIdAsync(sqlOrgnizationReportRecord.Id);
|
||||
var sqlOrganizationReportRecord = await sqlOrganizationReportRepo.CreateAsync(report);
|
||||
var savedSqlOrganizationReport = await sqlOrganizationReportRepo.GetByIdAsync(sqlOrganizationReportRecord.Id);
|
||||
records.Add(savedSqlOrganizationReport);
|
||||
|
||||
Assert.True(records.Count == 4);
|
||||
@@ -51,17 +51,19 @@ public class OrganizationReportRepositoryTests
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task RetrieveByOrganisation_Works(
|
||||
OrganizationReportRepository sqlPasswordHealthReportApplicationRepo,
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
var (firstOrg, _) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||
var (secondOrg, _) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||
var (firstOrg, firstReport) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
var (secondOrg, secondReport) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
|
||||
var firstSetOfRecords = await sqlPasswordHealthReportApplicationRepo.GetByOrganizationIdAsync(firstOrg.Id);
|
||||
var nextSetOfRecords = await sqlPasswordHealthReportApplicationRepo.GetByOrganizationIdAsync(secondOrg.Id);
|
||||
var firstRetrievedReport = await sqlOrganizationReportRepo.GetByIdAsync(firstReport.Id);
|
||||
var secondRetrievedReport = await sqlOrganizationReportRepo.GetByIdAsync(secondReport.Id);
|
||||
|
||||
Assert.True(firstSetOfRecords.Count == 1 && firstSetOfRecords.First().OrganizationId == firstOrg.Id);
|
||||
Assert.True(nextSetOfRecords.Count == 1 && nextSetOfRecords.First().OrganizationId == secondOrg.Id);
|
||||
Assert.NotNull(firstRetrievedReport);
|
||||
Assert.NotNull(secondRetrievedReport);
|
||||
Assert.Equal(firstOrg.Id, firstRetrievedReport.OrganizationId);
|
||||
Assert.Equal(secondOrg.Id, secondRetrievedReport.OrganizationId);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
@@ -112,6 +114,251 @@ public class OrganizationReportRepositoryTests
|
||||
Assert.True(dbRecords.Where(_ => _ == null).Count() == 4);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task GetLatestByOrganizationIdAsync_ShouldReturnLatestReport(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var (org, firstReport) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
|
||||
// Create a second report for the same organization with a later revision date
|
||||
var fixture = new Fixture();
|
||||
var secondReport = fixture.Build<OrganizationReport>()
|
||||
.With(x => x.OrganizationId, org.Id)
|
||||
.With(x => x.RevisionDate, firstReport.RevisionDate.AddMinutes(30))
|
||||
.Create();
|
||||
|
||||
await sqlOrganizationReportRepo.CreateAsync(secondReport);
|
||||
|
||||
// Act
|
||||
var latestReport = await sqlOrganizationReportRepo.GetLatestByOrganizationIdAsync(org.Id);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(latestReport);
|
||||
Assert.Equal(org.Id, latestReport.OrganizationId);
|
||||
Assert.True(latestReport.RevisionDate >= firstReport.RevisionDate);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task UpdateSummaryDataAsync_ShouldUpdateSummaryAndRevisionDate(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var (_, report) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
report.RevisionDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)); // ensure old revision date
|
||||
var newSummaryData = "Updated summary data";
|
||||
var originalRevisionDate = report.RevisionDate;
|
||||
|
||||
// Act
|
||||
var updatedReport = await sqlOrganizationReportRepo.UpdateSummaryDataAsync(report.OrganizationId, report.Id, newSummaryData);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(updatedReport);
|
||||
Assert.Equal(newSummaryData, updatedReport.SummaryData);
|
||||
Assert.True(updatedReport.RevisionDate > originalRevisionDate);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task GetSummaryDataAsync_ShouldReturnSummaryData(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var summaryData = "Test summary data";
|
||||
var (org, report) = await CreateOrganizationAndReportWithSummaryDataAsync(
|
||||
sqlOrganizationRepo, sqlOrganizationReportRepo, summaryData);
|
||||
|
||||
// Act
|
||||
var result = await sqlOrganizationReportRepo.GetSummaryDataAsync(report.Id);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(summaryData, result.SummaryData);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task GetSummaryDataByDateRangeAsync_ShouldReturnFilteredResults(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var baseDate = DateTime.UtcNow;
|
||||
var startDate = baseDate.AddDays(-10);
|
||||
var endDate = baseDate.AddDays(1);
|
||||
|
||||
// Create organization first
|
||||
var fixture = new Fixture();
|
||||
var organization = fixture.Create<Organization>();
|
||||
var org = await sqlOrganizationRepo.CreateAsync(organization);
|
||||
|
||||
// Create first report with a date within range
|
||||
var report1 = fixture.Build<OrganizationReport>()
|
||||
.With(x => x.OrganizationId, org.Id)
|
||||
.With(x => x.SummaryData, "Summary 1")
|
||||
.With(x => x.CreationDate, baseDate.AddDays(-5)) // Within range
|
||||
.With(x => x.RevisionDate, baseDate.AddDays(-5))
|
||||
.Create();
|
||||
await sqlOrganizationReportRepo.CreateAsync(report1);
|
||||
|
||||
// Create second report with a date within range
|
||||
var report2 = fixture.Build<OrganizationReport>()
|
||||
.With(x => x.OrganizationId, org.Id)
|
||||
.With(x => x.SummaryData, "Summary 2")
|
||||
.With(x => x.CreationDate, baseDate.AddDays(-3)) // Within range
|
||||
.With(x => x.RevisionDate, baseDate.AddDays(-3))
|
||||
.Create();
|
||||
await sqlOrganizationReportRepo.CreateAsync(report2);
|
||||
|
||||
// Act
|
||||
var results = await sqlOrganizationReportRepo.GetSummaryDataByDateRangeAsync(
|
||||
org.Id, startDate, endDate);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(results);
|
||||
var resultsList = results.ToList();
|
||||
Assert.True(resultsList.Count >= 2, $"Expected at least 2 results, but got {resultsList.Count}");
|
||||
Assert.All(resultsList, r => Assert.NotNull(r.SummaryData));
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task GetReportDataAsync_ShouldReturnReportData(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var reportData = "Test report data";
|
||||
var (org, report) = await CreateOrganizationAndReportWithReportDataAsync(
|
||||
sqlOrganizationRepo, sqlOrganizationReportRepo, reportData);
|
||||
|
||||
// Act
|
||||
var result = await sqlOrganizationReportRepo.GetReportDataAsync(report.Id);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(reportData, result.ReportData);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task UpdateReportDataAsync_ShouldUpdateReportDataAndRevisionDate(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var (org, report) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
var newReportData = "Updated report data";
|
||||
var originalRevisionDate = report.RevisionDate;
|
||||
|
||||
// Add a small delay to ensure revision date difference
|
||||
await Task.Delay(100);
|
||||
|
||||
// Act
|
||||
var updatedReport = await sqlOrganizationReportRepo.UpdateReportDataAsync(
|
||||
org.Id, report.Id, newReportData);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(updatedReport);
|
||||
Assert.Equal(org.Id, updatedReport.OrganizationId);
|
||||
Assert.Equal(report.Id, updatedReport.Id);
|
||||
Assert.Equal(newReportData, updatedReport.ReportData);
|
||||
Assert.True(updatedReport.RevisionDate >= originalRevisionDate,
|
||||
$"Expected RevisionDate {updatedReport.RevisionDate} to be >= {originalRevisionDate}");
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task GetApplicationDataAsync_ShouldReturnApplicationData(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var applicationData = "Test application data";
|
||||
var (org, report) = await CreateOrganizationAndReportWithApplicationDataAsync(
|
||||
sqlOrganizationRepo, sqlOrganizationReportRepo, applicationData);
|
||||
|
||||
// Act
|
||||
var result = await sqlOrganizationReportRepo.GetApplicationDataAsync(report.Id);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(applicationData, result.ApplicationData);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task UpdateApplicationDataAsync_ShouldUpdateApplicationDataAndRevisionDate(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var (org, report) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
var newApplicationData = "Updated application data";
|
||||
var originalRevisionDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)); // ensure old revision date
|
||||
|
||||
// Add a small delay to ensure revision date difference
|
||||
await Task.Delay(100);
|
||||
|
||||
// Act
|
||||
var updatedReport = await sqlOrganizationReportRepo.UpdateApplicationDataAsync(
|
||||
org.Id, report.Id, newApplicationData);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(updatedReport);
|
||||
Assert.Equal(org.Id, updatedReport.OrganizationId);
|
||||
Assert.Equal(report.Id, updatedReport.Id);
|
||||
Assert.Equal(newApplicationData, updatedReport.ApplicationData);
|
||||
Assert.True(updatedReport.RevisionDate >= originalRevisionDate,
|
||||
$"Expected RevisionDate {updatedReport.RevisionDate} to be >= {originalRevisionDate}");
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task GetSummaryDataAsync_WithNonExistentReport_ShouldReturnNull(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var (org, _) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
var nonExistentReportId = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
var result = await sqlOrganizationReportRepo.GetSummaryDataAsync(nonExistentReportId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task GetReportDataAsync_WithNonExistentReport_ShouldReturnNull(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var (org, _) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
var nonExistentReportId = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
var result = await sqlOrganizationReportRepo.GetReportDataAsync(nonExistentReportId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfOrganizationReportAutoData]
|
||||
public async Task GetApplicationDataAsync_WithNonExistentReport_ShouldReturnNull(
|
||||
OrganizationReportRepository sqlOrganizationReportRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
// Arrange
|
||||
var (org, _) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
|
||||
var nonExistentReportId = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
var result = await sqlOrganizationReportRepo.GetApplicationDataAsync(nonExistentReportId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
private async Task<(Organization, OrganizationReport)> CreateOrganizationAndReportAsync(
|
||||
IOrganizationRepository orgRepo,
|
||||
IOrganizationReportRepository orgReportRepo)
|
||||
@@ -121,6 +368,64 @@ public class OrganizationReportRepositoryTests
|
||||
|
||||
var orgReportRecord = fixture.Build<OrganizationReport>()
|
||||
.With(x => x.OrganizationId, organization.Id)
|
||||
.With(x => x.RevisionDate, organization.RevisionDate)
|
||||
.Create();
|
||||
|
||||
organization = await orgRepo.CreateAsync(organization);
|
||||
orgReportRecord = await orgReportRepo.CreateAsync(orgReportRecord);
|
||||
|
||||
return (organization, orgReportRecord);
|
||||
}
|
||||
|
||||
private async Task<(Organization, OrganizationReport)> CreateOrganizationAndReportWithSummaryDataAsync(
|
||||
IOrganizationRepository orgRepo,
|
||||
IOrganizationReportRepository orgReportRepo,
|
||||
string summaryData)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
var organization = fixture.Create<Organization>();
|
||||
|
||||
var orgReportRecord = fixture.Build<OrganizationReport>()
|
||||
.With(x => x.OrganizationId, organization.Id)
|
||||
.With(x => x.SummaryData, summaryData)
|
||||
.Create();
|
||||
|
||||
organization = await orgRepo.CreateAsync(organization);
|
||||
orgReportRecord = await orgReportRepo.CreateAsync(orgReportRecord);
|
||||
|
||||
return (organization, orgReportRecord);
|
||||
}
|
||||
|
||||
private async Task<(Organization, OrganizationReport)> CreateOrganizationAndReportWithReportDataAsync(
|
||||
IOrganizationRepository orgRepo,
|
||||
IOrganizationReportRepository orgReportRepo,
|
||||
string reportData)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
var organization = fixture.Create<Organization>();
|
||||
|
||||
var orgReportRecord = fixture.Build<OrganizationReport>()
|
||||
.With(x => x.OrganizationId, organization.Id)
|
||||
.With(x => x.ReportData, reportData)
|
||||
.Create();
|
||||
|
||||
organization = await orgRepo.CreateAsync(organization);
|
||||
orgReportRecord = await orgReportRepo.CreateAsync(orgReportRecord);
|
||||
|
||||
return (organization, orgReportRecord);
|
||||
}
|
||||
|
||||
private async Task<(Organization, OrganizationReport)> CreateOrganizationAndReportWithApplicationDataAsync(
|
||||
IOrganizationRepository orgRepo,
|
||||
IOrganizationReportRepository orgReportRepo,
|
||||
string applicationData)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
var organization = fixture.Create<Organization>();
|
||||
|
||||
var orgReportRecord = fixture.Build<OrganizationReport>()
|
||||
.With(x => x.OrganizationId, organization.Id)
|
||||
.With(x => x.ApplicationData, applicationData)
|
||||
.Create();
|
||||
|
||||
organization = await orgRepo.CreateAsync(organization);
|
||||
|
||||
Reference in New Issue
Block a user