From 1f7d7164e26fe710e80ed7f3205c4b00b6d0eb41 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Tue, 10 Dec 2024 10:30:50 +0000 Subject: [PATCH 01/21] EES-5656 Change PublicationService to use primary constructor --- .../Services/PublicationService.cs | 214 ++++++++---------- 1 file changed, 95 insertions(+), 119 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/PublicationService.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/PublicationService.cs index 1341e018e5..f3cb0a8689 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/PublicationService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/PublicationService.cs @@ -31,59 +31,35 @@ namespace GovUk.Education.ExploreEducationStatistics.Admin.Services { - public class PublicationService : IPublicationService + public class PublicationService( + ContentDbContext context, + IMapper mapper, + IPersistenceHelper persistenceHelper, + IUserService userService, + IPublicationRepository publicationRepository, + IReleaseVersionRepository releaseVersionRepository, + IMethodologyService methodologyService, + IPublicationCacheService publicationCacheService, + IMethodologyCacheService methodologyCacheService, + IRedirectsCacheService redirectsCacheService) + : IPublicationService { - private readonly ContentDbContext _context; - private readonly IMapper _mapper; - private readonly IPersistenceHelper _persistenceHelper; - private readonly IUserService _userService; - private readonly IPublicationRepository _publicationRepository; - private readonly IReleaseVersionRepository _releaseVersionRepository; - private readonly IMethodologyService _methodologyService; - private readonly IPublicationCacheService _publicationCacheService; - private readonly IMethodologyCacheService _methodologyCacheService; - private readonly IRedirectsCacheService _redirectsCacheService; - - public PublicationService( - ContentDbContext context, - IMapper mapper, - IPersistenceHelper persistenceHelper, - IUserService userService, - IPublicationRepository publicationRepository, - IReleaseVersionRepository releaseVersionRepository, - IMethodologyService methodologyService, - IPublicationCacheService publicationCacheService, - IMethodologyCacheService methodologyCacheService, - IRedirectsCacheService redirectsCacheService) - { - _context = context; - _mapper = mapper; - _persistenceHelper = persistenceHelper; - _userService = userService; - _publicationRepository = publicationRepository; - _releaseVersionRepository = releaseVersionRepository; - _methodologyService = methodologyService; - _publicationCacheService = publicationCacheService; - _methodologyCacheService = methodologyCacheService; - _redirectsCacheService = redirectsCacheService; - } - public async Task>> ListPublications( Guid? themeId = null) { - return await _userService + return await userService .CheckCanAccessSystem() - .OnSuccess(_ => _userService.CheckCanViewAllPublications() + .OnSuccess(_ => userService.CheckCanViewAllPublications() .OnSuccess(async () => { var hydratedPublication = HydratePublication( - _publicationRepository.QueryPublicationsForTheme(themeId)); + publicationRepository.QueryPublicationsForTheme(themeId)); return await hydratedPublication.ToListAsync(); }) .OrElse(() => { - var userId = _userService.GetUserId(); - return _publicationRepository.ListPublicationsForUser(userId, themeId); + var userId = userService.GetUserId(); + return publicationRepository.ListPublicationsForUser(userId, themeId); }) ) .OnSuccess(async publications => @@ -98,11 +74,11 @@ public async Task>> ListPublicat public async Task>> ListPublicationSummaries() { - return await _userService + return await userService .CheckCanViewAllPublications() .OnSuccess(_ => { - return _context.Publications + return context.Publications .Select(publication => new PublicationSummaryViewModel(publication)) .ToList(); }); @@ -115,7 +91,7 @@ public async Task> CreatePublic .OnSuccess(_ => ValidatePublicationSlug(publication.Slug)) .OnSuccess(async _ => { - var contact = await _context.Contacts.AddAsync(new Contact + var contact = await context.Contacts.AddAsync(new Contact { ContactName = publication.Contact.ContactName, ContactTelNo = string.IsNullOrWhiteSpace(publication.Contact.ContactTelNo) @@ -125,7 +101,7 @@ public async Task> CreatePublic TeamEmail = publication.Contact.TeamEmail }); - var saved = await _context.Publications.AddAsync(new Publication + var saved = await context.Publications.AddAsync(new Publication { Contact = contact.Entity, Title = publication.Title, @@ -134,9 +110,9 @@ public async Task> CreatePublic Slug = publication.Slug, }); - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(saved.Entity.Id, HydratePublication) .OnSuccess(GeneratePublicationCreateViewModel); }); @@ -146,14 +122,14 @@ public async Task> UpdatePublication( Guid publicationId, PublicationSaveRequest updatedPublication) { - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(publicationId) - .OnSuccess(_userService.CheckCanUpdatePublicationSummary) + .OnSuccess(userService.CheckCanUpdatePublicationSummary) .OnSuccessDo(async publication => { if (publication.Title != updatedPublication.Title) { - return await _userService.CheckCanUpdatePublication(); + return await userService.CheckCanUpdatePublication(); } return Unit.Instance; @@ -162,7 +138,7 @@ public async Task> UpdatePublication( { if (publication.SupersededById != updatedPublication.SupersededById) { - return await _userService.CheckCanUpdatePublication(); + return await userService.CheckCanUpdatePublication(); } return Unit.Instance; @@ -180,7 +156,7 @@ public async Task> UpdatePublication( { if (publication.ThemeId != updatedPublication.ThemeId) { - return await _userService.CheckCanUpdatePublication(); + return await userService.CheckCanUpdatePublication(); } return Unit.Instance; @@ -206,7 +182,7 @@ public async Task> UpdatePublication( publication.Slug = updatedPublication.Slug; if (publication.Live - && _context.PublicationRedirects.All(pr => + && context.PublicationRedirects.All(pr => !(pr.PublicationId == publicationId && pr.Slug == originalSlug))) // don't create duplicate redirect { var publicationRedirect = new PublicationRedirect @@ -215,16 +191,16 @@ public async Task> UpdatePublication( Publication = publication, PublicationId = publication.Id, }; - _context.PublicationRedirects.Add(publicationRedirect); + context.PublicationRedirects.Add(publicationRedirect); } // If there is an existing redirects for the new slug, they're redundant. Remove them - var redundantRedirects = await _context.PublicationRedirects + var redundantRedirects = await context.PublicationRedirects .Where(pr => pr.Slug == updatedPublication.Slug) .ToListAsync(); if (redundantRedirects.Count > 0) { - _context.PublicationRedirects.RemoveRange(redundantRedirects); + context.PublicationRedirects.RemoveRange(redundantRedirects); } } @@ -234,13 +210,13 @@ public async Task> UpdatePublication( publication.Updated = DateTime.UtcNow; publication.SupersededById = updatedPublication.SupersededById; - _context.Publications.Update(publication); + context.Publications.Update(publication); - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); if (titleChanged || slugChanged) { - await _methodologyService.PublicationTitleOrSlugChanged(publicationId, + await methodologyService.PublicationTitleOrSlugChanged(publicationId, originalSlug, publication.Title, publication.Slug); @@ -248,14 +224,14 @@ await _methodologyService.PublicationTitleOrSlugChanged(publicationId, if (publication.Live) { - await _methodologyCacheService.UpdateSummariesTree(); - await _publicationCacheService.UpdatePublicationTree(); - await _publicationCacheService.UpdatePublication(publication.Slug); + await methodologyCacheService.UpdateSummariesTree(); + await publicationCacheService.UpdatePublicationTree(); + await publicationCacheService.UpdatePublication(publication.Slug); if (slugChanged) { - await _publicationCacheService.RemovePublication(originalSlug); - await _redirectsCacheService.UpdateRedirects(); + await publicationCacheService.RemovePublication(originalSlug); + await redirectsCacheService.UpdateRedirects(); } await UpdateCachedSupersededPublications(publication); @@ -269,42 +245,42 @@ private async Task UpdateCachedSupersededPublications(Publication publication) { // NOTE: When a publication is updated, any publication that is superseded by it can be affected, so // update any superseded publications that are cached - var supersededPublications = await _context.Publications + var supersededPublications = await context.Publications .Where(p => p.SupersededById == publication.Id) .ToListAsync(); await supersededPublications .ToAsyncEnumerable() - .ForEachAwaitAsync(p => _publicationCacheService.UpdatePublication(p.Slug)); + .ForEachAwaitAsync(p => publicationCacheService.UpdatePublication(p.Slug)); } private async Task> ValidateSelectedTheme(Guid themeId) { - var theme = await _context.Themes.FindAsync(themeId); + var theme = await context.Themes.FindAsync(themeId); if (theme is null) { return ValidationActionResult(ThemeDoesNotExist); } - return await _userService.CheckCanCreatePublicationForTheme(theme) + return await userService.CheckCanCreatePublicationForTheme(theme) .OnSuccess(_ => Unit.Instance); } public async Task> GetPublication( Guid publicationId, bool includePermissions = false) { - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(publicationId, HydratePublication) - .OnSuccess(_userService.CheckCanViewPublication) + .OnSuccess(userService.CheckCanViewPublication) .OnSuccess(publication => GeneratePublicationViewModel(publication, includePermissions)); } public async Task> GetExternalMethodology(Guid publicationId) { - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(publicationId) - .OnSuccessDo(_userService.CheckCanViewPublication) + .OnSuccessDo(userService.CheckCanViewPublication) .OnSuccess(publication => publication.ExternalMethodology != null ? new ExternalMethodologyViewModel(publication.ExternalMethodology) : NotFound()); @@ -313,19 +289,19 @@ public async Task> GetExterna public async Task> UpdateExternalMethodology( Guid publicationId, ExternalMethodologySaveRequest updatedExternalMethodology) { - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(publicationId) - .OnSuccessDo(_userService.CheckCanManageExternalMethodologyForPublication) + .OnSuccessDo(userService.CheckCanManageExternalMethodologyForPublication) .OnSuccess(async publication => { - _context.Update(publication); + context.Update(publication); publication.ExternalMethodology ??= new ExternalMethodology(); publication.ExternalMethodology.Title = updatedExternalMethodology.Title; publication.ExternalMethodology.Url = updatedExternalMethodology.Url; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); // Update publication cache because ExternalMethodology is in Content.Services.ViewModels.PublicationViewModel - await _publicationCacheService.UpdatePublication(publication.Slug); + await publicationCacheService.UpdatePublication(publication.Slug); return new ExternalMethodologyViewModel(publication.ExternalMethodology); }); @@ -334,17 +310,17 @@ public async Task> UpdateExte public async Task> RemoveExternalMethodology( Guid publicationId) { - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(publicationId) - .OnSuccessDo(_userService.CheckCanManageExternalMethodologyForPublication) + .OnSuccessDo(userService.CheckCanManageExternalMethodologyForPublication) .OnSuccess(async publication => { - _context.Update(publication); + context.Update(publication); publication.ExternalMethodology = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); // Clear cache because ExternalMethodology is in Content.Services.ViewModels.PublicationViewModel - await _publicationCacheService.UpdatePublication(publication.Slug); + await publicationCacheService.UpdatePublication(publication.Slug); return Unit.Instance; }); @@ -352,24 +328,24 @@ public async Task> RemoveExternalMethodology( public async Task> GetContact(Guid publicationId) { - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(publicationId, query => query.Include(p => p.Contact)) - .OnSuccessDo(_userService.CheckCanViewPublication) - .OnSuccess(publication => _mapper.Map(publication.Contact)); + .OnSuccessDo(userService.CheckCanViewPublication) + .OnSuccess(publication => mapper.Map(publication.Contact)); } public async Task> UpdateContact(Guid publicationId, ContactSaveRequest updatedContact) { - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(publicationId, query => query.Include(p => p.Contact)) - .OnSuccessDo(_userService.CheckCanUpdateContact) + .OnSuccessDo(userService.CheckCanUpdateContact) .OnSuccess(async publication => { // Replace existing contact that is shared with another publication with a new // contact, as we want each publication to have its own contact. - if (_context.Publications + if (context.Publications .Any(p => p.ContactId == publication.ContactId && p.Id != publication.Id)) { publication.Contact = new Contact(); @@ -381,12 +357,12 @@ public async Task> UpdateContact(Guid pub : updatedContact.ContactTelNo; publication.Contact.TeamName = updatedContact.TeamName; publication.Contact.TeamEmail = updatedContact.TeamEmail; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); // Clear cache because Contact is in Content.Services.ViewModels.PublicationViewModel - await _publicationCacheService.UpdatePublication(publication.Slug); + await publicationCacheService.UpdatePublication(publication.Slug); - return _mapper.Map(publication.Contact); + return mapper.Map(publication.Contact); }); } @@ -415,14 +391,14 @@ public async Task>> ListLates bool? live = null, bool includePermissions = false) { - return await _persistenceHelper + return await persistenceHelper .CheckEntityExists(publicationId) - .OnSuccess(_userService.CheckCanViewPublication) + .OnSuccess(userService.CheckCanViewPublication) .OnSuccess(async () => { // Note the 'live' filter is applied after the latest release versions are retrieved. // A published release with a current draft version is deliberately not returned when 'live' is true. - var releaseVersions = (await _releaseVersionRepository.ListLatestReleaseVersions(publicationId)) + var releaseVersions = (await releaseVersionRepository.ListLatestReleaseVersions(publicationId)) .Where(rv => live == null || rv.Live == live) .ToList(); @@ -436,9 +412,9 @@ public async Task>> ListLates public async Task>> GetReleaseSeries( Guid publicationId) { - return await _context.Publications + return await context.Publications .FirstOrNotFoundAsync(p => p.Id == publicationId) - .OnSuccess(_userService.CheckCanViewPublication) + .OnSuccess(userService.CheckCanViewPublication) .OnSuccess(async publication => { var result = new List(); @@ -455,10 +431,10 @@ public async Task>> } else { - var release = await _context.Releases + var release = await context.Releases .SingleAsync(r => r.Id == seriesItem.ReleaseId); - var latestPublishedReleaseVersion = await _context.ReleaseVersions + var latestPublishedReleaseVersion = await context.ReleaseVersions .LatestReleaseVersion(releaseId: seriesItem.ReleaseId!.Value, publishedOnly: true) .SingleOrDefaultAsync(); @@ -483,9 +459,9 @@ public async Task>> Guid publicationId, ReleaseSeriesLegacyLinkAddRequest newLegacyLink) { - return await _context.Publications + return await context.Publications .FirstOrNotFoundAsync(p => p.Id == publicationId) - .OnSuccess(_userService.CheckCanManageReleaseSeries) + .OnSuccess(userService.CheckCanManageReleaseSeries) .OnSuccess(async publication => { publication.ReleaseSeries.Add(new ReleaseSeriesItem @@ -495,10 +471,10 @@ public async Task>> LegacyLinkUrl = newLegacyLink.Url, }); - _context.Publications.Update(publication); - await _context.SaveChangesAsync(); + context.Publications.Update(publication); + await context.SaveChangesAsync(); - await _publicationCacheService.UpdatePublication(publication.Slug); + await publicationCacheService.UpdatePublication(publication.Slug); return await GetReleaseSeries(publication.Id); }); @@ -508,9 +484,9 @@ public async Task>> Guid publicationId, List updatedReleaseSeriesItems) { - return await _context.Publications + return await context.Publications .FirstOrNotFoundAsync(p => p.Id == publicationId) - .OnSuccess(_userService.CheckCanManageReleaseSeries) + .OnSuccess(userService.CheckCanManageReleaseSeries) .OnSuccess(async publication => { // Check new series items details are correct @@ -530,7 +506,7 @@ public async Task>> } // Check all publication releases are included in updatedReleaseSeriesItems - var publicationReleaseIds = await _context.Releases + var publicationReleaseIds = await context.Releases .Where(r => r.PublicationId == publicationId) .Select(r => r.Id) .ToListAsync(); @@ -556,9 +532,9 @@ public async Task>> LegacyLinkUrl = request.LegacyLinkUrl, }).ToList(); - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); - await _publicationCacheService.UpdatePublication(publication.Slug); + await publicationCacheService.UpdatePublication(publication.Slug); return await GetReleaseSeries(publication.Id); }); @@ -567,7 +543,7 @@ public async Task>> private async Task> ValidatePublicationSlug( string newSlug, Guid? publicationId = null) { - if (await _context.Publications + if (await context.Publications .AnyAsync(publication => publication.Id != publicationId && publication.Slug == newSlug)) @@ -575,7 +551,7 @@ private async Task> ValidatePublicationSlug( return ValidationActionResult(PublicationSlugNotUnique); } - var hasRedirect = await _context.PublicationRedirects + var hasRedirect = await context.PublicationRedirects .AnyAsync(pr => pr.PublicationId != publicationId // If publication previously used this slug, can change it back && pr.Slug == newSlug); @@ -586,7 +562,7 @@ private async Task> ValidatePublicationSlug( } if (publicationId.HasValue && - _context.PublicationMethodologies.Any(pm => + context.PublicationMethodologies.Any(pm => pm.Publication.Id == publicationId && pm.Owner) // Strictly, we should also check whether the owned methodology inherits the publication slug - we don't @@ -594,7 +570,7 @@ private async Task> ValidatePublicationSlug( // this check is expensive and an unlikely edge case, so doesn't seem worth it. ) { - var methodologySlugValidation = await _methodologyService + var methodologySlugValidation = await methodologyService .ValidateMethodologySlug(newSlug); if (methodologySlugValidation.IsLeft) { @@ -614,14 +590,14 @@ public static IQueryable HydratePublication(IQueryable private async Task GeneratePublicationViewModel(Publication publication, bool includePermissions = false) { - var publicationViewModel = _mapper.Map(publication); + var publicationViewModel = mapper.Map(publication); - publicationViewModel.IsSuperseded = await _publicationRepository.IsSuperseded(publication.Id); + publicationViewModel.IsSuperseded = await publicationRepository.IsSuperseded(publication.Id); if (includePermissions) { publicationViewModel.Permissions = - await PermissionsUtils.GetPublicationPermissions(_userService, publication); + await PermissionsUtils.GetPublicationPermissions(userService, publication); } return publicationViewModel; @@ -629,9 +605,9 @@ private async Task GeneratePublicationViewModel(Publicatio private async Task GeneratePublicationCreateViewModel(Publication publication) { - var publicationCreateViewModel = _mapper.Map(publication); + var publicationCreateViewModel = mapper.Map(publication); - publicationCreateViewModel.IsSuperseded = await _publicationRepository.IsSuperseded(publication.Id); + publicationCreateViewModel.IsSuperseded = await publicationRepository.IsSuperseded(publication.Id); return publicationCreateViewModel; } @@ -639,11 +615,11 @@ private async Task GeneratePublicationCreateViewMode private async Task HydrateReleaseListItemViewModel(ReleaseVersion releaseVersion, bool includePermissions) { - var viewModel = _mapper.Map(releaseVersion); + var viewModel = mapper.Map(releaseVersion); if (includePermissions) { - viewModel.Permissions = await PermissionsUtils.GetReleasePermissions(_userService, releaseVersion); + viewModel.Permissions = await PermissionsUtils.GetReleasePermissions(userService, releaseVersion); } return viewModel; From 3043196c08ad068ac2702c469f5083ee0880067a Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Tue, 10 Dec 2024 09:40:52 +0000 Subject: [PATCH 02/21] EES-5656 Set the latest published release version for publication based on the latest release with a published version --- .../PublicationServicePermissionTests.cs | 2 + .../Services/PublicationServiceTests.cs | 169 ++++++++++++++++-- .../Services/PublicationService.cs | 29 +++ 3 files changed, 184 insertions(+), 16 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServicePermissionTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServicePermissionTests.cs index 75477c2195..62cc49678f 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServicePermissionTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServicePermissionTests.cs @@ -627,6 +627,7 @@ private static PublicationService BuildPublicationService( IReleaseVersionRepository? releaseVersionRepository = null, IMethodologyService? methodologyService = null, IPublicationCacheService? publicationCacheService = null, + IReleaseCacheService? releaseCacheService = null, IMethodologyCacheService? methodologyCacheService = null, IRedirectsCacheService? redirectsCacheService = null) { @@ -641,6 +642,7 @@ private static PublicationService BuildPublicationService( releaseVersionRepository ?? Mock.Of(Strict), methodologyService ?? Mock.Of(Strict), publicationCacheService ?? Mock.Of(Strict), + releaseCacheService ?? Mock.Of(Strict), methodologyCacheService ?? Mock.Of(Strict), redirectsCacheService ?? Mock.Of(Strict)); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs index 39fb7872c3..768316817b 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs @@ -2942,7 +2942,7 @@ public async Task UpdateReleaseSeries() _dataFixture .DefaultRelease(publishedVersions: 1, year: 2020), _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + .DefaultRelease(publishedVersions: 1, draftVersion: true, year: 2021), _dataFixture .DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022) ]) @@ -2953,6 +2953,16 @@ public async Task UpdateReleaseSeries() var release2021 = publication.Releases.Single(r => r.Year == 2021); var release2022 = publication.Releases.Single(r => r.Year == 2022); + // Check the publication's latest published release version in the generated test data setup + Assert.Equal(release2022.Versions[1].Id, publication.LatestPublishedReleaseVersionId); + + // Check the expected order of the release series items in the generated test data setup + Assert.Equal(4, publication.ReleaseSeries.Count); + Assert.Equal(release2022.Id, publication.ReleaseSeries[0].ReleaseId); + Assert.Equal(release2021.Id, publication.ReleaseSeries[1].ReleaseId); + Assert.Equal(release2020.Id, publication.ReleaseSeries[2].ReleaseId); + Assert.True(publication.ReleaseSeries[3].IsLegacyLink); + var contentDbContextId = Guid.NewGuid().ToString(); await using (var contentDbContext = InMemoryApplicationDbContext(contentDbContextId)) { @@ -2978,11 +2988,11 @@ public async Task UpdateReleaseSeries() new ReleaseSeriesItemUpdateRequest { LegacyLinkDescription = "Legacy link new", - LegacyLinkUrl = "https://test.com/new", + LegacyLinkUrl = "https://test.com/new" }, new ReleaseSeriesItemUpdateRequest { - ReleaseId = release2021.Id + ReleaseId = release2022.Id }, new ReleaseSeriesItemUpdateRequest { @@ -2990,14 +3000,13 @@ public async Task UpdateReleaseSeries() }, new ReleaseSeriesItemUpdateRequest { - ReleaseId = release2022.Id + ReleaseId = release2021.Id } ]); VerifyAllMocks(publicationCacheService); var viewModels = result.AssertRight(); - Assert.Equal(4, viewModels.Count); Assert.True(viewModels[0].IsLegacyLink); @@ -3009,11 +3018,11 @@ public async Task UpdateReleaseSeries() Assert.Equal("https://test.com/new", viewModels[0].LegacyLinkUrl); Assert.False(viewModels[1].IsLegacyLink); - Assert.Equal(release2021.Title, viewModels[1].Description); - Assert.Equal(release2021.Id, viewModels[1].ReleaseId); - Assert.Equal(release2021.Slug, viewModels[1].ReleaseSlug); - Assert.False(viewModels[1].IsLatest); - Assert.False(viewModels[1].IsPublished); + Assert.Equal(release2022.Title, viewModels[1].Description); + Assert.Equal(release2022.Id, viewModels[1].ReleaseId); + Assert.Equal(release2022.Slug, viewModels[1].ReleaseSlug); + Assert.True(viewModels[1].IsLatest); + Assert.True(viewModels[1].IsPublished); Assert.Null(viewModels[1].LegacyLinkUrl); Assert.False(viewModels[2].IsLegacyLink); @@ -3025,10 +3034,10 @@ public async Task UpdateReleaseSeries() Assert.Null(viewModels[2].LegacyLinkUrl); Assert.False(viewModels[3].IsLegacyLink); - Assert.Equal(release2022.Title, viewModels[3].Description); - Assert.Equal(release2022.Id, viewModels[3].ReleaseId); - Assert.Equal(release2022.Slug, viewModels[3].ReleaseSlug); - Assert.True(viewModels[3].IsLatest); + Assert.Equal(release2021.Title, viewModels[3].Description); + Assert.Equal(release2021.Id, viewModels[3].ReleaseId); + Assert.Equal(release2021.Slug, viewModels[3].ReleaseSlug); + Assert.False(viewModels[3].IsLatest); Assert.True(viewModels[3].IsPublished); Assert.Null(viewModels[3].LegacyLinkUrl); } @@ -3044,9 +3053,135 @@ public async Task UpdateReleaseSeries() Assert.Equal("Legacy link new", actualReleaseSeries[0].LegacyLinkDescription); Assert.Equal("https://test.com/new", actualReleaseSeries[0].LegacyLinkUrl); - Assert.Equal(release2021.Id, actualReleaseSeries[1].ReleaseId); + Assert.Equal(release2022.Id, actualReleaseSeries[1].ReleaseId); Assert.Equal(release2020.Id, actualReleaseSeries[2].ReleaseId); - Assert.Equal(release2022.Id, actualReleaseSeries[3].ReleaseId); + Assert.Equal(release2021.Id, actualReleaseSeries[3].ReleaseId); + + // The publication's latest published release version should be unchanged as 2022 was positioned + // as the first release after the legacy link + Assert.Equal(release2022.Versions[1].Id, publication.LatestPublishedReleaseVersionId); + } + } + + [Fact] + public async Task UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion() + { + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases([ + _dataFixture + .DefaultRelease(publishedVersions: 1, year: 2020), + _dataFixture + .DefaultRelease(publishedVersions: 1, draftVersion: true, year: 2021), + _dataFixture + .DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022) + ]) + .WithTheme(_dataFixture.DefaultTheme()); + + var release2020 = publication.Releases.Single(r => r.Year == 2020); + var release2021 = publication.Releases.Single(r => r.Year == 2021); + var release2022 = publication.Releases.Single(r => r.Year == 2022); + + var expectedLatestPublishedReleaseVersionId = release2021.Versions[0].Id; + + // Check the publication's latest published release version in the generated test data setup + Assert.Equal(release2022.Versions[1].Id, publication.LatestPublishedReleaseVersionId); + + // Check the expected order of the release series items in the generated test data setup + Assert.Equal(3, publication.ReleaseSeries.Count); + Assert.Equal(release2022.Id, publication.ReleaseSeries[0].ReleaseId); + Assert.Equal(release2021.Id, publication.ReleaseSeries[1].ReleaseId); + Assert.Equal(release2020.Id, publication.ReleaseSeries[2].ReleaseId); + + var contentDbContextId = Guid.NewGuid().ToString(); + await using (var contentDbContext = InMemoryApplicationDbContext(contentDbContextId)) + { + contentDbContext.Publications.Add(publication); + await contentDbContext.SaveChangesAsync(); + } + + await using (var contentDbContext = InMemoryApplicationDbContext(contentDbContextId)) + { + var publicationCacheService = new Mock(Strict); + publicationCacheService.Setup(mock => + mock.UpdatePublication(publication.Slug)) + .ReturnsAsync(new PublicationCacheViewModel()); + + var releaseCacheService = new Mock(Strict); + releaseCacheService.Setup(mock => mock.UpdateRelease( + expectedLatestPublishedReleaseVersionId, + publication.Slug, + null)) + .ReturnsAsync(new ReleaseCacheViewModel(expectedLatestPublishedReleaseVersionId)); + + var publicationService = BuildPublicationService( + contentDbContext, + publicationCacheService: publicationCacheService.Object, + releaseCacheService: releaseCacheService.Object); + + var result = await publicationService.UpdateReleaseSeries( + publication.Id, + updatedReleaseSeriesItems: + [ + new ReleaseSeriesItemUpdateRequest + { + ReleaseId = release2021.Id + }, + new ReleaseSeriesItemUpdateRequest + { + ReleaseId = release2020.Id + }, + new ReleaseSeriesItemUpdateRequest + { + ReleaseId = release2022.Id + } + ]); + + VerifyAllMocks(publicationCacheService, releaseCacheService); + + var viewModels = result.AssertRight(); + Assert.Equal(3, viewModels.Count); + + Assert.False(viewModels[0].IsLegacyLink); + Assert.Equal(release2021.Title, viewModels[0].Description); + Assert.Equal(release2021.Id, viewModels[0].ReleaseId); + Assert.Equal(release2021.Slug, viewModels[0].ReleaseSlug); + Assert.True(viewModels[0].IsLatest); + Assert.True(viewModels[0].IsPublished); + Assert.Null(viewModels[0].LegacyLinkUrl); + + Assert.False(viewModels[1].IsLegacyLink); + Assert.Equal(release2020.Title, viewModels[1].Description); + Assert.Equal(release2020.Id, viewModels[1].ReleaseId); + Assert.Equal(release2020.Slug, viewModels[1].ReleaseSlug); + Assert.False(viewModels[1].IsLatest); + Assert.True(viewModels[1].IsPublished); + Assert.Null(viewModels[1].LegacyLinkUrl); + + Assert.False(viewModels[2].IsLegacyLink); + Assert.Equal(release2022.Title, viewModels[2].Description); + Assert.Equal(release2022.Id, viewModels[2].ReleaseId); + Assert.Equal(release2022.Slug, viewModels[2].ReleaseSlug); + Assert.False(viewModels[2].IsLatest); + Assert.True(viewModels[2].IsPublished); + Assert.Null(viewModels[2].LegacyLinkUrl); + } + + await using (var contentDbContext = InMemoryApplicationDbContext(contentDbContextId)) + { + var actualPublication = await contentDbContext.Publications + .SingleAsync(p => p.Id == publication.Id); + + var actualReleaseSeries = actualPublication.ReleaseSeries; + Assert.Equal(3, actualReleaseSeries.Count); + + Assert.Equal(release2021.Id, actualReleaseSeries[0].ReleaseId); + Assert.Equal(release2020.Id, actualReleaseSeries[1].ReleaseId); + Assert.Equal(release2022.Id, actualReleaseSeries[2].ReleaseId); + + // The latest published version of 2021 should now be the publication's latest published release + // version since it was positioned as the first release + Assert.Equal(release2022.Versions[1].Id, publication.LatestPublishedReleaseVersionId); } } @@ -3246,6 +3381,7 @@ private static PublicationService BuildPublicationService( IReleaseVersionRepository? releaseVersionRepository = null, IMethodologyService? methodologyService = null, IPublicationCacheService? publicationCacheService = null, + IReleaseCacheService? releaseCacheService = null, IMethodologyCacheService? methodologyCacheService = null, IRedirectsCacheService? redirectsCacheService = null) { @@ -3258,6 +3394,7 @@ private static PublicationService BuildPublicationService( releaseVersionRepository ?? new ReleaseVersionRepository(context), methodologyService ?? Mock.Of(Strict), publicationCacheService ?? Mock.Of(Strict), + releaseCacheService ?? Mock.Of(Strict), methodologyCacheService ?? Mock.Of(Strict), redirectsCacheService ?? Mock.Of(Strict)); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/PublicationService.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/PublicationService.cs index f3cb0a8689..43b256454a 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/PublicationService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/PublicationService.cs @@ -40,6 +40,7 @@ public class PublicationService( IReleaseVersionRepository releaseVersionRepository, IMethodologyService methodologyService, IPublicationCacheService publicationCacheService, + IReleaseCacheService releaseCacheService, IMethodologyCacheService methodologyCacheService, IRedirectsCacheService redirectsCacheService) : IPublicationService @@ -523,6 +524,24 @@ public async Task>> publicationReleaseIds.JoinToString(",")); } + // Work out the publication's new latest published release version (if any). + // This is the latest published version of the first release which has a published version + Guid? latestPublishedReleaseVersionId = null; + foreach (var releaseId in updatedSeriesReleaseIds) + { + latestPublishedReleaseVersionId = (await context.ReleaseVersions + .LatestReleaseVersion(releaseId: releaseId, publishedOnly: true) + .SingleOrDefaultAsync())?.Id; + + if (latestPublishedReleaseVersionId != null) + { + break; + } + } + + var oldLatestPublishedReleaseVersionId = publication.LatestPublishedReleaseVersionId; + publication.LatestPublishedReleaseVersionId = latestPublishedReleaseVersionId; + publication.ReleaseSeries = updatedReleaseSeriesItems .Select(request => new ReleaseSeriesItem { @@ -534,8 +553,18 @@ public async Task>> await context.SaveChangesAsync(); + // Update the cached publication await publicationCacheService.UpdatePublication(publication.Slug); + // If the publication's latest published release version has changed, + // update the publication's cached latest release version + if (oldLatestPublishedReleaseVersionId != latestPublishedReleaseVersionId && + latestPublishedReleaseVersionId.HasValue) + { + await releaseCacheService.UpdateRelease(releaseVersionId: latestPublishedReleaseVersionId.Value, + publicationSlug: publication.Slug); + } + return await GetReleaseSeries(publication.Id); }); } From a12a20037f8ad81852eeb58a3126f7cd4c85eb5f Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Thu, 12 Dec 2024 09:46:04 +0000 Subject: [PATCH 03/21] EES-5656 Add UI tests to cover changing a publication's latest published release --- .../bau/release_reordering.robot | 75 ++++++++++++++++++- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/tests/robot-tests/tests/admin_and_public/bau/release_reordering.robot b/tests/robot-tests/tests/admin_and_public/bau/release_reordering.robot index 54fcece683..9daf295d23 100644 --- a/tests/robot-tests/tests/admin_and_public/bau/release_reordering.robot +++ b/tests/robot-tests/tests/admin_and_public/bau/release_reordering.robot @@ -136,8 +136,13 @@ Validate first release has latest release status in publication release order user checks table cell contains 3 4 Edit user checks table cell contains 3 4 Delete -Validate other releases section on public frontend +Navigate to first published release on public frontend user navigates to public frontend ${PUBLIC_RELEASE_1_URL} + +Validate first published release on public frontend is the latest data + user checks page contains This is the latest data + +Validate other releases section of first published release includes legacy releases user checks number of other releases is correct 2 ${view_releases}= user opens details dropdown View releases (2) @@ -230,8 +235,13 @@ Validate reordered publication releases user checks table cell contains 3 2 ${PUBLIC_RELEASE_1_URL} user checks table cell contains 3 3 Latest release -Validate other releases section on public frontend includes updated legacy release with expected order +Navigate to first published release on public frontend after reordering user navigates to public frontend ${PUBLIC_RELEASE_1_URL} + +Validate first published release is the latest data after reordering + user checks page contains This is the latest data + +Validate other releases section of first published release contains updated legacy release in expected order user checks number of other releases is correct 2 ${view_releases}= user opens details dropdown View releases (2) @@ -317,8 +327,13 @@ Validate second release has latest release status in publication release order user checks table cell contains 4 2 ${PUBLIC_RELEASE_1_URL} user checks table cell does not contain 4 3 Latest release -Validate other releases section on public frontend includes first release with expected order +Navigate to second published release on public frontend user navigates to public frontend ${PUBLIC_RELEASE_2_URL} + +Validate second published release is the latest data + user checks page contains This is the latest data + +Validate other releases section of second published release includes first release with expected order user checks number of other releases is correct 3 ${view_releases}= user opens details dropdown View releases (3) @@ -392,10 +407,62 @@ Validate first legacy release is deleted from publication release order user checks table cell contains 3 2 ${PUBLIC_RELEASE_1_URL} user checks table cell does not contain 3 3 Latest release -Validate other releases section on public frontend does not include first legacy release +Navigate to second published release on public frontend after deleting legacy release user navigates to public frontend ${PUBLIC_RELEASE_2_URL} + +Validate other releases section of second published release does not include first legacy release user checks number of other releases is correct 2 ${view_releases}= user opens details dropdown View releases (2) user checks other release is shown in position ${LEGACY_RELEASE_2_DESCRIPTION} 1 user checks other release is shown in position ${RELEASE_1_NAME} 2 + +Reorder the publication releases so the first release is the latest release + user navigates to publication page from dashboard ${PUBLICATION_NAME} + user clicks link Release order + user waits until h2 is visible Release order + + user clicks button Reorder releases + ${modal}= user waits until modal is visible Reorder releases + user clicks button OK ${modal} + user waits until modal is not visible Reorder releases + user waits until page contains button Confirm order + + click element xpath://div[text()="${RELEASE_1_NAME}"] CTRL + user presses keys ${SPACE} + user presses keys ARROW_UP + user presses keys ARROW_UP + user presses keys ${SPACE} + + user clicks button Confirm order + sleep 2 + +Validate first release has latest release status in publication release order after reordering + user waits until page contains button Reorder releases + user checks table body has x rows 3 testid:release-series + + user checks table cell contains 1 1 ${RELEASE_1_NAME} + user checks table cell contains 1 2 ${PUBLIC_RELEASE_1_URL} + user checks table cell contains 1 3 Latest release + + user checks table cell contains 2 1 ${RELEASE_2_NAME} + user checks table cell contains 2 2 ${PUBLIC_RELEASE_2_URL} + user checks table cell does not contain 2 3 Latest release + + user checks table cell contains 3 1 ${LEGACY_RELEASE_2_DESCRIPTION} + user checks table cell contains 3 2 ${LEGACY_RELEASE_2_URL} + user checks table cell contains 3 3 Legacy release + user checks table cell contains 3 4 Edit + user checks table cell contains 3 4 Delete + +Navigate to first published release on public frontend after changing the latest release + user navigates to public frontend ${PUBLIC_RELEASE_1_URL} + +Validate first published release is the latest data after changing the latest release + user checks page contains This is the latest data + +Navigate to second published release on public frontend after changing the latest release + user navigates to public frontend ${PUBLIC_RELEASE_2_URL} + +Validate second published release is not the latest data after changing the latest release + user checks page contains This is not the latest data From a15bf4fb93cd13cfe670ade6b8f874836898e70d Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Thu, 12 Dec 2024 15:43:10 +0000 Subject: [PATCH 04/21] EES-5656 Correct mistake in UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion --- .../Services/PublicationServiceTests.cs | 45 +++++-------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs index 768316817b..7af28748d0 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs @@ -2990,18 +2990,9 @@ public async Task UpdateReleaseSeries() LegacyLinkDescription = "Legacy link new", LegacyLinkUrl = "https://test.com/new" }, - new ReleaseSeriesItemUpdateRequest - { - ReleaseId = release2022.Id - }, - new ReleaseSeriesItemUpdateRequest - { - ReleaseId = release2020.Id - }, - new ReleaseSeriesItemUpdateRequest - { - ReleaseId = release2021.Id - } + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2022.Id }, + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2020.Id }, + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2021.Id } ]); VerifyAllMocks(publicationCacheService); @@ -3059,7 +3050,7 @@ public async Task UpdateReleaseSeries() // The publication's latest published release version should be unchanged as 2022 was positioned // as the first release after the legacy link - Assert.Equal(release2022.Versions[1].Id, publication.LatestPublishedReleaseVersionId); + Assert.Equal(release2022.Versions[1].Id, actualPublication.LatestPublishedReleaseVersionId); } } @@ -3123,18 +3114,9 @@ public async Task UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion() publication.Id, updatedReleaseSeriesItems: [ - new ReleaseSeriesItemUpdateRequest - { - ReleaseId = release2021.Id - }, - new ReleaseSeriesItemUpdateRequest - { - ReleaseId = release2020.Id - }, - new ReleaseSeriesItemUpdateRequest - { - ReleaseId = release2022.Id - } + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2021.Id }, + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2020.Id }, + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2022.Id } ]); VerifyAllMocks(publicationCacheService, releaseCacheService); @@ -3181,7 +3163,8 @@ public async Task UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion() // The latest published version of 2021 should now be the publication's latest published release // version since it was positioned as the first release - Assert.Equal(release2022.Versions[1].Id, publication.LatestPublishedReleaseVersionId); + Assert.Equal(expectedLatestPublishedReleaseVersionId, + actualPublication.LatestPublishedReleaseVersionId); } } @@ -3288,14 +3271,8 @@ public async Task UpdateReleaseSeries_SetDuplicateRelease() publication.Id, updatedReleaseSeriesItems: [ - new ReleaseSeriesItemUpdateRequest - { - ReleaseId = release.Id - }, - new ReleaseSeriesItemUpdateRequest - { - ReleaseId = release.Id - } + new ReleaseSeriesItemUpdateRequest { ReleaseId = release.Id }, + new ReleaseSeriesItemUpdateRequest { ReleaseId = release.Id } ])); Assert.Equal($"Missing or duplicate release in new release series. Expected ReleaseIds: {release.Id}", From 9787a3dc726c343f9071fe06bfe41cee48dd6f00 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Thu, 12 Dec 2024 16:38:40 +0000 Subject: [PATCH 05/21] EES-5656 Add additional test UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion_SkipsUnpublishedReleases and reduce level of detail checked in UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion --- .../Services/PublicationServiceTests.cs | 114 ++++++++++++++++-- 1 file changed, 102 insertions(+), 12 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs index 7af28748d0..de4e453532 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/PublicationServiceTests.cs @@ -3124,29 +3124,17 @@ public async Task UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion() var viewModels = result.AssertRight(); Assert.Equal(3, viewModels.Count); - Assert.False(viewModels[0].IsLegacyLink); - Assert.Equal(release2021.Title, viewModels[0].Description); Assert.Equal(release2021.Id, viewModels[0].ReleaseId); - Assert.Equal(release2021.Slug, viewModels[0].ReleaseSlug); Assert.True(viewModels[0].IsLatest); Assert.True(viewModels[0].IsPublished); - Assert.Null(viewModels[0].LegacyLinkUrl); - Assert.False(viewModels[1].IsLegacyLink); - Assert.Equal(release2020.Title, viewModels[1].Description); Assert.Equal(release2020.Id, viewModels[1].ReleaseId); - Assert.Equal(release2020.Slug, viewModels[1].ReleaseSlug); Assert.False(viewModels[1].IsLatest); Assert.True(viewModels[1].IsPublished); - Assert.Null(viewModels[1].LegacyLinkUrl); - Assert.False(viewModels[2].IsLegacyLink); - Assert.Equal(release2022.Title, viewModels[2].Description); Assert.Equal(release2022.Id, viewModels[2].ReleaseId); - Assert.Equal(release2022.Slug, viewModels[2].ReleaseSlug); Assert.False(viewModels[2].IsLatest); Assert.True(viewModels[2].IsPublished); - Assert.Null(viewModels[2].LegacyLinkUrl); } await using (var contentDbContext = InMemoryApplicationDbContext(contentDbContextId)) @@ -3168,6 +3156,108 @@ public async Task UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion() } } + [Fact] + public async Task UpdateReleaseSeries_UpdatesLatestPublishedReleaseVersion_SkipsUnpublishedReleases() + { + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases([ + _dataFixture + .DefaultRelease(publishedVersions: 1, year: 2020), + _dataFixture + .DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + _dataFixture + .DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022) + ]) + .WithTheme(_dataFixture.DefaultTheme()); + + var release2020 = publication.Releases.Single(r => r.Year == 2020); + var release2021 = publication.Releases.Single(r => r.Year == 2021); + var release2022 = publication.Releases.Single(r => r.Year == 2022); + + var expectedLatestPublishedReleaseVersionId = release2020.Versions[0].Id; + + // Check the publication's latest published release version in the generated test data setup + Assert.Equal(release2022.Versions[1].Id, publication.LatestPublishedReleaseVersionId); + + // Check the expected order of the release series items in the generated test data setup + Assert.Equal(3, publication.ReleaseSeries.Count); + Assert.Equal(release2022.Id, publication.ReleaseSeries[0].ReleaseId); + Assert.Equal(release2021.Id, publication.ReleaseSeries[1].ReleaseId); + Assert.Equal(release2020.Id, publication.ReleaseSeries[2].ReleaseId); + + var contentDbContextId = Guid.NewGuid().ToString(); + await using (var contentDbContext = InMemoryApplicationDbContext(contentDbContextId)) + { + contentDbContext.Publications.Add(publication); + await contentDbContext.SaveChangesAsync(); + } + + await using (var contentDbContext = InMemoryApplicationDbContext(contentDbContextId)) + { + var publicationCacheService = new Mock(Strict); + publicationCacheService.Setup(mock => + mock.UpdatePublication(publication.Slug)) + .ReturnsAsync(new PublicationCacheViewModel()); + + var releaseCacheService = new Mock(Strict); + releaseCacheService.Setup(mock => mock.UpdateRelease( + expectedLatestPublishedReleaseVersionId, + publication.Slug, + null)) + .ReturnsAsync(new ReleaseCacheViewModel(expectedLatestPublishedReleaseVersionId)); + + var publicationService = BuildPublicationService( + contentDbContext, + publicationCacheService: publicationCacheService.Object, + releaseCacheService: releaseCacheService.Object); + + var result = await publicationService.UpdateReleaseSeries( + publication.Id, + updatedReleaseSeriesItems: + [ + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2021.Id }, // Unpublished + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2020.Id }, + new ReleaseSeriesItemUpdateRequest { ReleaseId = release2022.Id } + ]); + + VerifyAllMocks(publicationCacheService, releaseCacheService); + + var viewModels = result.AssertRight(); + Assert.Equal(3, viewModels.Count); + + Assert.Equal(release2021.Id, viewModels[0].ReleaseId); + Assert.False(viewModels[0].IsLatest); + Assert.False(viewModels[0].IsPublished); + + Assert.Equal(release2020.Id, viewModels[1].ReleaseId); + Assert.True(viewModels[1].IsLatest); + Assert.True(viewModels[1].IsPublished); + + Assert.Equal(release2022.Id, viewModels[2].ReleaseId); + Assert.False(viewModels[2].IsLatest); + Assert.True(viewModels[2].IsPublished); + } + + await using (var contentDbContext = InMemoryApplicationDbContext(contentDbContextId)) + { + var actualPublication = await contentDbContext.Publications + .SingleAsync(p => p.Id == publication.Id); + + var actualReleaseSeries = actualPublication.ReleaseSeries; + Assert.Equal(3, actualReleaseSeries.Count); + + Assert.Equal(release2021.Id, actualReleaseSeries[0].ReleaseId); + Assert.Equal(release2020.Id, actualReleaseSeries[1].ReleaseId); + Assert.Equal(release2022.Id, actualReleaseSeries[2].ReleaseId); + + // The latest published version of 2020 should now be the publication's latest published release + // version since it was positioned as the next release after 2021 which is unpublished + Assert.Equal(expectedLatestPublishedReleaseVersionId, + actualPublication.LatestPublishedReleaseVersionId); + } + } + [Fact] public async Task UpdateReleaseSeries_SetEmpty() { From 5fba23dee9d17b7590074b06b6b78d6826d8d41f Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Thu, 12 Dec 2024 17:05:07 +0000 Subject: [PATCH 06/21] EES-5656 Remove incorrect mention of ordering in method comments --- .../Repository/Interfaces/IReleaseVersionRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs index f17b7e734c..7333c2e4be 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs @@ -65,7 +65,7 @@ Task IsLatestReleaseVersion( CancellationToken cancellationToken = default); /// - /// Retrieves the latest published release version id's associated with a publication in reverse chronological order. + /// Retrieves the latest published release version id's associated with a publication. /// /// The unique identifier of the publication. /// A to observe while waiting for the task to complete. @@ -85,7 +85,7 @@ Task> ListLatestPublishedReleaseVersions( CancellationToken cancellationToken = default); /// - /// Retrieves the latest version id's of all releases associated with a publication in reverse chronological order. + /// Retrieves the latest version id's of all releases associated with a publication. /// /// The unique identifier of the publication. /// A to observe while waiting for the task to complete. From 303e0a1203717b55a43c6dce2bc304746173bf52 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Thu, 12 Dec 2024 17:05:21 +0000 Subject: [PATCH 07/21] EES-5656 Remove mention of 'published' in method comments because it's dependent on the value of `publishedOnly` --- .../Predicates/ReleaseVersionPredicates.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Predicates/ReleaseVersionPredicates.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Predicates/ReleaseVersionPredicates.cs index 90fd93d88a..5756080918 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Predicates/ReleaseVersionPredicates.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Predicates/ReleaseVersionPredicates.cs @@ -11,7 +11,7 @@ public static class ReleaseVersionPredicates { /// /// Filters a sequence of of type to only include the latest - /// published versions of each release. + /// versions of each release. /// /// The source of type to filter. /// Unique identifier of a publication to filter by. @@ -50,20 +50,20 @@ public static IQueryable LatestReleaseVersions(this IQueryable /// Filters a sequence of of type to only include the latest - /// published version of the release. + /// version of the release. /// - /// The source of type to filter. + /// The source of type to filter. /// Unique identifier of a release to filter by. /// Flag to only include published release versions. /// An of type that contains elements from the input /// sequence filtered to only include the latest version of the release. - public static IQueryable LatestReleaseVersion(this IQueryable releaseVersionsQueryable, + public static IQueryable LatestReleaseVersion(this IQueryable releaseVersions, Guid releaseId, bool publishedOnly = false) { - return releaseVersionsQueryable + return releaseVersions .Where(releaseVersion => releaseVersion.ReleaseId == releaseId) - .Where(releaseVersion => releaseVersion.Version == releaseVersionsQueryable + .Where(releaseVersion => releaseVersion.Version == releaseVersions .Where(latestVersion => latestVersion.ReleaseId == releaseId) .Where(latestVersion => !publishedOnly || latestVersion.Published.HasValue) .Select(latestVersion => (int?)latestVersion.Version) From 2bb26de3ae678b3952a2f44e1962014890749611 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Thu, 12 Dec 2024 17:08:19 +0000 Subject: [PATCH 08/21] EES-5656 Check latest release link has correct release name on non-latest release page --- .../tests/admin_and_public/bau/release_reordering.robot | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/robot-tests/tests/admin_and_public/bau/release_reordering.robot b/tests/robot-tests/tests/admin_and_public/bau/release_reordering.robot index 9daf295d23..2aaa418882 100644 --- a/tests/robot-tests/tests/admin_and_public/bau/release_reordering.robot +++ b/tests/robot-tests/tests/admin_and_public/bau/release_reordering.robot @@ -466,3 +466,4 @@ Navigate to second published release on public frontend after changing the lates Validate second published release is not the latest data after changing the latest release user checks page contains This is not the latest data + user waits until page contains link View latest data: ${RELEASE_1_NAME} From 7ff9fe3deb4c1407bd09b78b54c77ee034f5f253 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Fri, 13 Dec 2024 17:54:08 +0000 Subject: [PATCH 09/21] EES-5656 Change ManageContentPageService.GetManageContentPageViewModel to not use method ListLatestPublishedReleaseVersions --- .../ManageContentPageServiceTests.cs | 9 +-- .../ManageContent/ManageContentPageService.cs | 74 ++++++++++--------- .../Startup.cs | 1 + .../Interfaces/IReleaseRepository.cs | 30 ++++++++ .../Repository/ReleaseRepository.cs | 40 ++++++++++ 5 files changed, 113 insertions(+), 41 deletions(-) create mode 100644 src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseRepository.cs create mode 100644 src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseRepository.cs diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/ManageContent/ManageContentPageServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/ManageContent/ManageContentPageServiceTests.cs index 7d7b01ece2..f4dea4b73a 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/ManageContent/ManageContentPageServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/ManageContent/ManageContentPageServiceTests.cs @@ -17,6 +17,7 @@ using GovUk.Education.ExploreEducationStatistics.Content.Model; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; using GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository; using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; using GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; using Microsoft.AspNetCore.Mvc; @@ -26,10 +27,6 @@ using static GovUk.Education.ExploreEducationStatistics.Common.Model.TimeIdentifier; using static GovUk.Education.ExploreEducationStatistics.Common.Services.CollectionUtils; using HtmlBlockViewModel = GovUk.Education.ExploreEducationStatistics.Admin.ViewModels.HtmlBlockViewModel; -using IReleaseVersionRepository = - GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces.IReleaseVersionRepository; -using ReleaseVersionRepository = - GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.ReleaseVersionRepository; namespace GovUk.Education.ExploreEducationStatistics.Admin.Tests.Services.ManageContent { @@ -678,7 +675,7 @@ private static ManageContentPageService SetupManageContentPageService( IDataBlockService? dataBlockService = null, IMethodologyVersionRepository? methodologyVersionRepository = null, IReleaseFileService? releaseFileService = null, - IReleaseVersionRepository? releaseVersionRepository = null, + IReleaseRepository? releaseRepository = null, IUserService? userService = null) { return new( @@ -688,7 +685,7 @@ private static ManageContentPageService SetupManageContentPageService( dataBlockService ?? new Mock().Object, methodologyVersionRepository ?? new Mock().Object, releaseFileService ?? new Mock().Object, - releaseVersionRepository ?? new ReleaseVersionRepository(contentDbContext), + releaseRepository ?? new ReleaseRepository(contentDbContext), userService ?? MockUtils.AlwaysTrueUserService().Object ); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/ManageContent/ManageContentPageService.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/ManageContent/ManageContentPageService.cs index f6c6c038a9..84ba3798b6 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/ManageContent/ManageContentPageService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/ManageContent/ManageContentPageService.cs @@ -19,7 +19,6 @@ using System.Linq; using System.Threading.Tasks; using static GovUk.Education.ExploreEducationStatistics.Common.Model.FileType; -using IReleaseVersionRepository = GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces.IReleaseVersionRepository; namespace GovUk.Education.ExploreEducationStatistics.Admin.Services.ManageContent { @@ -31,7 +30,7 @@ public class ManageContentPageService : IManageContentPageService private readonly IDataBlockService _dataBlockService; private readonly IMethodologyVersionRepository _methodologyVersionRepository; private readonly IReleaseFileService _releaseFileService; - private readonly IReleaseVersionRepository _releaseVersionRepository; + private readonly IReleaseRepository _releaseRepository; private readonly IUserService _userService; public ManageContentPageService( @@ -41,7 +40,7 @@ public ManageContentPageService( IDataBlockService dataBlockService, IMethodologyVersionRepository methodologyVersionRepository, IReleaseFileService releaseFileService, - IReleaseVersionRepository releaseVersionRepository, + IReleaseRepository releaseRepository, IUserService userService) { _contentDbContext = contentDbContext; @@ -50,7 +49,7 @@ public ManageContentPageService( _dataBlockService = dataBlockService; _methodologyVersionRepository = methodologyVersionRepository; _releaseFileService = releaseFileService; - _releaseVersionRepository = releaseVersionRepository; + _releaseRepository = releaseRepository; _userService = userService; } @@ -101,38 +100,12 @@ public async Task> GetManageCon var releaseViewModel = _mapper.Map(releaseVersion); - // Hydrate Publication.ReleaseSeries - var publishedVersions = - await _releaseVersionRepository - .ListLatestPublishedReleaseVersions(releaseVersion.PublicationId); - var filteredReleaseSeries = releaseVersion.Publication.ReleaseSeries - .Where(rsi => // only show items for legacy links and published releases - rsi.IsLegacyLink - || publishedVersions - .Any(rv => rsi.ReleaseId == rv.ReleaseId) - ).ToList(); - releaseViewModel.Publication.ReleaseSeries = filteredReleaseSeries - .Select(rsi => - { - if (rsi.IsLegacyLink) - { - return new ReleaseSeriesItemViewModel - { - Description = rsi.LegacyLinkDescription!, - LegacyLinkUrl = rsi.LegacyLinkUrl, - }; - } - - var latestReleaseVersion = publishedVersions - .Single(rv => rv.ReleaseId == rsi.ReleaseId); + var publishedReleases = + await _releaseRepository.ListPublishedReleases(releaseVersion.PublicationId); - return new ReleaseSeriesItemViewModel - { - Description = latestReleaseVersion.Title, - ReleaseId = latestReleaseVersion.ReleaseId, - ReleaseSlug = latestReleaseVersion.Slug, - }; - }).ToList(); + // Hydrate Publication.ReleaseSeries + releaseViewModel.Publication.ReleaseSeries = + BuildReleaseSeriesItemViewModels(releaseVersion.Publication, publishedReleases); releaseViewModel.DownloadFiles = files.ToList(); releaseViewModel.Publication.Methodologies = @@ -146,6 +119,37 @@ await _releaseVersionRepository }); } + private static List BuildReleaseSeriesItemViewModels( + Publication publication, + List publishedReleases) + { + var publishedReleasesById = publishedReleases.ToDictionary(r => r.Id); + return publication.ReleaseSeries + // Only include release series items for legacy links and published releases + .Where(rsi => rsi.IsLegacyLink || publishedReleasesById.ContainsKey(rsi.ReleaseId!.Value)) + .Select(rsi => + { + if (rsi.IsLegacyLink) + { + return new ReleaseSeriesItemViewModel + { + Description = rsi.LegacyLinkDescription!, + LegacyLinkUrl = rsi.LegacyLinkUrl + }; + } + + var release = publishedReleasesById[rsi.ReleaseId!.Value]; + + return new ReleaseSeriesItemViewModel + { + Description = release.Title, + ReleaseId = release.Id, + ReleaseSlug = release.Slug + }; + }) + .ToList(); + } + private IQueryable HydrateReleaseQuery(IQueryable queryable) { // Using `AsSplitQuery` as the generated SQL without it is incredibly diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Startup.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Startup.cs index b04002a6bb..f41641b645 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin/Startup.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Startup.cs @@ -552,6 +552,7 @@ public virtual void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient + /// Retrieves the published releases that are associated with a publication. + /// + /// The unique identifier of the publication. + /// A to observe while waiting for the task to complete. + /// A collection of the published releases associated with the publication. + Task> ListPublishedReleases( + Guid publicationId, + CancellationToken cancellationToken = default); + + /// + /// Retrieves the id's of all published releases that are associated with a publication. + /// + /// The unique identifier of the publication. + /// A to observe while waiting for the task to complete. + /// A collection of the id's of all published releases associated with the publication. + Task> ListPublishedReleaseIds( + Guid publicationId, + CancellationToken cancellationToken = default); +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseRepository.cs new file mode 100644 index 0000000000..fc9bc10a6b --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseRepository.cs @@ -0,0 +1,40 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace GovUk.Education.ExploreEducationStatistics.Content.Model.Repository; + +public class ReleaseRepository(ContentDbContext context) : IReleaseRepository +{ + public Task> ListPublishedReleases( + Guid publicationId, + CancellationToken cancellationToken = default) + { + return QueryPublishedReleases(publicationId) + .ToListAsync(cancellationToken); + } + + public Task> ListPublishedReleaseIds( + Guid publicationId, + CancellationToken cancellationToken = default) + { + return QueryPublishedReleases(publicationId) + .Select(r => r.Id) + .ToListAsync(cancellationToken); + } + + private IQueryable QueryPublishedReleases(Guid publicationId) + { + // For simplicity, we only query releases that have ANY version that has been published. + // In future this may need to change if release versions can be recalled/unpublished. + return context.Releases + .Where(r => r.PublicationId == publicationId) + .Where(r => r.Versions.Any(rv => rv.Published != null)); + } +} From da82791b157ba444bce12c384bb7c927de713833 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Fri, 13 Dec 2024 18:09:06 +0000 Subject: [PATCH 10/21] EES-5656 Change PublicationService.Get to not use method ListLatestPublishedReleaseVersions --- .../Startup.cs | 1 + .../Interfaces/IReleaseRepository.cs | 4 +- .../Repository/ReleaseRepository.cs | 26 +++-- .../Cache/PublicationCacheServiceTests.cs | 8 +- .../PublicationServiceTests.cs | 107 ++++++++++-------- .../PublicationService.cs | 104 ++++++++--------- .../PublicationCacheViewModel.cs | 2 +- .../PublicationViewModel.cs | 4 +- .../ReleaseTitleViewModel.cs | 10 ++ .../ReleaseVersionTitleViewModel.cs | 10 -- .../PublisherHostBuilderExtensions.cs | 1 + 11 files changed, 153 insertions(+), 124 deletions(-) create mode 100644 src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/ReleaseTitleViewModel.cs delete mode 100644 src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/ReleaseVersionTitleViewModel.cs diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Startup.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Startup.cs index f926c6e179..b75ee88371 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Startup.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Startup.cs @@ -162,6 +162,7 @@ public virtual void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseRepository.cs index ab46e1d668..9a7bbbef14 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseRepository.cs @@ -9,11 +9,11 @@ namespace GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.In public interface IReleaseRepository { /// - /// Retrieves the published releases that are associated with a publication. + /// Retrieves the published releases in release series order that are associated with a publication. /// /// The unique identifier of the publication. /// A to observe while waiting for the task to complete. - /// A collection of the published releases associated with the publication. + /// A collection of the published releases in release series order associated with the publication. Task> ListPublishedReleases( Guid publicationId, CancellationToken cancellationToken = default); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseRepository.cs index fc9bc10a6b..ce4e1aa401 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseRepository.cs @@ -5,26 +5,38 @@ using System.Threading; using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; using Microsoft.EntityFrameworkCore; namespace GovUk.Education.ExploreEducationStatistics.Content.Model.Repository; -public class ReleaseRepository(ContentDbContext context) : IReleaseRepository +public class ReleaseRepository(ContentDbContext contentDbContext) : IReleaseRepository { - public Task> ListPublishedReleases( + public async Task> ListPublishedReleases( Guid publicationId, CancellationToken cancellationToken = default) { - return QueryPublishedReleases(publicationId) - .ToListAsync(cancellationToken); + var publication = await contentDbContext.Publications + .SingleAsync(p => p.Id == publicationId, cancellationToken: cancellationToken); + + var publicationReleaseSeriesReleaseIds = publication.ReleaseSeries.ReleaseIds(); + + var releaseIdIndexMap = publicationReleaseSeriesReleaseIds + .Select((releaseId, index) => (releaseId, index)) + .ToDictionary(tuple => tuple.releaseId, tuple => tuple.index); + + return (await QueryPublishedReleases(publicationId) + .ToListAsync(cancellationToken)) + .OrderBy(r => releaseIdIndexMap[r.Id]) + .ToList(); } - public Task> ListPublishedReleaseIds( + public async Task> ListPublishedReleaseIds( Guid publicationId, CancellationToken cancellationToken = default) { - return QueryPublishedReleases(publicationId) + return await QueryPublishedReleases(publicationId) .Select(r => r.Id) .ToListAsync(cancellationToken); } @@ -33,7 +45,7 @@ private IQueryable QueryPublishedReleases(Guid publicationId) { // For simplicity, we only query releases that have ANY version that has been published. // In future this may need to change if release versions can be recalled/unpublished. - return context.Releases + return contentDbContext.Releases .Where(r => r.PublicationId == publicationId) .Where(r => r.Versions.Any(rv => rv.Published != null)); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/Cache/PublicationCacheServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/Cache/PublicationCacheServiceTests.cs index 55bd9df941..ccf1951379 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/Cache/PublicationCacheServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/Cache/PublicationCacheServiceTests.cs @@ -43,15 +43,15 @@ public class PublicationCacheServiceTests : CacheServiceTestFixture Url = "" }, LatestReleaseId = Guid.NewGuid(), - Releases = new List - { - new() + Releases = + [ + new ReleaseTitleViewModel { Id = Guid.NewGuid(), Slug = "", Title = "" } - }, + ], ReleaseSeries = [ new ReleaseSeriesItemViewModel diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/PublicationServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/PublicationServiceTests.cs index 6821c31bde..862a979d2e 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/PublicationServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/PublicationServiceTests.cs @@ -122,32 +122,39 @@ public class GetTests : PublicationServiceTests Url = "https://external.methodology.com", }; - private readonly List _legacyLinks = new() - { - new ReleaseSeriesItem - { - Id = Guid.NewGuid(), - LegacyLinkDescription = "Legacy release description", - LegacyLinkUrl = "https://legacy.release.com", - }, - }; - [Fact] public async Task Success() { + ReleaseSeriesItem legacyLink = _dataFixture.DefaultLegacyReleaseSeriesItem(); + Publication publication = _dataFixture .DefaultPublication() - .WithReleases(ListOf( + .WithReleases([ _dataFixture - .DefaultRelease(publishedVersions: 1, year: 2020), + .DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022), _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + .DefaultRelease(publishedVersions: 1, year: 2020), _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022))) + .DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021) + ]) .WithContact(_contact) .WithExternalMethodology(_externalMethodology) - .WithLegacyLinks(_legacyLinks) - .WithTheme(_dataFixture.DefaultTheme()); + .WithLegacyLinks([legacyLink]) + .WithTheme(_dataFixture.DefaultTheme()) + .FinishWith(p => + { + // Adjust the generated LatestPublishedReleaseVersion to make 2020 the latest published release + var release2020Version0 = p.Releases.Single(r => r.Year == 2020).Versions[0]; + p.LatestPublishedReleaseVersion = release2020Version0; + p.LatestPublishedReleaseVersionId = release2020Version0.Id; + + // Apply a different release series order rather than using the default + p.ReleaseSeries = + [.. GenerateReleaseSeries(p.Releases, 2021, 2020, 2022), legacyLink]; + }); + + var release2020 = publication.Releases.Single(r => r.Year == 2020); + var release2022 = publication.Releases.Single(r => r.Year == 2022); var contentDbContextId = Guid.NewGuid().ToString(); await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) @@ -158,11 +165,6 @@ public async Task Success() await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { - var expectedReleaseVersion1 = publication.ReleaseVersions - .Single(rv => rv is { Year: 2022, Version: 1 }); - var expectedReleaseVersion2 = publication.ReleaseVersions - .Single(rv => rv is { Year: 2020, Version: 0 }); - var service = SetupPublicationService(contentDbContext); var result = await service.Get(publication.Slug); @@ -175,41 +177,41 @@ public async Task Success() Assert.False(publicationViewModel.IsSuperseded); Assert.Equal(2, publicationViewModel.Releases.Count); - Assert.Equal(expectedReleaseVersion1.Id, publicationViewModel.LatestReleaseId); - Assert.Equal(expectedReleaseVersion1.Id, publicationViewModel.Releases[0].Id); - Assert.Equal(expectedReleaseVersion1.Slug, publicationViewModel.Releases[0].Slug); - Assert.Equal(expectedReleaseVersion1.Title, publicationViewModel.Releases[0].Title); + Assert.Equal(release2020.Versions[0].Id, publicationViewModel.LatestReleaseId); + + Assert.Equal(release2020.Id, publicationViewModel.Releases[0].Id); + Assert.Equal(release2020.Slug, publicationViewModel.Releases[0].Slug); + Assert.Equal(release2020.Title, publicationViewModel.Releases[0].Title); - Assert.Equal(expectedReleaseVersion2.Id, publicationViewModel.Releases[1].Id); - Assert.Equal(expectedReleaseVersion2.Slug, publicationViewModel.Releases[1].Slug); - Assert.Equal(expectedReleaseVersion2.Title, publicationViewModel.Releases[1].Title); + Assert.Equal(release2022.Id, publicationViewModel.Releases[1].Id); + Assert.Equal(release2022.Slug, publicationViewModel.Releases[1].Slug); + Assert.Equal(release2022.Title, publicationViewModel.Releases[1].Title); - Assert.Equal(3, publicationViewModel.ReleaseSeries.Count); + var releaseSeries = publicationViewModel.ReleaseSeries; - var releaseSeriesItem1 = publicationViewModel.ReleaseSeries[0]; - Assert.False(releaseSeriesItem1.IsLegacyLink); - Assert.Equal(expectedReleaseVersion1.ReleaseId, releaseSeriesItem1.ReleaseId); - Assert.Equal(expectedReleaseVersion1.Title, releaseSeriesItem1.Description); - Assert.Equal(expectedReleaseVersion1.Slug, releaseSeriesItem1.ReleaseSlug); - Assert.Null(releaseSeriesItem1.LegacyLinkUrl); + Assert.Equal(3, releaseSeries.Count); + + Assert.False(releaseSeries[0].IsLegacyLink); + Assert.Equal(release2020.Id, releaseSeries[0].ReleaseId); + Assert.Equal(release2020.Title, releaseSeries[0].Description); + Assert.Equal(release2020.Slug, releaseSeries[0].ReleaseSlug); + Assert.Null(releaseSeries[0].LegacyLinkUrl); // NOTE: 2021 release does exist in the database's publication.ReleaseSeries, but is filtered out // because it's unpublished - var releaseSeriesItem2 = publicationViewModel.ReleaseSeries[1]; - Assert.False(releaseSeriesItem2.IsLegacyLink); - Assert.Equal(expectedReleaseVersion2.ReleaseId, releaseSeriesItem2.ReleaseId); - Assert.Equal(expectedReleaseVersion2.Title, releaseSeriesItem2.Description); - Assert.Equal(expectedReleaseVersion2.Slug, releaseSeriesItem2.ReleaseSlug); - Assert.Null(releaseSeriesItem2.LegacyLinkUrl); + Assert.False(releaseSeries[1].IsLegacyLink); + Assert.Equal(release2022.Id, releaseSeries[1].ReleaseId); + Assert.Equal(release2022.Title, releaseSeries[1].Description); + Assert.Equal(release2022.Slug, releaseSeries[1].ReleaseSlug); + Assert.Null(releaseSeries[1].LegacyLinkUrl); - var releaseSeriesItem3 = publicationViewModel.ReleaseSeries[2]; - Assert.True(releaseSeriesItem3.IsLegacyLink); - Assert.Null(releaseSeriesItem3.ReleaseId); - Assert.Equal(_legacyLinks[0].LegacyLinkDescription, releaseSeriesItem3.Description); - Assert.Null(releaseSeriesItem3.ReleaseSlug); - Assert.Equal(_legacyLinks[0].LegacyLinkUrl, releaseSeriesItem3.LegacyLinkUrl); + Assert.True(releaseSeries[2].IsLegacyLink); + Assert.Null(releaseSeries[2].ReleaseId); + Assert.Equal(legacyLink.LegacyLinkDescription, releaseSeries[2].Description); + Assert.Null(releaseSeries[2].ReleaseSlug); + Assert.Equal(legacyLink.LegacyLinkUrl, releaseSeries[2].LegacyLinkUrl); Assert.Equal(publication.Theme.Id, publicationViewModel.Theme.Id); Assert.Equal(publication.Theme.Slug, publicationViewModel.Theme.Slug); @@ -1932,9 +1934,19 @@ public async Task ListSitemapItems() } } + private List GenerateReleaseSeries(IReadOnlyList releases, params int[] years) + { + return years.Select(year => + { + var release = releases.Single(r => r.Year == year); + return _dataFixture.DefaultReleaseSeriesItem().WithReleaseId(release.Id).Generate(); + }).ToList(); + } + private static PublicationService SetupPublicationService( ContentDbContext? contentDbContext = null, IPublicationRepository? publicationRepository = null, + IReleaseRepository? releaseRepository = null, IReleaseVersionRepository? releaseVersionRepository = null) { contentDbContext ??= InMemoryContentDbContext(); @@ -1943,6 +1955,7 @@ private static PublicationService SetupPublicationService( contentDbContext, new PersistenceHelper(contentDbContext), publicationRepository ?? new PublicationRepository(contentDbContext), + releaseRepository ?? new ReleaseRepository(contentDbContext), releaseVersionRepository ?? new ReleaseVersionRepository(contentDbContext) ); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/PublicationService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/PublicationService.cs index 7792a5f2b9..25554190db 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/PublicationService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/PublicationService.cs @@ -27,17 +27,20 @@ public class PublicationService : IPublicationService private readonly ContentDbContext _contentDbContext; private readonly IPersistenceHelper _contentPersistenceHelper; private readonly IPublicationRepository _publicationRepository; + private readonly IReleaseRepository _releaseRepository; private readonly IReleaseVersionRepository _releaseVersionRepository; public PublicationService( ContentDbContext contentDbContext, IPersistenceHelper contentPersistenceHelper, IPublicationRepository publicationRepository, + IReleaseRepository releaseRepository, IReleaseVersionRepository releaseVersionRepository) { _contentDbContext = contentDbContext; _contentPersistenceHelper = contentPersistenceHelper; _publicationRepository = publicationRepository; + _releaseRepository = releaseRepository; _releaseVersionRepository = releaseVersionRepository; } @@ -81,42 +84,9 @@ public async Task> Get(string pu return new Either(new NotFoundResult()); } - var publishedReleaseVersions = await _releaseVersionRepository.ListLatestPublishedReleaseVersions(publication.Id); - + var publishedReleases = await _releaseRepository.ListPublishedReleases(publication.Id); var isSuperseded = await _publicationRepository.IsSuperseded(publication.Id); - - // Only show legacy links and published releases in ReleaseSeries - var filteredReleaseSeries = publication.ReleaseSeries - .Where(rsi => - rsi.IsLegacyLink - || publishedReleaseVersions - .Any(rv => rsi.ReleaseId == rv.ReleaseId) - ).ToList(); - - var releaseSeriesItemViewModels = filteredReleaseSeries - .Select(rsi => - { - if (rsi.IsLegacyLink) - { - return new ReleaseSeriesItemViewModel - { - Description = rsi.LegacyLinkDescription!, - LegacyLinkUrl = rsi.LegacyLinkUrl, - }; - } - - var latestReleaseVersion = publishedReleaseVersions - .Single(rv => rv.ReleaseId == rsi.ReleaseId); - - return new ReleaseSeriesItemViewModel - { - Description = latestReleaseVersion.Title, - ReleaseId = latestReleaseVersion.ReleaseId, - ReleaseSlug = latestReleaseVersion.Slug, - }; - }).ToList(); - - return BuildPublicationViewModel(publication, publishedReleaseVersions, isSuperseded, releaseSeriesItemViewModels); + return BuildPublicationViewModel(publication, publishedReleases, isSuperseded); }); } @@ -222,23 +192,24 @@ public async Task releaseVersions, - bool isSuperseded, - List releaseSeries) + List releases, + bool isSuperseded) { - var theme = new ThemeViewModel( - publication.Theme.Id, - publication.Theme.Slug, - publication.Theme.Title, - publication.Theme.Summary - ); + var theme = publication.Theme; + + var releaseSeriesItemViewModels = BuildReleaseSeriesItemViewModels(publication, releases); return new PublicationCacheViewModel { Id = publication.Id, Title = publication.Title, Slug = publication.Slug, - Theme = theme, + Theme = new ThemeViewModel( + theme.Id, + theme.Slug, + theme.Title, + theme.Summary + ), Contact = new ContactViewModel(publication.Contact), ExternalMethodology = publication.ExternalMethodology != null ? new ExternalMethodologyViewModel(publication.ExternalMethodology) @@ -253,18 +224,49 @@ private static PublicationCacheViewModel BuildPublicationViewModel( Title = publication.SupersededBy.Title } : null, - Releases = releaseVersions - .Select(releaseVersion => new ReleaseVersionTitleViewModel + Releases = releases + .Select(r => new ReleaseTitleViewModel { - Id = releaseVersion.Id, - Slug = releaseVersion.Slug, - Title = releaseVersion.Title, + Id = r.Id, + Slug = r.Slug, + Title = r.Title }) .ToList(), - ReleaseSeries = releaseSeries, + ReleaseSeries = releaseSeriesItemViewModels }; } + private static List BuildReleaseSeriesItemViewModels( + Publication publication, + List releases) + { + var publishedReleasesById = releases.ToDictionary(r => r.Id); + return publication.ReleaseSeries + // Only include release series items for legacy links and published releases + .Where(rsi => rsi.IsLegacyLink || publishedReleasesById.ContainsKey(rsi.ReleaseId!.Value)) + .Select(rsi => + { + if (rsi.IsLegacyLink) + { + return new ReleaseSeriesItemViewModel + { + Description = rsi.LegacyLinkDescription!, + LegacyLinkUrl = rsi.LegacyLinkUrl + }; + } + + var release = publishedReleasesById[rsi.ReleaseId!.Value]; + + return new ReleaseSeriesItemViewModel + { + Description = release.Title, + ReleaseId = release.Id, + ReleaseSlug = release.Slug + }; + }) + .ToList(); + } + private async Task BuildPublicationTreeTheme(Theme theme) { var publications = await theme.Publications diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/PublicationCacheViewModel.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/PublicationCacheViewModel.cs index 56e581c8ad..fd047c0df7 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/PublicationCacheViewModel.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/PublicationCacheViewModel.cs @@ -16,7 +16,7 @@ public record PublicationCacheViewModel public PublicationSupersededByViewModel? SupersededBy { get; init; } = new(); - public List Releases { get; init; } = []; + public List Releases { get; init; } = []; public List ReleaseSeries { get; init; } = []; diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/PublicationViewModel.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/PublicationViewModel.cs index 4a83b1455b..100a88072f 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/PublicationViewModel.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/PublicationViewModel.cs @@ -16,9 +16,9 @@ public record PublicationViewModel public PublicationSupersededByViewModel? SupersededBy { get; init; } = new(); - public List Releases { get; init; } = new(); + public List Releases { get; init; } = []; - public List ReleaseSeries { get; init; } = new(); + public List ReleaseSeries { get; init; } = []; public ThemeViewModel Theme { get; init; } = null!; diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/ReleaseTitleViewModel.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/ReleaseTitleViewModel.cs new file mode 100644 index 0000000000..862370facf --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/ReleaseTitleViewModel.cs @@ -0,0 +1,10 @@ +namespace GovUk.Education.ExploreEducationStatistics.Content.ViewModels; + +public record ReleaseTitleViewModel +{ + public required Guid Id { get; init; } + + public required string Slug { get; init; } + + public required string Title { get; init; } +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/ReleaseVersionTitleViewModel.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/ReleaseVersionTitleViewModel.cs deleted file mode 100644 index 1a6017b82c..0000000000 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.ViewModels/ReleaseVersionTitleViewModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GovUk.Education.ExploreEducationStatistics.Content.ViewModels; - -public record ReleaseVersionTitleViewModel -{ - public Guid Id { get; set; } - - public string Slug { get; set; } = string.Empty; - - public string Title { get; set; } = string.Empty; -} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/PublisherHostBuilderExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/PublisherHostBuilderExtensions.cs index 3fc0b16ebd..395fe57831 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/PublisherHostBuilderExtensions.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/PublisherHostBuilderExtensions.cs @@ -123,6 +123,7 @@ public static IHostBuilder ConfigurePublisherHostBuilder(this IHostBuilder hostB .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() From 0d82a73c57a8c6b443e96b64dcf2f4b587291ea3 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Mon, 16 Dec 2024 09:33:05 +0000 Subject: [PATCH 11/21] EES-5656 Consolidate methods ListLatestReleaseVersionIds/ListLatestPublishedReleaseVersionIds and ListLatestReleaseVersions/ListLatestPublishedReleaseVersions with new publishedOnly flag. --- .../ReleaseVersionRepositoryTests.cs | 540 ++++++++++-------- .../Interfaces/IReleaseVersionRepository.cs | 24 +- .../Repository/ReleaseVersionRepository.cs | 27 +- .../ReleaseServiceTests.cs | 2 +- .../PublicationService.cs | 3 +- .../ReleaseService.cs | 2 +- 6 files changed, 327 insertions(+), 271 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs index 8cf258824c..d4446fa2cc 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs @@ -370,247 +370,333 @@ public async Task ReleaseVersionDoesNotExist_ReturnsFalse() } } - public class ListLatestPublishedReleaseVersionIdsTests : ReleaseVersionRepositoryTests + public class ListLatestReleaseVersionsTests : ReleaseVersionRepositoryTests { - [Fact] - public async Task Success() - { - var publications = _dataFixture - .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - var result = await repository.ListLatestPublishedReleaseVersionIds(publications[0].Id); - - // Expect the result to contain the highest published version of each release for the specified publication - AssertIdsAreEqualIgnoringOrder( - [ - publications[0].ReleaseVersions[2].Id, - publications[0].ReleaseVersions[5].Id - ], - result); - } - - [Fact] - public async Task PublicationHasNoPublishedReleaseVersions_ReturnsEmpty() - { - var publications = _dataFixture - .DefaultPublication() - // Index 0 has an unpublished release version - // Index 1 has a published release version - .ForIndex(0, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true) - .Generate(1))) - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Empty(await repository.ListLatestPublishedReleaseVersionIds(publications[0].Id)); - } - - [Fact] - public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() + public class AnyPublishedStateTests : ListLatestReleaseVersionIdsTests { - var publications = _dataFixture - .DefaultPublication() - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Empty(await repository.ListLatestPublishedReleaseVersionIds(publications[0].Id)); + [Fact] + public async Task Success() + { + var publications = _dataFixture + .DefaultPublication() + .WithReleases(_ => ListOf( + _dataFixture + .DefaultRelease(publishedVersions: 0, draftVersion: true), + _dataFixture + .DefaultRelease(publishedVersions: 2, draftVersion: true), + _dataFixture + .DefaultRelease(publishedVersions: 2))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + var result = await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: false); + + // Expect the result to contain the highest version of each release for the specified publication + AssertIdsAreEqualIgnoringOrder( + [ + publications[0].ReleaseVersions[0].Id, + publications[0].ReleaseVersions[3].Id, + publications[0].ReleaseVersions[5].Id + ], + result); + } + + [Fact] + public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() + { + var publications = _dataFixture + .DefaultPublication() + // Index 0 has no release versions + // Index 1 has a published release version + .ForIndex(1, + p => p.SetReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty(await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: false)); + } + + [Fact] + public async Task PublicationDoesNotExist_ReturnsEmpty() + { + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1)); + + var contextId = await AddTestData(publication); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty(await repository.ListLatestReleaseVersions(publicationId: Guid.NewGuid(), + publishedOnly: false)); + } } - [Fact] - public async Task PublicationDoesNotExist_ReturnsEmpty() + public class PublishedOnlyTests : ListLatestReleaseVersionsTests { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); - - var contextId = await AddTestData(publication); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Empty(await repository.ListLatestPublishedReleaseVersionIds(Guid.NewGuid())); - } - } - - public class ListLatestPublishedReleaseVersionsTests : ReleaseVersionRepositoryTests - { - [Fact] - public async Task Success() - { - var publications = _dataFixture - .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - var result = await repository.ListLatestPublishedReleaseVersions(publications[0].Id); - - // Expect the result to contain the highest published version of each release for the specified publication - AssertIdsAreEqualIgnoringOrder( - [ - publications[0].ReleaseVersions[2].Id, - publications[0].ReleaseVersions[5].Id - ], - result); - } - - [Fact] - public async Task PublicationHasNoPublishedReleaseVersions_ReturnsEmpty() - { - var publications = _dataFixture - .DefaultPublication() - // Index 0 has an unpublished release version - // Index 1 has a published release version - .ForIndex(0, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true) - .Generate(1))) - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Empty(await repository.ListLatestPublishedReleaseVersions(publications[0].Id)); - } - - [Fact] - public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() - { - var publications = _dataFixture - .DefaultPublication() - // Index 0 has no release versions - // Index 1 has a published release version - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Empty(await repository.ListLatestPublishedReleaseVersions(publications[0].Id)); - } - - [Fact] - public async Task PublicationDoesNotExist_ReturnsEmpty() - { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); - - var contextId = await AddTestData(publication); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Empty(await repository.ListLatestPublishedReleaseVersions(Guid.NewGuid())); + [Fact] + public async Task Success() + { + var publications = _dataFixture + .DefaultPublication() + .WithReleases(_ => ListOf( + _dataFixture + .DefaultRelease(publishedVersions: 0, draftVersion: true), + _dataFixture + .DefaultRelease(publishedVersions: 2, draftVersion: true), + _dataFixture + .DefaultRelease(publishedVersions: 2))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + var result = await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: true); + + // Expect the result to contain the highest published version of each release for the specified publication + AssertIdsAreEqualIgnoringOrder( + [ + publications[0].ReleaseVersions[2].Id, + publications[0].ReleaseVersions[5].Id + ], + result); + } + + [Fact] + public async Task PublicationHasNoPublishedReleaseVersions_ReturnsEmpty() + { + var publications = _dataFixture + .DefaultPublication() + // Index 0 has an unpublished release version + // Index 1 has a published release version + .ForIndex(0, + p => p.SetReleases(_dataFixture + .DefaultRelease(publishedVersions: 0, draftVersion: true) + .Generate(1))) + .ForIndex(1, + p => p.SetReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty(await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: true)); + } + + [Fact] + public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() + { + var publications = _dataFixture + .DefaultPublication() + // Index 0 has no release versions + // Index 1 has a published release version + .ForIndex(1, + p => p.SetReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty(await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: true)); + } + + [Fact] + public async Task PublicationDoesNotExist_ReturnsEmpty() + { + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1)); + + var contextId = await AddTestData(publication); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty( + await repository.ListLatestReleaseVersions(publicationId: Guid.NewGuid(), publishedOnly: true)); + } } } public class ListLatestReleaseVersionIdsTests : ReleaseVersionRepositoryTests { - [Fact] - public async Task Success() + public class AnyPublishedStateTests : ListLatestReleaseVersionIdsTests { - var publications = _dataFixture - .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - var result = await repository.ListLatestReleaseVersionIds(publications[0].Id); - - // Expect the result to contain the highest version of each release for the specified publication - AssertIdsAreEqualIgnoringOrder( - [ - publications[0].ReleaseVersions[0].Id, - publications[0].ReleaseVersions[3].Id, - publications[0].ReleaseVersions[5].Id - ], - result); + [Fact] + public async Task Success() + { + var publications = _dataFixture + .DefaultPublication() + .WithReleases(_ => ListOf( + _dataFixture + .DefaultRelease(publishedVersions: 0, draftVersion: true), + _dataFixture + .DefaultRelease(publishedVersions: 2, draftVersion: true), + _dataFixture + .DefaultRelease(publishedVersions: 2))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + var result = await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: false); + + // Expect the result to contain the highest version of each release for the specified publication + AssertIdsAreEqualIgnoringOrder( + [ + publications[0].ReleaseVersions[0].Id, + publications[0].ReleaseVersions[3].Id, + publications[0].ReleaseVersions[5].Id + ], + result); + } + + [Fact] + public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() + { + var publications = _dataFixture + .DefaultPublication() + // Index 0 has no release versions + // Index 1 has a published release version + .ForIndex(1, + p => p.SetReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty(await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: false)); + } + + [Fact] + public async Task PublicationDoesNotExist_ReturnsEmpty() + { + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1)); + + var contextId = await AddTestData(publication); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty(await repository.ListLatestReleaseVersionIds(publicationId: Guid.NewGuid(), + publishedOnly: false)); + } } - [Fact] - public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() + public class PublishedOnlyTrueTests : ListLatestReleaseVersionIdsTests { - var publications = _dataFixture - .DefaultPublication() - // Index 0 has no release versions - // Index 1 has a published release version - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Empty(await repository.ListLatestReleaseVersionIds(publications[0].Id)); - } - - [Fact] - public async Task PublicationDoesNotExist_ReturnsEmpty() - { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); - - var contextId = await AddTestData(publication); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Empty(await repository.ListLatestReleaseVersionIds(Guid.NewGuid())); + [Fact] + public async Task Success() + { + var publications = _dataFixture + .DefaultPublication() + .WithReleases(_ => ListOf( + _dataFixture + .DefaultRelease(publishedVersions: 0, draftVersion: true), + _dataFixture + .DefaultRelease(publishedVersions: 2, draftVersion: true), + _dataFixture + .DefaultRelease(publishedVersions: 2))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + var result = await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: true); + + // Expect the result to contain the highest published version of each release for the specified publication + AssertIdsAreEqualIgnoringOrder( + [ + publications[0].ReleaseVersions[2].Id, + publications[0].ReleaseVersions[5].Id + ], + result); + } + + [Fact] + public async Task PublicationHasNoPublishedReleaseVersions_ReturnsEmpty() + { + var publications = _dataFixture + .DefaultPublication() + // Index 0 has an unpublished release version + // Index 1 has a published release version + .ForIndex(0, + p => p.SetReleases(_dataFixture + .DefaultRelease(publishedVersions: 0, draftVersion: true) + .Generate(1))) + .ForIndex(1, + p => p.SetReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty(await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: true)); + } + + [Fact] + public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() + { + var publications = _dataFixture + .DefaultPublication() + .ForIndex(1, + p => p.SetReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1))) + .GenerateList(2); + + var contextId = await AddTestData(publications); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty(await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: true)); + } + + [Fact] + public async Task PublicationDoesNotExist_ReturnsEmpty() + { + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases(_dataFixture + .DefaultRelease(publishedVersions: 1) + .Generate(1)); + + var contextId = await AddTestData(publication); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + Assert.Empty( + await repository.ListLatestReleaseVersionIds(publicationId: Guid.NewGuid(), publishedOnly: true)); + } } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs index 7333c2e4be..8cc5dd3d21 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs @@ -64,43 +64,27 @@ Task IsLatestReleaseVersion( Guid releaseVersionId, CancellationToken cancellationToken = default); - /// - /// Retrieves the latest published release version id's associated with a publication. - /// - /// The unique identifier of the publication. - /// A to observe while waiting for the task to complete. - /// A collection of the latest published version id's of all releases associated with the publication. - Task> ListLatestPublishedReleaseVersionIds( - Guid publicationId, - CancellationToken cancellationToken = default); - - /// - /// Retrieves the latest published versions of all releases associated with a publication in reverse chronological order. - /// - /// The unique identifier of the publication. - /// A to observe while waiting for the task to complete. - /// A collection of the latest published versions of all releases associated with the publication. - Task> ListLatestPublishedReleaseVersions( - Guid publicationId, - CancellationToken cancellationToken = default); - /// /// Retrieves the latest version id's of all releases associated with a publication. /// /// The unique identifier of the publication. + /// Flag to only include published release version id's. /// A to observe while waiting for the task to complete. /// A collection of the latest version id's of all releases associated with the publication. Task> ListLatestReleaseVersionIds( Guid publicationId, + bool publishedOnly = false, CancellationToken cancellationToken = default); /// /// Retrieves the latest versions of all releases associated with a given publication in reverse chronological order. /// /// The unique identifier of the publication. + /// Flag to only include published release versions. /// A to observe while waiting for the task to complete. /// A collection of the latest version id's of all releases associated with the publication. Task> ListLatestReleaseVersions( Guid publicationId, + bool publishedOnly = false, CancellationToken cancellationToken = default); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs index 7d7071ae15..6a167df5e3 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs @@ -103,39 +103,24 @@ public async Task IsLatestReleaseVersion( cancellationToken: cancellationToken); } - public async Task> ListLatestPublishedReleaseVersions( - Guid publicationId, - CancellationToken cancellationToken = default) - { - return (await _contentDbContext.ReleaseVersions.LatestReleaseVersions(publicationId, publishedOnly: true) - .ToListAsync(cancellationToken: cancellationToken)) - .OrderByReverseChronologicalOrder() - .ToList(); - } - - public async Task> ListLatestPublishedReleaseVersionIds( - Guid publicationId, - CancellationToken cancellationToken = default) - { - return await _contentDbContext.ReleaseVersions.LatestReleaseVersions(publicationId, publishedOnly: true) - .Select(rv => rv.Id) - .ToListAsync(cancellationToken: cancellationToken); - } - public async Task> ListLatestReleaseVersionIds( Guid publicationId, + bool publishedOnly = false, CancellationToken cancellationToken = default) { - return await _contentDbContext.ReleaseVersions.LatestReleaseVersions(publicationId) + return await _contentDbContext.ReleaseVersions + .LatestReleaseVersions(publicationId, publishedOnly: publishedOnly) .Select(rv => rv.Id) .ToListAsync(cancellationToken: cancellationToken); } public async Task> ListLatestReleaseVersions( Guid publicationId, + bool publishedOnly = false, CancellationToken cancellationToken = default) { - return (await _contentDbContext.ReleaseVersions.LatestReleaseVersions(publicationId) + return (await _contentDbContext.ReleaseVersions + .LatestReleaseVersions(publicationId, publishedOnly: publishedOnly) .ToListAsync(cancellationToken: cancellationToken)) .OrderByReverseChronologicalOrder() .ToList(); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/ReleaseServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/ReleaseServiceTests.cs index d54156bf6b..2ddc439be2 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/ReleaseServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/ReleaseServiceTests.cs @@ -745,7 +745,7 @@ public async Task List() Assert.Equal(2, releases.Count); - // Ordered from most newest to oldest + // Ordered from newest to oldest Assert.Equal(release2Version1.Id, releases[0].Id); Assert.Equal(release2Version1.Title, releases[0].Title); Assert.Equal(release2Version1.Slug, releases[0].Slug); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/PublicationService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/PublicationService.cs index 25554190db..8548db4a29 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/PublicationService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/PublicationService.cs @@ -294,7 +294,8 @@ private async Task BuildPublicationTreePubl var latestReleaseHasData = latestPublishedReleaseVersionId.HasValue && await HasAnyDataFiles(latestPublishedReleaseVersionId.Value); - var publishedReleaseVersionIds = await _releaseVersionRepository.ListLatestPublishedReleaseVersionIds(publication.Id); + var publishedReleaseVersionIds = + await _releaseVersionRepository.ListLatestReleaseVersionIds(publication.Id, publishedOnly: true); var anyLiveReleaseHasData = await publishedReleaseVersionIds .ToAsyncEnumerable() .AnyAwaitAsync(async id => await HasAnyDataFiles(id)); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs index 97d0b74d26..076fa2592b 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs @@ -115,7 +115,7 @@ public async Task>> List(stri .OnSuccess(async publication => { var publishedReleaseVersions = - await _releaseVersionRepository.ListLatestPublishedReleaseVersions(publication.Id); + await _releaseVersionRepository.ListLatestReleaseVersions(publication.Id, publishedOnly: true); return publishedReleaseVersions .Select(releaseVersion => new ReleaseSummaryViewModel(releaseVersion, latestPublishedRelease: releaseVersion.Id == publication.LatestPublishedReleaseVersionId)) From cbbeb43a47b9b76ad0c0f2b1d7f8c063c0c4c54f Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Mon, 16 Dec 2024 11:14:51 +0000 Subject: [PATCH 12/21] EES-5656 Change ReleaseService to not use method GetLatestPublishedReleaseVersion(Guid publicationId) --- .../ReleaseServiceTests.cs | 2 - .../ReleaseService.cs | 47 +++++++++---------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/ReleaseServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/ReleaseServiceTests.cs index 2ddc439be2..bc7f6e58e3 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/ReleaseServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services.Tests/ReleaseServiceTests.cs @@ -7,7 +7,6 @@ using GovUk.Education.ExploreEducationStatistics.Common.Services.Interfaces.Security; using GovUk.Education.ExploreEducationStatistics.Common.Tests.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Tests.Fixtures; -using GovUk.Education.ExploreEducationStatistics.Common.Utils; using GovUk.Education.ExploreEducationStatistics.Content.Model; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository; @@ -851,7 +850,6 @@ private static ReleaseService SetupReleaseService( { return new( contentDbContext, - new PersistenceHelper(contentDbContext), releaseFileRepository ?? new ReleaseFileRepository(contentDbContext), releaseVersionRepository ?? new ReleaseVersionRepository(contentDbContext), userService ?? AlwaysTrueUserService().Object, diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs index 076fa2592b..68bc9328b6 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs @@ -8,7 +8,6 @@ using GovUk.Education.ExploreEducationStatistics.Common.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Model; using GovUk.Education.ExploreEducationStatistics.Common.Services.Interfaces.Security; -using GovUk.Education.ExploreEducationStatistics.Common.Utils; using GovUk.Education.ExploreEducationStatistics.Content.Model; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; using GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; @@ -25,7 +24,6 @@ namespace GovUk.Education.ExploreEducationStatistics.Content.Services public class ReleaseService : IReleaseService { private readonly ContentDbContext _contentDbContext; - private readonly IPersistenceHelper _persistenceHelper; private readonly IReleaseFileRepository _releaseFileRepository; private readonly IReleaseVersionRepository _releaseVersionRepository; private readonly IUserService _userService; @@ -36,42 +34,41 @@ public class ReleaseService : IReleaseService public ReleaseService( ContentDbContext contentDbContext, - IPersistenceHelper persistenceHelper, IReleaseFileRepository releaseFileRepository, IReleaseVersionRepository releaseVersionRepository, IUserService userService, IMapper mapper) { _contentDbContext = contentDbContext; - _persistenceHelper = persistenceHelper; _releaseFileRepository = releaseFileRepository; _releaseVersionRepository = releaseVersionRepository; _userService = userService; _mapper = mapper; } - public async Task> GetRelease(string publicationSlug, + public async Task> GetRelease( + string publicationSlug, string? releaseSlug = null) { - return await _persistenceHelper.CheckEntityExists(q => - q.Where(p => p.Slug == publicationSlug)) + return await _contentDbContext.Publications + .SingleOrNotFoundAsync(p => p.Slug == publicationSlug) .OnSuccess(async publication => { - // If no release is requested get the latest published release version - if (releaseSlug == null) - { - return await _releaseVersionRepository.GetLatestPublishedReleaseVersion(publication.Id) - .OrNotFound(); - } - - // Otherwise get the latest published version of the requested release - return await _releaseVersionRepository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug) - .OrNotFound(); - }) - .OnSuccess(releaseVersion => GetRelease(releaseVersion.Id)); + // If no release is requested use the publication's latest published release version, + // otherwise use the latest published version of the requested release + var latestReleaseVersionId = releaseSlug == null + ? publication.LatestPublishedReleaseVersionId + : (await _releaseVersionRepository.GetLatestPublishedReleaseVersion(publication.Id, + releaseSlug))?.Id; + + return latestReleaseVersionId.HasValue + ? await GetRelease(latestReleaseVersionId.Value) + : new NotFoundResult(); + }); } - public async Task> GetRelease(Guid releaseVersionId, + public async Task> GetRelease( + Guid releaseVersionId, DateTime? expectedPublishDate = null) { // Note this method is allowed to return a view model for an unpublished release version so that Publisher @@ -107,10 +104,8 @@ public async Task> GetRelease(Guid r public async Task>> List(string publicationSlug) { - return await _persistenceHelper.CheckEntityExists( - q => q - .Where(p => p.Slug == publicationSlug) - ) + return await _contentDbContext.Publications + .SingleOrNotFoundAsync(p => p.Slug == publicationSlug) .OnSuccess(_userService.CheckCanViewPublication) .OnSuccess(async publication => { @@ -140,9 +135,9 @@ private static void FilterContentBlock(IContentBlockViewModel block) private async Task> GetDownloadFiles(ReleaseVersion releaseVersion) { var files = await _releaseFileRepository.GetByFileType( - releaseVersion.Id, + releaseVersion.Id, types: [FileType.Ancillary, FileType.Data]); - + return files .Select(rf => rf.ToPublicFileInfo()) .OrderBy(file => file.Name) From 65a1bb8fab066accd152b7816258393cf5797595 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Mon, 16 Dec 2024 15:18:36 +0000 Subject: [PATCH 13/21] EES-5656 Change PermalinkService to not use method GetLatestPublishedReleaseVersion(Guid publicationId) --- .../Services/PermalinkServiceTests.cs | 479 ++++++------------ ...tePermalink_WithReleaseVersionId_csv.snap} | 0 ...ePermalink_WithReleaseVersionId_json.snap} | 0 ...ermalink_WithoutReleaseVersionId_csv.snap} | 0 ...rmalink_WithoutReleaseVersionId_json.snap} | 0 .../Services/PermalinkService.cs | 28 +- 6 files changed, 175 insertions(+), 332 deletions(-) rename src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/{PermalinkServiceTests.CreatePermalink_WithReleaseId_csv.snap => PermalinkServiceTests.CreatePermalink_WithReleaseVersionId_csv.snap} (100%) rename src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/{PermalinkServiceTests.CreatePermalink_WithReleaseId_json.snap => PermalinkServiceTests.CreatePermalink_WithReleaseVersionId_json.snap} (100%) rename src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/{PermalinkServiceTests.CreatePermalink_WithoutReleaseId_csv.snap => PermalinkServiceTests.CreatePermalink_WithoutReleaseVersionId_csv.snap} (100%) rename src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/{PermalinkServiceTests.CreatePermalink_WithoutReleaseId_json.snap => PermalinkServiceTests.CreatePermalink_WithoutReleaseVersionId_json.snap} (100%) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/PermalinkServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/PermalinkServiceTests.cs index b3ca52e070..e55fa71c11 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/PermalinkServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/PermalinkServiceTests.cs @@ -1,4 +1,11 @@ #nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Model; @@ -12,11 +19,13 @@ using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository; using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; using GovUk.Education.ExploreEducationStatistics.Data.Api.Models; using GovUk.Education.ExploreEducationStatistics.Data.Api.Requests; using GovUk.Education.ExploreEducationStatistics.Data.Api.Services; using GovUk.Education.ExploreEducationStatistics.Data.Api.Services.Interfaces; using GovUk.Education.ExploreEducationStatistics.Data.Api.ViewModels; +using GovUk.Education.ExploreEducationStatistics.Data.Model; using GovUk.Education.ExploreEducationStatistics.Data.Model.Repository.Interfaces; using GovUk.Education.ExploreEducationStatistics.Data.Model.Tests.Fixtures; using GovUk.Education.ExploreEducationStatistics.Data.Model.Utils; @@ -29,18 +38,12 @@ using Newtonsoft.Json.Linq; using Snapshooter; using Snapshooter.Xunit; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Xunit; using static GovUk.Education.ExploreEducationStatistics.Common.Model.TimeIdentifier; using static GovUk.Education.ExploreEducationStatistics.Common.Services.CollectionUtils; using static GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Utils.ContentDbUtils; using File = GovUk.Education.ExploreEducationStatistics.Content.Model.File; +using ReleaseVersion = GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion; namespace GovUk.Education.ExploreEducationStatistics.Data.Api.Tests.Services { @@ -55,8 +58,6 @@ public class PermalinkServiceTests } }; - private readonly Guid _publicationId = Guid.NewGuid(); - private readonly PermalinkTableViewModel _frontendTableResponse = new() { Caption = "Admission Numbers for 'Sample publication' in North East between 2022 and 2023", @@ -70,6 +71,10 @@ public class PermalinkServiceTests [Fact] public async Task CreatePermalink_LatestPublishedReleaseForSubjectNotFound() { + Publication publication = _fixture.DefaultPublication() + .WithReleases([_fixture.DefaultRelease(publishedVersions: 0, draftVersion: true)]) + .WithTheme(_fixture.DefaultTheme()); + var request = new PermalinkCreateRequest { Query = @@ -78,57 +83,64 @@ public async Task CreatePermalink_LatestPublishedReleaseForSubjectNotFound() } }; - var releaseVersionRepository = new Mock(MockBehavior.Strict); var subjectRepository = new Mock(MockBehavior.Strict); - releaseVersionRepository - .Setup(s => s.GetLatestPublishedReleaseVersion(_publicationId, default)) - .ReturnsAsync((ReleaseVersion?)null); - subjectRepository .Setup(s => s.FindPublicationIdForSubject(request.Query.SubjectId, default)) - .ReturnsAsync(_publicationId); + .ReturnsAsync(publication.Id); - var service = BuildService(releaseVersionRepository: releaseVersionRepository.Object, - subjectRepository: subjectRepository.Object); + var contentDbContextId = Guid.NewGuid().ToString(); + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) + { + contentDbContext.Publications.Add(publication); + await contentDbContext.SaveChangesAsync(); + } + + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) + { + var service = BuildService( + contentDbContext: contentDbContext, + subjectRepository: subjectRepository.Object + ); - var result = await service.CreatePermalink(request); + var result = await service.CreatePermalink(request); - MockUtils.VerifyAllMocks( - releaseVersionRepository, - subjectRepository); + MockUtils.VerifyAllMocks(subjectRepository); - result.AssertNotFound(); + result.AssertNotFound(); + } } [Fact] - public async Task CreatePermalink_WithoutReleaseId() + public async Task CreatePermalink_WithoutReleaseVersionId() { - var subject = _fixture + Subject subject = _fixture .DefaultSubject() .WithFilters(_fixture.DefaultFilter() - .ForIndex(0, s => - s.SetGroupCsvColumn("filter_0_grouping") - .SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 1) - .ForInstance(s => s.Set( - fg => fg.Label, - (_, _, context) => $"Filter group {context.FixtureTypeIndex}")) - .Generate(2))) - .ForIndex(1, s => - s.SetGroupCsvColumn("filter_1_grouping") - .SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 1) - .ForInstance(s => s.Set( - fg => fg.Label, - (_, _, context) => $"Filter group {context.FixtureTypeIndex}")) - .Generate(2))) - .ForIndex(2, s => - s.SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 2) - .Generate(1))) + .ForIndex(0, + s => + s.SetGroupCsvColumn("filter_0_grouping") + .SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 1) + .ForInstance(s => s.Set( + fg => fg.Label, + (_, _, context) => $"Filter group {context.FixtureTypeIndex}")) + .Generate(2))) + .ForIndex(1, + s => + s.SetGroupCsvColumn("filter_1_grouping") + .SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 1) + .ForInstance(s => s.Set( + fg => fg.Label, + (_, _, context) => $"Filter group {context.FixtureTypeIndex}")) + .Generate(2))) + .ForIndex(2, + s => + s.SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 2) + .Generate(1))) .GenerateList()) .WithIndicatorGroups(_fixture .DefaultIndicatorGroup(indicatorCount: 1) - .Generate(3)) - .Generate(); + .Generate(3)); var indicators = subject .IndicatorGroups @@ -201,17 +213,11 @@ public async Task CreatePermalink_WithoutReleaseId() Filters = FiltersMetaViewModelBuilder.BuildFilters(subject.Filters), Indicators = IndicatorsMetaViewModelBuilder.BuildIndicators(indicators), Footnotes = footnoteViewModels, - TimePeriodRange = new List - { - new(2022, AcademicYear) - { - Label = "2022/23" - }, - new(2023, AcademicYear) - { - Label = "2023/24" - } - } + TimePeriodRange = + [ + new TimePeriodMetaViewModel(2022, AcademicYear) { Label = "2022/23" }, + new TimePeriodMetaViewModel(2023, AcademicYear) { Label = "2023/24" } + ] }, Results = observations .Select(o => @@ -219,20 +225,13 @@ public async Task CreatePermalink_WithoutReleaseId() .ToList() }; - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - }; + Publication publication = _fixture.DefaultPublication() + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]) + .WithTheme(_fixture.DefaultTheme()); - var publication = new Publication - { - Id = _publicationId, - LatestPublishedReleaseVersion = releaseVersion - }; + var releaseVersion = publication.Releases.Single().Versions.Single(); + + var releaseDataFile = ReleaseDataFile(releaseVersion, subject.Id); var csvMeta = new PermalinkCsvMetaViewModel { @@ -242,8 +241,8 @@ public async Task CreatePermalink_WithoutReleaseId() .Select(i => new IndicatorCsvMetaViewModel(i)) .ToDictionary(i => i.Name), Locations = locations.ToDictionary(l => l.Id, l => l.GetCsvValues()), - Headers = new List - { + Headers = + [ "time_period", "time_identifier", "geographic_level", @@ -262,7 +261,7 @@ public async Task CreatePermalink_WithoutReleaseId() indicators[0].Name, indicators[1].Name, indicators[2].Name - } + ] }; var request = new PermalinkCreateRequest @@ -349,17 +348,11 @@ public async Task CreatePermalink_WithoutReleaseId() It.IsAny())) .ReturnsAsync(csvMeta); - var releaseVersionRepository = new Mock(MockBehavior.Strict); - - releaseVersionRepository - .Setup(s => s.GetLatestPublishedReleaseVersion(_publicationId, default)) - .ReturnsAsync(releaseVersion); - var subjectRepository = new Mock(MockBehavior.Strict); subjectRepository .Setup(s => s.FindPublicationIdForSubject(subject.Id, default)) - .ReturnsAsync(_publicationId); + .ReturnsAsync(publication.Id); var tableBuilderService = new Mock(MockBehavior.Strict); @@ -375,8 +368,7 @@ public async Task CreatePermalink_WithoutReleaseId() await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { contentDbContext.Publications.Add(publication); - contentDbContext.ReleaseVersions.Add(releaseVersion); - contentDbContext.ReleaseFiles.AddRange(ReleaseDataFile(releaseVersion, subject.Id)); + contentDbContext.ReleaseFiles.Add(releaseDataFile); await contentDbContext.SaveChangesAsync(); } @@ -388,7 +380,6 @@ public async Task CreatePermalink_WithoutReleaseId() publicBlobStorageService: publicBlobStorageService.Object, frontendService: frontendService.Object, permalinkCsvMetaService: permalinkCsvMetaService.Object, - releaseVersionRepository: releaseVersionRepository.Object, subjectRepository: subjectRepository.Object, tableBuilderService: tableBuilderService.Object); @@ -398,7 +389,6 @@ public async Task CreatePermalink_WithoutReleaseId() publicBlobStorageService, frontendService, permalinkCsvMetaService, - releaseVersionRepository, subjectRepository, tableBuilderService); @@ -439,33 +429,35 @@ public async Task CreatePermalink_WithoutReleaseId() } [Fact] - public async Task CreatePermalink_WithReleaseId() + public async Task CreatePermalink_WithReleaseVersionId() { - var subject = _fixture + Subject subject = _fixture .DefaultSubject() .WithFilters(_fixture.DefaultFilter() - .ForIndex(0, s => - s.SetGroupCsvColumn("filter_0_grouping") - .SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 1) - .ForInstance(s => s.Set( - fg => fg.Label, - (_, _, context) => $"Filter group {context.FixtureTypeIndex}")) - .Generate(2))) - .ForIndex(1, s => - s.SetGroupCsvColumn("filter_1_grouping") - .SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 1) - .ForInstance(s => s.Set( - fg => fg.Label, - (_, _, context) => $"Filter group {context.FixtureTypeIndex}")) - .Generate(2))) - .ForIndex(2, s => - s.SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 2) - .Generate(1))) + .ForIndex(0, + s => + s.SetGroupCsvColumn("filter_0_grouping") + .SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 1) + .ForInstance(s => s.Set( + fg => fg.Label, + (_, _, context) => $"Filter group {context.FixtureTypeIndex}")) + .Generate(2))) + .ForIndex(1, + s => + s.SetGroupCsvColumn("filter_1_grouping") + .SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 1) + .ForInstance(s => s.Set( + fg => fg.Label, + (_, _, context) => $"Filter group {context.FixtureTypeIndex}")) + .Generate(2))) + .ForIndex(2, + s => + s.SetFilterGroups(_fixture.DefaultFilterGroup(filterItemCount: 2) + .Generate(1))) .GenerateList()) .WithIndicatorGroups(_fixture .DefaultIndicatorGroup(indicatorCount: 1) - .Generate(3)) - .Generate(); + .Generate(3)); var indicators = subject .IndicatorGroups @@ -537,17 +529,11 @@ public async Task CreatePermalink_WithReleaseId() Filters = FiltersMetaViewModelBuilder.BuildFilters(subject.Filters), Indicators = IndicatorsMetaViewModelBuilder.BuildIndicators(indicators), Footnotes = footnoteViewModels, - TimePeriodRange = new List - { - new(2022, AcademicYear) - { - Label = "2022/23" - }, - new(2023, AcademicYear) - { - Label = "2023/24" - } - } + TimePeriodRange = + [ + new TimePeriodMetaViewModel(2022, AcademicYear) { Label = "2022/23" }, + new TimePeriodMetaViewModel(2023, AcademicYear) { Label = "2023/24" } + ] }, Results = observations .Select(o => @@ -555,20 +541,13 @@ public async Task CreatePermalink_WithReleaseId() .ToList() }; - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - }; + Publication publication = _fixture.DefaultPublication() + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]) + .WithTheme(_fixture.DefaultTheme()); - var publication = new Publication - { - Id = _publicationId, - LatestPublishedReleaseVersion = releaseVersion - }; + var releaseVersion = publication.Releases.Single().Versions.Single(); + + var releaseDataFile = ReleaseDataFile(releaseVersion, subject.Id); var csvMeta = new PermalinkCsvMetaViewModel { @@ -578,8 +557,8 @@ public async Task CreatePermalink_WithReleaseId() .Select(i => new IndicatorCsvMetaViewModel(i)) .ToDictionary(i => i.Name), Locations = locations.ToDictionary(l => l.Id, l => l.GetCsvValues()), - Headers = new List - { + Headers = + [ "time_period", "time_identifier", "geographic_level", @@ -598,7 +577,7 @@ public async Task CreatePermalink_WithReleaseId() indicators[0].Name, indicators[1].Name, indicators[2].Name - } + ] }; var request = new PermalinkCreateRequest @@ -699,8 +678,7 @@ public async Task CreatePermalink_WithReleaseId() await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { contentDbContext.Publications.Add(publication); - contentDbContext.ReleaseVersions.Add(releaseVersion); - contentDbContext.ReleaseFiles.AddRange(ReleaseDataFile(releaseVersion, subject.Id)); + contentDbContext.ReleaseFiles.Add(releaseDataFile); await contentDbContext.SaveChangesAsync(); } @@ -761,20 +739,11 @@ public async Task CreatePermalink_WithReleaseId() [Fact] public async Task GetPermalink() { - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - ReleaseName = "2000", - PublicationId = _publicationId, - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - }; + Publication publication = _fixture.DefaultPublication() + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]) + .WithTheme(_fixture.DefaultTheme()); - var publication = new Publication - { - Id = _publicationId, - LatestPublishedReleaseVersion = releaseVersion - }; + var releaseVersion = publication.Releases.Single().Versions.Single(); var permalink = new Permalink { @@ -785,6 +754,8 @@ public async Task GetPermalink() SubjectId = Guid.NewGuid() }; + var releaseDataFile = ReleaseDataFile(releaseVersion, permalink.SubjectId); + var table = _frontendTableResponse with { Footnotes = FootnotesViewModelBuilder.BuildFootnotes(_fixture @@ -797,8 +768,7 @@ public async Task GetPermalink() { contentDbContext.Permalinks.Add(permalink); contentDbContext.Publications.Add(publication); - contentDbContext.ReleaseVersions.Add(releaseVersion); - contentDbContext.ReleaseFiles.AddRange(ReleaseDataFile(releaseVersion, permalink.SubjectId)); + contentDbContext.ReleaseFiles.Add(releaseDataFile); await contentDbContext.SaveChangesAsync(); } @@ -873,7 +843,7 @@ public async Task GetPermalink_BlobNotFound() } [Fact] - public async Task GetPermalink_ReleaseWithSubjectNotFound() + public async Task GetPermalink_ReleaseVersionWithSubjectNotFound() { var permalink = new Permalink { @@ -912,47 +882,29 @@ public async Task GetPermalink_ReleaseWithSubjectNotFound() [Fact] public async Task GetPermalink_SubjectIsForMultipleReleaseVersions() { - var previousVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - PreviousVersionId = null - }; - - var latestVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - PreviousVersionId = previousVersion.Id - }; + Publication publication = _fixture.DefaultPublication() + .WithReleases([_fixture.DefaultRelease(publishedVersions: 2)]) + .WithTheme(_fixture.DefaultTheme()); - var publication = new Publication - { - Id = _publicationId, - LatestPublishedReleaseVersion = latestVersion - }; + var (previousReleaseVersion, latestReleaseVersion) = publication.Releases.Single().Versions.ToTuple2(); var permalink = new Permalink { SubjectId = Guid.NewGuid() }; + ReleaseFile[] releaseDataFiles = + [ + ReleaseDataFile(previousReleaseVersion, permalink.SubjectId), + ReleaseDataFile(latestReleaseVersion, permalink.SubjectId) + ]; + var contentDbContextId = Guid.NewGuid().ToString(); await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { contentDbContext.Permalinks.Add(permalink); contentDbContext.Publications.Add(publication); - contentDbContext.ReleaseVersions.AddRange(previousVersion, latestVersion); - contentDbContext.ReleaseFiles.AddRange( - ReleaseDataFile(previousVersion, permalink.SubjectId), - ReleaseDataFile(latestVersion, permalink.SubjectId) - ); + contentDbContext.ReleaseFiles.AddRange(releaseDataFiles); await contentDbContext.SaveChangesAsync(); } @@ -980,109 +932,37 @@ public async Task GetPermalink_SubjectIsForMultipleReleaseVersions() } [Fact] - public async Task GetPermalink_SubjectIsNotForLatestRelease_OlderByYear() + public async Task GetPermalink_SubjectIsNotForPublicationsLatestRelease() { - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - }; - - var latestReleaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2001", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - }; - - var publication = new Publication - { - Id = _publicationId, - LatestPublishedReleaseVersion = latestReleaseVersion - }; + Publication publication = _fixture + .DefaultPublication() + .WithReleases([ + _fixture + .DefaultRelease(publishedVersions: 1, year: 2020), + _fixture + .DefaultRelease(publishedVersions: 1, year: 2021) + ]) + .WithTheme(_fixture.DefaultTheme()); + + var release2020 = publication.Releases.Single(r => r.Year == 2020); + var release2021 = publication.Releases.Single(r => r.Year == 2021); + + // Check the publication's latest published release version in the generated test data setup + Assert.Equal(release2021.Versions[0].Id, publication.LatestPublishedReleaseVersionId); var permalink = new Permalink { SubjectId = Guid.NewGuid() }; - var contentDbContextId = Guid.NewGuid().ToString(); - await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) - { - contentDbContext.Permalinks.Add(permalink); - contentDbContext.Publications.Add(publication); - contentDbContext.ReleaseVersions.AddRange(releaseVersion, latestReleaseVersion); - contentDbContext.ReleaseFiles.AddRange(ReleaseDataFile(releaseVersion, permalink.SubjectId)); - - await contentDbContext.SaveChangesAsync(); - } - - var publicBlobStorageService = new Mock(MockBehavior.Strict); - - publicBlobStorageService.SetupGetDeserializedJson( - container: BlobContainers.PermalinkSnapshots, - path: $"{permalink.Id}.json.zst", - value: new PermalinkTableViewModel()); - - await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) - { - var service = BuildService( - contentDbContext: contentDbContext, - publicBlobStorageService: publicBlobStorageService.Object); - - var result = (await service.GetPermalink(permalink.Id)).AssertRight(); - - MockUtils.VerifyAllMocks(publicBlobStorageService); - - Assert.Equal(permalink.Id, result.Id); - Assert.Equal(PermalinkStatus.NotForLatestRelease, result.Status); - } - } - - [Fact] - public async Task GetPermalink_SubjectIsNotForLatestRelease_OlderByTimePeriod() - { - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = January, - Published = DateTime.UtcNow, - }; - - var latestReleaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = February, - Published = DateTime.UtcNow, - }; - - var publication = new Publication - { - Id = _publicationId, - LatestPublishedReleaseVersion = latestReleaseVersion - }; - - var permalink = new Permalink - { - SubjectId = Guid.NewGuid() - }; + var releaseDataFile = ReleaseDataFile(release2020.Versions[0], permalink.SubjectId); var contentDbContextId = Guid.NewGuid().ToString(); await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { contentDbContext.Permalinks.Add(permalink); contentDbContext.Publications.Add(publication); - contentDbContext.ReleaseVersions.AddRange(releaseVersion, latestReleaseVersion); - contentDbContext.ReleaseFiles.AddRange(ReleaseDataFile(releaseVersion, permalink.SubjectId)); + contentDbContext.ReleaseFiles.Add(releaseDataFile); await contentDbContext.SaveChangesAsync(); } @@ -1112,45 +992,24 @@ public async Task GetPermalink_SubjectIsNotForLatestRelease_OlderByTimePeriod() [Fact] public async Task GetPermalink_SubjectIsNotForLatestReleaseVersion() { - var previousVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - PreviousVersionId = null - }; + Publication publication = _fixture.DefaultPublication() + .WithReleases([_fixture.DefaultRelease(publishedVersions: 2)]); - var latestVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - PreviousVersionId = previousVersion.Id - }; - - var publication = new Publication - { - Id = _publicationId, - LatestPublishedReleaseVersion = latestVersion - }; + var previousReleaseVersion = publication.Releases.Single().Versions[0]; var permalink = new Permalink { SubjectId = Guid.NewGuid() }; + var releaseDataFile = ReleaseDataFile(previousReleaseVersion, permalink.SubjectId); + var contentDbContextId = Guid.NewGuid().ToString(); await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { contentDbContext.Permalinks.Add(permalink); contentDbContext.Publications.Add(publication); - contentDbContext.ReleaseVersions.AddRange(previousVersion, latestVersion); - contentDbContext.ReleaseFiles.AddRange(ReleaseDataFile(previousVersion, - permalink.SubjectId)); + contentDbContext.ReleaseFiles.Add(releaseDataFile); await contentDbContext.SaveChangesAsync(); } @@ -1180,37 +1039,27 @@ public async Task GetPermalink_SubjectIsNotForLatestReleaseVersion() [Fact] public async Task GetPermalink_SubjectIsFromSupersededPublication() { - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - PublicationId = _publicationId, - ReleaseName = "2000", - TimePeriodCoverage = AcademicYear, - Published = DateTime.UtcNow, - }; + Publication publication = _fixture.DefaultPublication() + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]) + .WithSupersededBy(_fixture + .DefaultPublication() + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)])); - var publication = new Publication - { - Id = _publicationId, - LatestPublishedReleaseVersion = releaseVersion, - SupersededBy = new Publication - { - LatestPublishedReleaseVersionId = Guid.NewGuid() - } - }; + var releaseVersion = publication.Releases.Single().Versions.Single(); var permalink = new Permalink { SubjectId = Guid.NewGuid() }; + var releaseDataFile = ReleaseDataFile(releaseVersion, permalink.SubjectId); + var contentDbContextId = Guid.NewGuid().ToString(); await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { contentDbContext.Permalinks.Add(permalink); - contentDbContext.Publications.Add(publication); - contentDbContext.ReleaseVersions.Add(releaseVersion); - contentDbContext.ReleaseFiles.AddRange(ReleaseDataFile(releaseVersion, permalink.SubjectId)); + contentDbContext.Publications.AddRange(publication); + contentDbContext.ReleaseFiles.Add(releaseDataFile); await contentDbContext.SaveChangesAsync(); } @@ -1337,7 +1186,6 @@ private static PermalinkService BuildService( IPermalinkCsvMetaService? permalinkCsvMetaService = null, IPublicBlobStorageService? publicBlobStorageService = null, IFrontendService? frontendService = null, - IReleaseVersionRepository? releaseVersionRepository = null, ISubjectRepository? subjectRepository = null, IPublicationRepository? publicationRepository = null) { @@ -1350,8 +1198,7 @@ private static PermalinkService BuildService( publicBlobStorageService ?? Mock.Of(MockBehavior.Strict), frontendService ?? Mock.Of(MockBehavior.Strict), subjectRepository ?? Mock.Of(MockBehavior.Strict), - publicationRepository ?? new PublicationRepository(contentDbContext), - releaseVersionRepository ?? Mock.Of(MockBehavior.Strict) + publicationRepository ?? new PublicationRepository(contentDbContext) ); } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithReleaseId_csv.snap b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithReleaseVersionId_csv.snap similarity index 100% rename from src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithReleaseId_csv.snap rename to src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithReleaseVersionId_csv.snap diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithReleaseId_json.snap b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithReleaseVersionId_json.snap similarity index 100% rename from src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithReleaseId_json.snap rename to src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithReleaseVersionId_json.snap diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithoutReleaseId_csv.snap b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithoutReleaseVersionId_csv.snap similarity index 100% rename from src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithoutReleaseId_csv.snap rename to src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithoutReleaseVersionId_csv.snap diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithoutReleaseId_json.snap b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithoutReleaseVersionId_json.snap similarity index 100% rename from src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithoutReleaseId_json.snap rename to src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/__snapshots__/PermalinkServiceTests.CreatePermalink_WithoutReleaseVersionId_json.snap diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/PermalinkService.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/PermalinkService.cs index 5638c3496e..e72b68fce1 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/PermalinkService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/PermalinkService.cs @@ -40,7 +40,6 @@ public class PermalinkService : IPermalinkService private readonly IFrontendService _frontendService; private readonly ISubjectRepository _subjectRepository; private readonly IPublicationRepository _publicationRepository; - private readonly IReleaseVersionRepository _releaseVersionRepository; public PermalinkService( ContentDbContext contentDbContext, @@ -49,8 +48,7 @@ public PermalinkService( IPublicBlobStorageService publicBlobStorageService, IFrontendService frontendService, ISubjectRepository subjectRepository, - IPublicationRepository publicationRepository, - IReleaseVersionRepository releaseVersionRepository) + IPublicationRepository publicationRepository) { _contentDbContext = contentDbContext; _tableBuilderService = tableBuilderService; @@ -59,7 +57,6 @@ public PermalinkService( _frontendService = frontendService; _subjectRepository = subjectRepository; _publicationRepository = publicationRepository; - _releaseVersionRepository = releaseVersionRepository; } public async Task> GetPermalink(Guid permalinkId, @@ -307,9 +304,11 @@ private async Task> FindLatestPublishedReleaseVersion { return await _subjectRepository.FindPublicationIdForSubject(subjectId) .OrNotFound() - .OnSuccess(publicationId => - _releaseVersionRepository.GetLatestPublishedReleaseVersion(publicationId).OrNotFound()) - .OnSuccess(releaseVersion => releaseVersion.Id); + .OnSuccess(async publicationId => await _contentDbContext.Publications + .Where(p => p.Id == publicationId) + .Select(p => p.LatestPublishedReleaseVersionId) + .SingleOrDefaultAsync() + .OrNotFound()); } private async Task GetPermalinkStatus(Guid subjectId) @@ -317,9 +316,7 @@ private async Task GetPermalinkStatus(Guid subjectId) // TODO EES-3339 This doesn't currently include a status to warn if the footnotes have been amended on a Release, // and will return 'Current' unless one of the other cases also applies. - var releasesWithSubject = await _contentDbContext.ReleaseFiles - .Include(rf => rf.File) - .Include(rf => rf.ReleaseVersion) + var releasesVersionsWithSubject = await _contentDbContext.ReleaseFiles .Where(rf => rf.File.SubjectId == subjectId && rf.File.Type == FileType.Data @@ -327,26 +324,25 @@ private async Task GetPermalinkStatus(Guid subjectId) .Select(rf => rf.ReleaseVersion) .ToListAsync(); - if (releasesWithSubject.Count == 0) + if (releasesVersionsWithSubject.Count == 0) { return PermalinkStatus.SubjectRemoved; } var publication = await _contentDbContext.Publications .Include(p => p.LatestPublishedReleaseVersion) - .SingleAsync(p => p.Id == releasesWithSubject.First().PublicationId); + .SingleAsync(p => p.Id == releasesVersionsWithSubject.First().PublicationId); var latestPublishedReleaseVersion = publication.LatestPublishedReleaseVersion; - if (latestPublishedReleaseVersion != null && releasesWithSubject.All(rv => - rv.Year != latestPublishedReleaseVersion.Year - || rv.TimePeriodCoverage != latestPublishedReleaseVersion.TimePeriodCoverage)) + if (latestPublishedReleaseVersion != null && releasesVersionsWithSubject.All(rv => + rv.ReleaseId != latestPublishedReleaseVersion.ReleaseId)) { return PermalinkStatus.NotForLatestRelease; } if (latestPublishedReleaseVersion != null - && releasesWithSubject.All(rv => rv.Id != latestPublishedReleaseVersion.Id)) + && releasesVersionsWithSubject.All(rv => rv.Id != latestPublishedReleaseVersion.Id)) { return PermalinkStatus.SubjectReplacedOrRemoved; } From 364dc0887710866afddbbe562c5414068e74e529 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Mon, 16 Dec 2024 16:24:16 +0000 Subject: [PATCH 14/21] EES-5656 Change TableBuilderService to not use method GetLatestPublishedReleaseVersion(Guid publicationId) --- .../TableBuilderServicePermissionTests.cs | 124 ++++++++--------- .../TableBuilderServiceTests.cs | 125 +++++++----------- ...ts.QueryToCsvStream_ReleaseVersionId.snap} | 0 ...CsvStream_ReleaseVersionId_NoFilters.snap} | 0 .../TableBuilderService.cs | 40 +++--- 5 files changed, 134 insertions(+), 155 deletions(-) rename src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/{TableBuilderServiceTests.QueryToCsvStream_ReleaseId.snap => TableBuilderServiceTests.QueryToCsvStream_ReleaseVersionId.snap} (100%) rename src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/{TableBuilderServiceTests.QueryToCsvStream_ReleaseId_NoFilters.snap => TableBuilderServiceTests.QueryToCsvStream_ReleaseVersionId_NoFilters.snap} (100%) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/TableBuilderServicePermissionTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/TableBuilderServicePermissionTests.cs index 9ff053349b..94b5d24c31 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/TableBuilderServicePermissionTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/TableBuilderServicePermissionTests.cs @@ -1,110 +1,115 @@ #nullable enable +using System; +using System.Linq; +using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Model.Data.Query; using GovUk.Education.ExploreEducationStatistics.Common.Services.Interfaces.Security; +using GovUk.Education.ExploreEducationStatistics.Common.Tests.Extensions; +using GovUk.Education.ExploreEducationStatistics.Common.Tests.Fixtures; using GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils; using GovUk.Education.ExploreEducationStatistics.Common.Utils; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; +using GovUk.Education.ExploreEducationStatistics.Content.Model; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; using GovUk.Education.ExploreEducationStatistics.Data.Model; using GovUk.Education.ExploreEducationStatistics.Data.Model.Database; using GovUk.Education.ExploreEducationStatistics.Data.Model.Repository.Interfaces; using GovUk.Education.ExploreEducationStatistics.Data.Services.Interfaces; +using GovUk.Education.ExploreEducationStatistics.Data.Services.Options; using GovUk.Education.ExploreEducationStatistics.Data.Services.Security; using Microsoft.Extensions.Options; using Moq; -using System; -using System.Threading.Tasks; -using GovUk.Education.ExploreEducationStatistics.Common.Tests.Extensions; -using GovUk.Education.ExploreEducationStatistics.Data.Services.Options; using Xunit; using static GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils.PermissionTestUtils; +using static GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Utils.ContentDbUtils; using static Moq.MockBehavior; -using ReleaseVersion = GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion; namespace GovUk.Education.ExploreEducationStatistics.Data.Services.Tests { public class TableBuilderServicePermissionTests { - private static readonly Guid PublicationId = Guid.NewGuid(); - private static readonly Guid ReleaseVersionId = Guid.NewGuid(); - private static readonly Guid SubjectId = Guid.NewGuid(); - - private readonly Subject _subject = new() - { - Id = Guid.NewGuid(), - }; - - private readonly ReleaseSubject _releaseSubject = new() - { - ReleaseVersionId = ReleaseVersionId, - SubjectId = SubjectId, - }; + private readonly DataFixture _dataFixture = new(); [Fact] public async Task Query_LatestRelease_CanViewSubjectData() { - await PolicyCheckBuilder() - .SetupResourceCheckToFail(_releaseSubject, DataSecurityPolicies.CanViewSubjectData) - .AssertForbidden( - async userService => - { - var statisticsPersistenceHelper = StatisticsPersistenceHelperMock(_subject); + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); - MockUtils.SetupCall(statisticsPersistenceHelper, _releaseSubject); + var releaseVersion = publication.Releases.Single().Versions.Single(); - var subjectRepository = new Mock(Strict); + var releaseSubject = new ReleaseSubject + { + ReleaseVersionId = releaseVersion.Id, + SubjectId = Guid.NewGuid(), + }; - subjectRepository - .Setup(s => s.FindPublicationIdForSubject(_subject.Id, default)) - .ReturnsAsync(PublicationId); + var statisticsPersistenceHelper = MockUtils.MockPersistenceHelper(); + MockUtils.SetupCall(statisticsPersistenceHelper, releaseSubject); - var releaseVersionRepository = new Mock(Strict); + await using var contextDbContext = InMemoryContentDbContext(); + contextDbContext.Publications.Add(publication); + await contextDbContext.SaveChangesAsync(); - releaseVersionRepository - .Setup(s => s.GetLatestPublishedReleaseVersion(PublicationId, default)) - .ReturnsAsync(new ReleaseVersion - { - Id = ReleaseVersionId - }); + var subjectRepository = new Mock(Strict); + subjectRepository + .Setup(s => s.FindPublicationIdForSubject(releaseSubject.SubjectId, default)) + .ReturnsAsync(publication.Id); + + await PolicyCheckBuilder() + .SetupResourceCheckToFail(releaseSubject, DataSecurityPolicies.CanViewSubjectData) + .AssertForbidden( + async userService => + { var service = BuildTableBuilderService( + contextDbContext, userService: userService.Object, subjectRepository: subjectRepository.Object, - releaseVersionRepository: releaseVersionRepository.Object, statisticsPersistenceHelper: statisticsPersistenceHelper.Object ); return await service.Query( - new FullTableQuery - { - SubjectId = _subject.Id - } + new FullTableQuery { SubjectId = releaseSubject.SubjectId } ); } ); } [Fact] - public async Task Query_ReleaseId_CanViewSubjectData() + public async Task Query_ReleaseVersionId_CanViewSubjectData() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var releaseVersion = publication.Releases.Single().Versions.Single(); + + var releaseSubject = new ReleaseSubject + { + ReleaseVersionId = releaseVersion.Id, + SubjectId = Guid.NewGuid(), + }; + + var statisticsPersistenceHelper = MockUtils.MockPersistenceHelper(); + MockUtils.SetupCall(statisticsPersistenceHelper, releaseSubject); + + await using var contextDbContext = InMemoryContentDbContext(); + contextDbContext.Publications.Add(publication); + await contextDbContext.SaveChangesAsync(); + await PolicyCheckBuilder() - .SetupResourceCheckToFail(_releaseSubject, DataSecurityPolicies.CanViewSubjectData) + .SetupResourceCheckToFail(releaseSubject, DataSecurityPolicies.CanViewSubjectData) .AssertForbidden( async userService => { - var statisticsPersistenceHelper = StatisticsPersistenceHelperMock(_subject); - - MockUtils.SetupCall(statisticsPersistenceHelper, _releaseSubject); - var service = BuildTableBuilderService( + contextDbContext, userService: userService.Object, statisticsPersistenceHelper: statisticsPersistenceHelper.Object ); return await service.Query( - ReleaseVersionId, - new FullTableQuery - { - SubjectId = _subject.Id - }, + releaseVersionId: releaseVersion.Id, + new FullTableQuery { SubjectId = releaseSubject.SubjectId }, boundaryLevelId: null ); } @@ -112,6 +117,7 @@ await PolicyCheckBuilder() } private TableBuilderService BuildTableBuilderService( + ContentDbContext contentDbContext, IFilterItemRepository? filterItemRepository = null, ILocationService? locationService = null, IObservationService? observationService = null, @@ -120,29 +126,23 @@ private TableBuilderService BuildTableBuilderService( ISubjectCsvMetaService? subjectCsvMetaService = null, ISubjectRepository? subjectRepository = null, IUserService? userService = null, - IReleaseVersionRepository? releaseVersionRepository = null, IOptions? tableBuilderOptions = null, IOptions? locationsOptions = null) { return new( Mock.Of(), + contentDbContext, filterItemRepository ?? Mock.Of(Strict), locationService ?? Mock.Of(Strict), observationService ?? Mock.Of(Strict), - statisticsPersistenceHelper ?? StatisticsPersistenceHelperMock(_subject).Object, + statisticsPersistenceHelper ?? MockUtils.MockPersistenceHelper().Object, subjectResultMetaService ?? Mock.Of(Strict), subjectCsvMetaService ?? Mock.Of(Strict), subjectRepository ?? Mock.Of(Strict), userService ?? Mock.Of(Strict), - releaseVersionRepository ?? Mock.Of(Strict), tableBuilderOptions ?? new TableBuilderOptions().ToOptionsWrapper(), locationsOptions ?? new LocationsOptions().ToOptionsWrapper() ); } - - private static Mock> StatisticsPersistenceHelperMock(Subject subject) - { - return MockUtils.MockPersistenceHelper(subject.Id, subject); - } } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/TableBuilderServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/TableBuilderServiceTests.cs index 8ef7eba13c..3b4c307a5e 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/TableBuilderServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/TableBuilderServiceTests.cs @@ -1,4 +1,9 @@ #nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using GovUk.Education.ExploreEducationStatistics.Common.Model.Data.Query; @@ -8,8 +13,6 @@ using GovUk.Education.ExploreEducationStatistics.Common.Utils; using GovUk.Education.ExploreEducationStatistics.Content.Model; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; using GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; using GovUk.Education.ExploreEducationStatistics.Data.Model; using GovUk.Education.ExploreEducationStatistics.Data.Model.Database; @@ -18,17 +21,12 @@ using GovUk.Education.ExploreEducationStatistics.Data.Model.Tests.Fixtures; using GovUk.Education.ExploreEducationStatistics.Data.Model.Utils; using GovUk.Education.ExploreEducationStatistics.Data.Services.Interfaces; +using GovUk.Education.ExploreEducationStatistics.Data.Services.Options; using GovUk.Education.ExploreEducationStatistics.Data.Services.Utils; using GovUk.Education.ExploreEducationStatistics.Data.ViewModels.Meta; using Microsoft.Extensions.Options; using Moq; using Snapshooter.Xunit; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using GovUk.Education.ExploreEducationStatistics.Data.Services.Options; using Xunit; using static GovUk.Education.ExploreEducationStatistics.Common.Model.TimeIdentifier; using static GovUk.Education.ExploreEducationStatistics.Common.Services.CollectionUtils; @@ -48,9 +46,7 @@ public async Task Query_LatestRelease() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -244,9 +240,7 @@ public async Task Query_LatestRelease_SubjectNotFound() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -290,9 +284,7 @@ public async Task Query_LatestRelease_PublicationNotFound() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -334,7 +326,7 @@ public async Task Query_LatestRelease_PublicationNotFound() [Fact] public async Task Query_LatestRelease_ReleaseNotFound() { - // Set up a ReleaseSubject that references a non-existent release + // Set up a ReleaseSubject that references a non-existent release version ReleaseSubject releaseSubject = _fixture .DefaultReleaseSubject() .WithReleaseVersion(_fixture @@ -370,9 +362,7 @@ public async Task Query_LatestRelease_PredictedTableTooBig() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -453,9 +443,7 @@ public async Task Query_ReleaseId() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -640,13 +628,11 @@ public async Task Query_ReleaseId() } [Fact] - public async Task Query_ReleaseId_ReleaseNotFound() + public async Task Query_ReleaseVersionId_ReleaseVersionNotFound() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -677,21 +663,22 @@ public async Task Query_ReleaseId_ReleaseNotFound() SubjectId = releaseSubject.SubjectId }; - // Query using a non-existent release id - var result = await service.Query(Guid.NewGuid(), query, null); + // Query using a non-existent release version id + var result = await service.Query( + releaseVersionId: Guid.NewGuid(), + query, + boundaryLevelId: null); result.AssertNotFound(); } } [Fact] - public async Task Query_ReleaseId_SubjectNotFound() + public async Task Query_ReleaseVersionId_SubjectNotFound() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -723,20 +710,21 @@ public async Task Query_ReleaseId_SubjectNotFound() SubjectId = Guid.NewGuid(), }; - var result = await service.Query(releaseVersion.Id, query, null); + var result = await service.Query( + releaseVersionId: releaseVersion.Id, + query, + boundaryLevelId: null); result.AssertNotFound(); } } [Fact] - public async Task Query_ReleaseId_PredictedTableTooBig() + public async Task Query_ReleaseVersionId_PredictedTableTooBig() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -803,7 +791,10 @@ public async Task Query_ReleaseId_PredictedTableTooBig() tableBuilderOptions: options.ToOptionsWrapper() ); - var result = await service.Query(releaseSubject.ReleaseVersionId, query, null); + var result = await service.Query( + releaseVersionId: releaseSubject.ReleaseVersionId, + query, + boundaryLevelId: null); VerifyAllMocks(filterItemRepository); @@ -816,9 +807,7 @@ public async Task QueryToCsvStream_LatestRelease() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var filters = _fixture.DefaultFilter() .ForIndex(0, s => @@ -1011,7 +1000,7 @@ public async Task QueryToCsvStream_LatestRelease() } [Fact] - public async Task QueryToCsvStream_LatestRelease_ReleaseNotFound() + public async Task QueryToCsvStream_LatestRelease_ReleaseVersionNotFound() { // Set up a ReleaseSubject that references a non-existent release ReleaseSubject releaseSubject = _fixture @@ -1053,9 +1042,7 @@ public async Task QueryToCsvStream_LatestRelease_SubjectNotFound() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -1101,9 +1088,7 @@ public async Task QueryToCsvStream_LatestRelease_PredictedTableTooBig() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -1182,13 +1167,11 @@ public async Task QueryToCsvStream_LatestRelease_PredictedTableTooBig() } [Fact] - public async Task QueryToCsvStream_ReleaseId() + public async Task QueryToCsvStream_ReleaseVersionId() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -1368,13 +1351,11 @@ public async Task QueryToCsvStream_ReleaseId() } [Fact] - public async Task QueryToCsvStream_ReleaseId_NoFilters() + public async Task QueryToCsvStream_ReleaseVersionId_NoFilters() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -1504,13 +1485,11 @@ public async Task QueryToCsvStream_ReleaseId_NoFilters() } [Fact] - public async Task QueryToCsvStream_ReleaseId_ReleaseNotFound() + public async Task QueryToCsvStream_ReleaseVersionId_ReleaseVersionNotFound() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -1544,21 +1523,22 @@ public async Task QueryToCsvStream_ReleaseId_ReleaseNotFound() using var stream = new MemoryStream(); - // Query using a non-existent release id - var result = await service.QueryToCsvStream(Guid.NewGuid(), query, stream); + // Query using a non-existent release version id + var result = await service.QueryToCsvStream( + releaseVersionId: Guid.NewGuid(), + query, + stream); result.AssertNotFound(); } } [Fact] - public async Task QueryToCsvStream_ReleaseId_SubjectNotFound() + public async Task QueryToCsvStream_ReleaseVersionId_SubjectNotFound() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -1600,13 +1580,11 @@ public async Task QueryToCsvStream_ReleaseId_SubjectNotFound() } [Fact] - public async Task QueryToCsvStream_ReleaseId_PredictedTableTooBig() + public async Task QueryToCsvStream_ReleaseVersionId_PredictedTableTooBig() { Publication publication = _fixture .DefaultPublication() - .WithReleases(_fixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]); var releaseVersion = publication.ReleaseVersions.Single(); @@ -1711,12 +1689,12 @@ private static TableBuilderService BuildTableBuilderService( ISubjectCsvMetaService? subjectCsvMetaService = null, ISubjectRepository? subjectRepository = null, IUserService? userService = null, - IReleaseVersionRepository? releaseVersionRepository = null, IOptions? tableBuilderOptions = null, IOptions? locationsOptions = null) { return new( statisticsDbContext, + contentDbContext ?? InMemoryContentDbContext(), filterItemRepository ?? Mock.Of(Strict), locationService ?? Mock.Of(Strict), observationService ?? Mock.Of(Strict), @@ -1725,7 +1703,6 @@ private static TableBuilderService BuildTableBuilderService( subjectCsvMetaService ?? Mock.Of(Strict), subjectRepository ?? new SubjectRepository(statisticsDbContext), userService ?? AlwaysTrueUserService().Object, - releaseVersionRepository ?? new ReleaseVersionRepository(contentDbContext ?? Mock.Of()), tableBuilderOptions ?? DefaultTableBuilderOptions(), locationsOptions ?? DefaultLocationOptions() ); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/TableBuilderServiceTests.QueryToCsvStream_ReleaseId.snap b/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/TableBuilderServiceTests.QueryToCsvStream_ReleaseVersionId.snap similarity index 100% rename from src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/TableBuilderServiceTests.QueryToCsvStream_ReleaseId.snap rename to src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/TableBuilderServiceTests.QueryToCsvStream_ReleaseVersionId.snap diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/TableBuilderServiceTests.QueryToCsvStream_ReleaseId_NoFilters.snap b/src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/TableBuilderServiceTests.QueryToCsvStream_ReleaseVersionId_NoFilters.snap similarity index 100% rename from src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/TableBuilderServiceTests.QueryToCsvStream_ReleaseId_NoFilters.snap rename to src/GovUk.Education.ExploreEducationStatistics.Data.Services.Tests/__snapshots__/TableBuilderServiceTests.QueryToCsvStream_ReleaseVersionId_NoFilters.snap diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Services/TableBuilderService.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Services/TableBuilderService.cs index a3c0c22295..ce7ae6d4af 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Services/TableBuilderService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Services/TableBuilderService.cs @@ -1,4 +1,12 @@ #nullable enable +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using CsvHelper; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Model; @@ -6,7 +14,7 @@ using GovUk.Education.ExploreEducationStatistics.Common.Services.Interfaces.Security; using GovUk.Education.ExploreEducationStatistics.Common.Utils; using GovUk.Education.ExploreEducationStatistics.Common.Validators; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; using GovUk.Education.ExploreEducationStatistics.Data.Model; using GovUk.Education.ExploreEducationStatistics.Data.Model.Database; using GovUk.Education.ExploreEducationStatistics.Data.Model.Repository.Interfaces; @@ -20,14 +28,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Dynamic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using static GovUk.Education.ExploreEducationStatistics.Data.Services.Utils.TableBuilderUtils; using static GovUk.Education.ExploreEducationStatistics.Data.Services.ValidationErrorMessages; using Unit = GovUk.Education.ExploreEducationStatistics.Common.Model.Unit; @@ -36,7 +36,8 @@ namespace GovUk.Education.ExploreEducationStatistics.Data.Services { public class TableBuilderService : ITableBuilderService { - private readonly StatisticsDbContext _context; + private readonly StatisticsDbContext _statisticsDbContext; + private readonly ContentDbContext _contentDbContext; private readonly IFilterItemRepository _filterItemRepository; private readonly ILocationService _locationService; private readonly IObservationService _observationService; @@ -45,12 +46,12 @@ public class TableBuilderService : ITableBuilderService private readonly ISubjectCsvMetaService _subjectCsvMetaService; private readonly ISubjectRepository _subjectRepository; private readonly IUserService _userService; - private readonly IReleaseVersionRepository _releaseVersionRepository; private readonly TableBuilderOptions _options; private readonly LocationsOptions _locationOptions; public TableBuilderService( - StatisticsDbContext context, + StatisticsDbContext statisticsDbContext, + ContentDbContext contentDbContext, IFilterItemRepository filterItemRepository, ILocationService locationService, IObservationService observationService, @@ -59,11 +60,11 @@ public TableBuilderService( ISubjectCsvMetaService subjectCsvMetaService, ISubjectRepository subjectRepository, IUserService userService, - IReleaseVersionRepository releaseVersionRepository, IOptions options, IOptions locationOptions) { - _context = context; + _statisticsDbContext = statisticsDbContext; + _contentDbContext = contentDbContext; _filterItemRepository = filterItemRepository; _locationService = locationService; _observationService = observationService; @@ -72,7 +73,6 @@ public TableBuilderService( _subjectCsvMetaService = subjectCsvMetaService; _subjectRepository = subjectRepository; _userService = userService; - _releaseVersionRepository = releaseVersionRepository; _options = options.Value; _locationOptions = locationOptions.Value; } @@ -189,7 +189,7 @@ private async Task>> ListQueryObservation (await _observationService.GetMatchedObservations(query, cancellationToken)) .Select(row => row.Id); - return await _context + return await _statisticsDbContext .Observation .AsNoTracking() .Include(o => o.Location) @@ -236,9 +236,11 @@ private async Task> FindLatestPublishedReleaseVersion { return await _subjectRepository.FindPublicationIdForSubject(subjectId) .OrNotFound() - .OnSuccess(publicationId => - _releaseVersionRepository.GetLatestPublishedReleaseVersion(publicationId).OrNotFound()) - .OnSuccess(releaseVersion => releaseVersion.Id); + .OnSuccess(async publicationId => await _contentDbContext.Publications + .Where(p => p.Id == publicationId) + .Select(p => p.LatestPublishedReleaseVersionId) + .SingleOrDefaultAsync() + .OrNotFound()); } private Task> CheckReleaseSubjectExists(Guid subjectId, From 2d81e797bbdd0620486d9453983354d62332f0cf Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Mon, 16 Dec 2024 17:36:09 +0000 Subject: [PATCH 15/21] EES-5656 Change TableBuilderController to not use method GetLatestPublishedReleaseVersion(Guid publicationId) --- .../TableBuilderControllerTests.cs | 457 +++++++++++------- .../Controllers/TableBuilderController.cs | 72 ++- 2 files changed, 318 insertions(+), 211 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Controllers/TableBuilderControllerTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Controllers/TableBuilderControllerTests.cs index d182167b4b..687c858841 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Controllers/TableBuilderControllerTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Controllers/TableBuilderControllerTests.cs @@ -1,4 +1,10 @@ #nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Model; using GovUk.Education.ExploreEducationStatistics.Common.Model.Chart; @@ -9,7 +15,6 @@ using GovUk.Education.ExploreEducationStatistics.Common.Utils; using GovUk.Education.ExploreEducationStatistics.Content.Model; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; using GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; using GovUk.Education.ExploreEducationStatistics.Data.Api.Cache; using GovUk.Education.ExploreEducationStatistics.Data.Api.Controllers; @@ -23,15 +28,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; using Moq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Xunit; using static GovUk.Education.ExploreEducationStatistics.Common.Model.TimeIdentifier; -using static GovUk.Education.ExploreEducationStatistics.Common.Services.CollectionUtils; using static GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils.MockUtils; using static Moq.MockBehavior; @@ -40,83 +38,50 @@ namespace GovUk.Education.ExploreEducationStatistics.Data.Api.Tests.Controllers public class TableBuilderControllerTests(TestApplicationFactory testApp) : IntegrationTestFixture(testApp) { - private static readonly DataFixture Fixture = new(); - - private static readonly Release Release = Fixture.DefaultRelease(publishedVersions: 1) - .WithPublication(Fixture.DefaultPublication()); - - private static readonly ReleaseVersion ReleaseVersion = Release.Versions.Single(); - - private static readonly DataBlockParent DataBlockParent = Fixture - .DefaultDataBlockParent() - .WithLatestPublishedVersion(Fixture - .DefaultDataBlockVersion() - .WithReleaseVersion(ReleaseVersion) - .WithDates(published: DateTime.UtcNow.AddDays(-1)) - .WithQuery(new FullTableQuery - { - SubjectId = Guid.NewGuid(), - LocationIds = [Guid.NewGuid(),], - TimePeriod = new TimePeriodQuery - { - StartYear = 2021, - StartCode = CalendarYear, - EndYear = 2022, - EndCode = CalendarYear - }, - Filters = new List(), - Indicators = new List // use collection expression -> test failures - { - Guid.NewGuid(), - }, - }) - .WithTable(new TableBuilderConfiguration - { - TableHeaders = new TableHeaders - { - Rows = new List { new("table header 1", TableHeaderType.Filter) } - } - }) - .WithCharts(ListOf(new LineChart - { - Title = "Test chart", - Height = 400, - Width = 500, - })) - .Generate()) - .Generate(); - - private static readonly DataBlockParent DataBlockParentWithNoPublishedVersion = Fixture - .DefaultDataBlockParent() - .WithLatestDraftVersion(Fixture - .DefaultDataBlockVersion() - .WithReleaseVersion(ReleaseVersion) - .Generate()) - .Generate(); - - private static readonly Guid PublicationId = ReleaseVersion.PublicationId; - - private static readonly Guid ReleaseVersionId = ReleaseVersion.Id; + private readonly DataFixture _dataFixture = new(); - private static readonly Guid DataBlockId = DataBlockParent.LatestPublishedVersion!.Id; - - private static readonly Guid DataBlockParentId = DataBlockParent.Id; + private static readonly List Charts = + [ + new LineChart + { + Title = "Test chart", + Height = 400, + Width = 500 + } + ]; - private static readonly FullTableQuery FullTableQuery = - DataBlockParent.LatestPublishedVersion!.Query; + private static readonly FullTableQuery FullTableQuery = new() + { + SubjectId = Guid.NewGuid(), + LocationIds = [Guid.NewGuid()], + TimePeriod = new TimePeriodQuery + { + StartYear = 2021, + StartCode = CalendarYear, + EndYear = 2022, + EndCode = CalendarYear + }, + Filters = new List(), + Indicators = new List // use collection expression -> test failures + { + Guid.NewGuid() + } + }; - private static readonly TableBuilderConfiguration TableConfiguration = - DataBlockParent.LatestPublishedVersion!.Table; + private static readonly TableBuilderConfiguration TableConfiguration = new() + { + TableHeaders = new TableHeaders { Rows = [new TableHeader("table header 1", TableHeaderType.Filter)] } + }; private readonly TableBuilderResultViewModel _tableBuilderResults = new() { SubjectMeta = new SubjectResultMetaViewModel { - TimePeriodRange = new List - { - new(2020, AcademicYear), - new(2021, AcademicYear), - } + TimePeriodRange = + [ + new TimePeriodMetaViewModel(2020, AcademicYear), + new TimePeriodMetaViewModel(2021, AcademicYear) + ] }, Results = new List { @@ -164,8 +129,7 @@ public async Task Query_Csv() ) .ReturnsAsync(Unit.Instance) .Callback( - (_, stream, _) => { stream.WriteText("Test csv"); } - ); + (_, stream, _) => stream.WriteText("Test csv")); var client = SetupApp(tableBuilderService: tableBuilderService.Object).CreateClient(); @@ -181,17 +145,22 @@ public async Task Query_Csv() } [Fact] - public async Task Query_ReleaseId() + public async Task Query_ReleaseVersionId() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var releaseVersion = publication.Releases.Single().Versions.Single(); + await TestApp.AddTestData(context => - context.ReleaseVersions.Add(ReleaseVersion)); + context.Publications.Add(publication)); var tableBuilderService = new Mock(Strict); tableBuilderService .Setup( s => s.Query( - ReleaseVersionId, + releaseVersion.Id, ItIs.DeepEqualTo(FullTableQuery), It.IsAny(), It.IsAny() @@ -203,7 +172,7 @@ await TestApp.AddTestData(context => .CreateClient(); var response = await client - .PostAsync($"/api/tablebuilder/release/{ReleaseVersionId}", + .PostAsync($"/api/tablebuilder/release/{releaseVersion.Id}", new JsonNetContent(FullTableQuery)); VerifyAllMocks(tableBuilderService); @@ -212,17 +181,23 @@ await TestApp.AddTestData(context => } [Fact] - public async Task Query_ReleaseId_Csv() + public async Task Query_ReleaseVersionId_Csv() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var release = publication.Releases.Single(); + var releaseVersion = release.Versions.Single(); + await TestApp.AddTestData(context => - context.ReleaseVersions.Add(ReleaseVersion)); + context.Publications.Add(publication)); var tableBuilderService = new Mock(Strict); tableBuilderService .Setup( s => s.QueryToCsvStream( - ReleaseVersionId, + releaseVersion.Id, ItIs.DeepEqualTo(FullTableQuery), It.IsAny(), It.IsAny() @@ -230,14 +205,13 @@ await TestApp.AddTestData(context => ) .ReturnsAsync(Unit.Instance) .Callback( - (_, _, stream, _) => { stream.WriteText("Test csv"); } - ); + (_, _, stream, _) => stream.WriteText("Test csv")); var client = SetupApp(tableBuilderService: tableBuilderService.Object) .CreateClient(); var response = await client - .PostAsync($"/api/tablebuilder/release/{ReleaseVersionId}", + .PostAsync($"/api/tablebuilder/release/{releaseVersion.Id}", content: new JsonNetContent(FullTableQuery), headers: new Dictionary { { HeaderNames.Accept, ContentTypes.Csv } } ); @@ -251,12 +225,34 @@ await TestApp.AddTestData(context => [Fact] public async Task QueryForTableBuilderResult() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var release = publication.Releases.Single(); + var releaseVersion = release.Versions.Single(); + + DataBlockParent dataBlockParent = _dataFixture + .DefaultDataBlockParent() + .WithLatestPublishedVersion(_dataFixture + .DefaultDataBlockVersion() + .WithReleaseVersion(releaseVersion) + .WithDates(published: DateTime.UtcNow.AddDays(-1)) + .WithQuery(FullTableQuery) + .WithTable(TableConfiguration) + .WithCharts(Charts)); + + var dataBlockId = dataBlockParent.LatestPublishedVersion!.Id; + await TestApp.AddTestData(context => - context.DataBlockParents.Add(DataBlockParent)); + { + context.Publications.Add(publication); + context.DataBlockParents.Add(dataBlockParent); + }); - var cacheKey = new DataBlockTableResultCacheKey(publicationSlug: ReleaseVersion.Publication.Slug, - releaseSlug: ReleaseVersion.Slug, - DataBlockParentId); + var cacheKey = new DataBlockTableResultCacheKey( + publicationSlug: publication.Slug, + releaseSlug: release.Slug, + dataBlockParent.Id); BlobCacheService .Setup(s => s.GetItemAsync(cacheKey, typeof(TableBuilderResultViewModel))) @@ -269,7 +265,7 @@ await TestApp.AddTestData(context => var dataBlockService = new Mock(); dataBlockService - .Setup(s => s.GetDataBlockTableResult(ReleaseVersionId, DataBlockId, null)) + .Setup(s => s.GetDataBlockTableResult(releaseVersion.Id, dataBlockId, null)) .ReturnsAsync(_tableBuilderResults); var client = SetupApp(dataBlockService: dataBlockService.Object) @@ -277,7 +273,7 @@ await TestApp.AddTestData(context => var response = await client.GetAsync( - $"http://localhost/api/tablebuilder/release/{ReleaseVersionId}/data-block/{DataBlockParentId}"); + $"http://localhost/api/tablebuilder/release/{releaseVersion.Id}/data-block/{dataBlockParent.Id}"); VerifyAllMocks(BlobCacheService, dataBlockService); @@ -287,10 +283,18 @@ await client.GetAsync( [Fact] public async Task QueryForTableBuilderResult_NotFound() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var release = publication.Releases.Single(); + var releaseVersion = release.Versions.Single(); + + await TestApp.AddTestData(context => context.Publications.Add(publication)); + var client = SetupApp().CreateClient(); var response = - await client.GetAsync($"/api/tablebuilder/release/{ReleaseVersionId}/data-block/{DataBlockParentId}"); + await client.GetAsync($"/api/tablebuilder/release/{releaseVersion.Id}/data-block/{Guid.NewGuid()}"); response.AssertNotFound(); } @@ -298,13 +302,32 @@ public async Task QueryForTableBuilderResult_NotFound() [Fact] public async Task QueryForTableBuilderResult_NotModified() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var release = publication.Releases.Single(); + var releaseVersion = release.Versions.Single(); + + DataBlockParent dataBlockParent = _dataFixture + .DefaultDataBlockParent() + .WithLatestPublishedVersion(_dataFixture + .DefaultDataBlockVersion() + .WithReleaseVersion(releaseVersion) + .WithDates(published: DateTime.UtcNow.AddDays(-1)) + .WithQuery(FullTableQuery) + .WithTable(TableConfiguration) + .WithCharts(Charts)); + await TestApp.AddTestData(context => - context.DataBlockParents.Add(DataBlockParent)); + { + context.Publications.Add(publication); + context.DataBlockParents.Add(dataBlockParent); + }); var client = SetupApp() .CreateClient(); - var publishedDate = DataBlockParent + var publishedDate = dataBlockParent .LatestPublishedVersion! .Published! .Value; @@ -314,7 +337,7 @@ await TestApp.AddTestData(context => var ifModifiedSinceDate = publishedDate.AddSeconds(1); var response = await client.GetAsync( - $"/api/tablebuilder/release/{ReleaseVersionId}/data-block/{DataBlockParentId}", + $"/api/tablebuilder/release/{releaseVersion.Id}/data-block/{dataBlockParent.Id}", new Dictionary { { HeaderNames.IfModifiedSince, ifModifiedSinceDate.ToUniversalTime().ToString("R") }, @@ -330,12 +353,34 @@ await TestApp.AddTestData(context => [Fact] public async Task QueryForTableBuilderResult_ETagChanged() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var release = publication.Releases.Single(); + var releaseVersion = release.Versions.Single(); + + DataBlockParent dataBlockParent = _dataFixture + .DefaultDataBlockParent() + .WithLatestPublishedVersion(_dataFixture + .DefaultDataBlockVersion() + .WithReleaseVersion(releaseVersion) + .WithDates(published: DateTime.UtcNow.AddDays(-1)) + .WithQuery(FullTableQuery) + .WithTable(TableConfiguration) + .WithCharts(Charts)); + + var dataBlockId = dataBlockParent.LatestPublishedVersion!.Id; + await TestApp.AddTestData(context => - context.DataBlockParents.Add(DataBlockParent)); + { + context.Publications.Add(publication); + context.DataBlockParents.Add(dataBlockParent); + }); - var cacheKey = new DataBlockTableResultCacheKey(publicationSlug: ReleaseVersion.Publication.Slug, - releaseSlug: ReleaseVersion.Slug, - DataBlockParentId); + var cacheKey = new DataBlockTableResultCacheKey( + publicationSlug: publication.Slug, + releaseSlug: release.Slug, + dataBlockParent.Id); BlobCacheService .Setup(s => s.GetItemAsync(cacheKey, typeof(TableBuilderResultViewModel))) @@ -348,13 +393,13 @@ await TestApp.AddTestData(context => var dataBlockService = new Mock(Strict); dataBlockService - .Setup(s => s.GetDataBlockTableResult(ReleaseVersionId, DataBlockId, null)) + .Setup(s => s.GetDataBlockTableResult(releaseVersion.Id, dataBlockId, null)) .ReturnsAsync(_tableBuilderResults); var client = SetupApp(dataBlockService: dataBlockService.Object) .CreateClient(); - var publishedDate = DataBlockParent + var publishedDate = dataBlockParent .LatestPublishedVersion! .Published! .Value; @@ -365,7 +410,7 @@ await TestApp.AddTestData(context => var ifModifiedSinceDate = publishedDate.AddSeconds(1); var response = await client.GetAsync( - $"/api/tablebuilder/release/{ReleaseVersionId}/data-block/{DataBlockParentId}", + $"/api/tablebuilder/release/{releaseVersion.Id}/data-block/{dataBlockParent.Id}", new Dictionary { { HeaderNames.IfModifiedSince, ifModifiedSinceDate.ToUniversalTime().ToString("R") }, @@ -381,12 +426,34 @@ await TestApp.AddTestData(context => [Fact] public async Task QueryForTableBuilderResult_LastModifiedChanged() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var release = publication.Releases.Single(); + var releaseVersion = release.Versions.Single(); + + DataBlockParent dataBlockParent = _dataFixture + .DefaultDataBlockParent() + .WithLatestPublishedVersion(_dataFixture + .DefaultDataBlockVersion() + .WithReleaseVersion(releaseVersion) + .WithDates(published: DateTime.UtcNow.AddDays(-1)) + .WithQuery(FullTableQuery) + .WithTable(TableConfiguration) + .WithCharts(Charts)); + + var dataBlockId = dataBlockParent.LatestPublishedVersion!.Id; + await TestApp.AddTestData(context => - context.DataBlockParents.Add(DataBlockParent)); + { + context.Publications.Add(publication); + context.DataBlockParents.Add(dataBlockParent); + }); - var cacheKey = new DataBlockTableResultCacheKey(publicationSlug: ReleaseVersion.Publication.Slug, - releaseSlug: ReleaseVersion.Slug, - DataBlockParentId); + var cacheKey = new DataBlockTableResultCacheKey( + publicationSlug: publication.Slug, + releaseSlug: release.Slug, + dataBlockParent.Id); BlobCacheService .Setup(s => s.GetItemAsync(cacheKey, typeof(TableBuilderResultViewModel))) @@ -399,7 +466,7 @@ await TestApp.AddTestData(context => var dataBlockService = new Mock(Strict); dataBlockService - .Setup(s => s.GetDataBlockTableResult(ReleaseVersionId, DataBlockId, null)) + .Setup(s => s.GetDataBlockTableResult(releaseVersion.Id, dataBlockId, null)) .ReturnsAsync(_tableBuilderResults); var client = SetupApp(dataBlockService: dataBlockService.Object) @@ -407,14 +474,14 @@ await TestApp.AddTestData(context => // The latest published DataBlockVersion has been published since the caller last requested it, so we // consider this "Modified" by the published date alone. - var yearBeforePublishedDate = DataBlockParent + var yearBeforePublishedDate = dataBlockParent .LatestPublishedVersion! .Published! .Value .AddYears(-1); var response = await client.GetAsync( - $"/api/tablebuilder/release/{ReleaseVersionId}/data-block/{DataBlockParentId}", + $"/api/tablebuilder/release/{releaseVersion.Id}/data-block/{dataBlockParent.Id}", new Dictionary { { HeaderNames.IfModifiedSince, yearBeforePublishedDate.ToUniversalTime().ToString("R") }, @@ -430,20 +497,40 @@ await TestApp.AddTestData(context => [Fact] public async Task QueryForFastTrack() { - await TestApp.AddTestData(context => - context.DataBlockParents.Add(DataBlockParent)); + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases([ + _dataFixture + .DefaultRelease(publishedVersions: 1, year: 2020), + _dataFixture + .DefaultRelease(publishedVersions: 1, year: 2021) + ]); + + var release = publication.Releases.Single(r => r.Year == 2021); + var releaseVersion = release.Versions.Single(); + + DataBlockParent dataBlockParent = _dataFixture + .DefaultDataBlockParent() + .WithLatestPublishedVersion(_dataFixture + .DefaultDataBlockVersion() + .WithReleaseVersion(releaseVersion) + .WithDates(published: DateTime.UtcNow.AddDays(-1)) + .WithQuery(FullTableQuery) + .WithTable(TableConfiguration) + .WithCharts(Charts)); + + var dataBlockId = dataBlockParent.LatestPublishedVersion!.Id; - var latestReleaseVersion = new ReleaseVersion + await TestApp.AddTestData(context => { - Id = ReleaseVersionId, - ReleaseName = "2020", - TimePeriodCoverage = AcademicYear - }; + context.Publications.Add(publication); + context.DataBlockParents.Add(dataBlockParent); + }); var cacheKey = new DataBlockTableResultCacheKey( - ReleaseVersion.Publication.Slug, - ReleaseVersion.Slug, - DataBlockParentId); + publicationSlug: publication.Slug, + releaseSlug: release.Slug, + dataBlockParent.Id); BlobCacheService .Setup(s => s.GetItemAsync(cacheKey, typeof(TableBuilderResultViewModel))) @@ -456,38 +543,34 @@ await TestApp.AddTestData(context => var dataBlockService = new Mock(Strict); dataBlockService - .Setup(s => s.GetDataBlockTableResult(ReleaseVersionId, DataBlockId, null)) + .Setup(s => s.GetDataBlockTableResult(releaseVersion.Id, dataBlockId, null)) .ReturnsAsync(_tableBuilderResults); - var releaseVersionRepository = new Mock(Strict); - - releaseVersionRepository - .Setup(s => s.GetLatestPublishedReleaseVersion(PublicationId, default)) - .ReturnsAsync(latestReleaseVersion); - var client = SetupApp( - dataBlockService: dataBlockService.Object, - releaseVersionRepository: releaseVersionRepository.Object + dataBlockService: dataBlockService.Object ) .CreateClient(); - var response = await client.GetAsync($"/api/tablebuilder/fast-track/{DataBlockParentId}"); + var response = await client.GetAsync($"/api/tablebuilder/fast-track/{dataBlockParent.Id}"); - VerifyAllMocks(BlobCacheService, dataBlockService, releaseVersionRepository); + VerifyAllMocks( + BlobCacheService, + dataBlockService + ); var viewModel = response.AssertOk(); - Assert.Equal(DataBlockParentId, viewModel.DataBlockParentId); - Assert.Equal(ReleaseVersionId, viewModel.ReleaseId); - Assert.Equal(ReleaseVersion.Slug, viewModel.ReleaseSlug); - Assert.Equal(ReleaseVersion.Type, viewModel.ReleaseType); + Assert.Equal(dataBlockParent.Id, viewModel.DataBlockParentId); + Assert.Equal(releaseVersion.Id, viewModel.ReleaseId); + Assert.Equal(release.Slug, viewModel.ReleaseSlug); + Assert.Equal(releaseVersion.Type, viewModel.ReleaseType); viewModel.Configuration.AssertDeepEqualTo(TableConfiguration); viewModel.FullTable.AssertDeepEqualTo(_tableBuilderResults); Assert.True(viewModel.LatestData); - Assert.Equal("Academic year 2020/21", viewModel.LatestReleaseTitle); + Assert.Equal(release.Title, viewModel.LatestReleaseTitle); var queryViewModel = viewModel.Query; Assert.NotNull(queryViewModel); - Assert.Equal(PublicationId, queryViewModel.PublicationId); + Assert.Equal(publication.Id, queryViewModel.PublicationId); Assert.Equal(FullTableQuery.SubjectId, viewModel.Query.SubjectId); Assert.Equal(FullTableQuery.TimePeriod, viewModel.Query.TimePeriod); Assert.Equal(FullTableQuery.Filters, viewModel.Query.Filters); @@ -498,14 +581,29 @@ await TestApp.AddTestData(context => [Fact] public async Task QueryForFastTrack_DataBlockNotYetPublished() { + Publication publication = _dataFixture.DefaultPublication() + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1)]); + + var release = publication.Releases.Single(); + var releaseVersion = release.Versions.Single(); + + DataBlockParent dataBlockParentWithNoPublishedVersion = _dataFixture + .DefaultDataBlockParent() + .WithLatestDraftVersion(_dataFixture + .DefaultDataBlockVersion() + .WithReleaseVersion(releaseVersion)); + await TestApp.AddTestData(context => - context.DataBlockParents.Add(DataBlockParentWithNoPublishedVersion)); + { + context.Publications.Add(publication); + context.DataBlockParents.Add(dataBlockParentWithNoPublishedVersion); + }); var client = SetupApp() .CreateClient(); var response = - await client.GetAsync($"/api/tablebuilder/fast-track/{DataBlockParentWithNoPublishedVersion.Id}"); + await client.GetAsync($"/api/tablebuilder/fast-track/{dataBlockParentWithNoPublishedVersion.Id}"); VerifyAllMocks(BlobCacheService); @@ -515,19 +613,43 @@ await TestApp.AddTestData(context => [Fact] public async Task QueryForFastTrack_NotLatestRelease() { - await TestApp.AddTestData(context => - context.DataBlockParents.Add(DataBlockParent)); + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases([ + _dataFixture + .DefaultRelease(publishedVersions: 1, year: 2020), + _dataFixture + .DefaultRelease(publishedVersions: 1, year: 2021) + ]); + + var release2020 = publication.Releases.Single(r => r.Year == 2020); + var release2021 = publication.Releases.Single(r => r.Year == 2021); + + // Release version is from the 2020 release which is not the latest release for the publication + var releaseVersion = release2020.Versions.Single(); + + DataBlockParent dataBlockParent = _dataFixture + .DefaultDataBlockParent() + .WithLatestPublishedVersion(_dataFixture + .DefaultDataBlockVersion() + .WithReleaseVersion(releaseVersion) + .WithDates(published: DateTime.UtcNow.AddDays(-1)) + .WithQuery(FullTableQuery) + .WithTable(TableConfiguration) + .WithCharts(Charts)); + + var dataBlockId = dataBlockParent.LatestPublishedVersion!.Id; - var latestReleaseVersion = new ReleaseVersion + await TestApp.AddTestData(context => { - Id = Guid.NewGuid(), - ReleaseName = "2021", - TimePeriodCoverage = AcademicYear - }; + context.Publications.Add(publication); + context.DataBlockParents.Add(dataBlockParent); + }); - var cacheKey = new DataBlockTableResultCacheKey(publicationSlug: ReleaseVersion.Publication.Slug, - releaseSlug: ReleaseVersion.Slug, - DataBlockParentId); + var cacheKey = new DataBlockTableResultCacheKey( + publicationSlug: publication.Slug, + releaseSlug: release2020.Slug, + dataBlockParent.Id); BlobCacheService .Setup(s => s.GetItemAsync(cacheKey, typeof(TableBuilderResultViewModel))) @@ -540,37 +662,32 @@ await TestApp.AddTestData(context => var dataBlockService = new Mock(Strict); dataBlockService - .Setup(s => s.GetDataBlockTableResult(ReleaseVersionId, DataBlockId, null)) + .Setup(s => s.GetDataBlockTableResult(releaseVersion.Id, dataBlockId, null)) .ReturnsAsync(_tableBuilderResults); - var releaseVersionRepository = new Mock(Strict); - - releaseVersionRepository - .Setup(s => s.GetLatestPublishedReleaseVersion(PublicationId, default)) - .ReturnsAsync(latestReleaseVersion); - var client = SetupApp( - dataBlockService: dataBlockService.Object, - releaseVersionRepository: releaseVersionRepository.Object + dataBlockService: dataBlockService.Object ) .CreateClient(); - var response = await client.GetAsync($"/api/tablebuilder/fast-track/{DataBlockParentId}"); + var response = await client.GetAsync($"/api/tablebuilder/fast-track/{dataBlockParent.Id}"); - VerifyAllMocks(BlobCacheService, dataBlockService, releaseVersionRepository); + VerifyAllMocks( + BlobCacheService, + dataBlockService + ); var viewModel = response.AssertOk(); - Assert.Equal(DataBlockParentId, viewModel.DataBlockParentId); - Assert.Equal(ReleaseVersionId, viewModel.ReleaseId); - Assert.Equal(ReleaseVersion.Slug, viewModel.ReleaseSlug); - Assert.Equal(ReleaseVersion.Type, viewModel.ReleaseType); + Assert.Equal(dataBlockParent.Id, viewModel.DataBlockParentId); + Assert.Equal(releaseVersion.Id, viewModel.ReleaseId); + Assert.Equal(release2020.Slug, viewModel.ReleaseSlug); + Assert.Equal(releaseVersion.Type, viewModel.ReleaseType); Assert.False(viewModel.LatestData); - Assert.Equal("Academic year 2021/22", viewModel.LatestReleaseTitle); + Assert.Equal(release2021.Title, viewModel.LatestReleaseTitle); } private WebApplicationFactory SetupApp( IDataBlockService? dataBlockService = null, - IReleaseVersionRepository? releaseVersionRepository = null, ITableBuilderService? tableBuilderService = null) { return TestApp @@ -580,8 +697,6 @@ private WebApplicationFactory SetupApp( services.ReplaceService(BlobCacheService); services.AddTransient(_ => dataBlockService ?? Mock.Of(Strict)); - services.AddTransient(_ => - releaseVersionRepository ?? Mock.Of(Strict)); services.AddTransient(_ => tableBuilderService ?? Mock.Of(Strict)); } ); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Controllers/TableBuilderController.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Controllers/TableBuilderController.cs index 6f76f1c2af..6f6e13bac0 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Controllers/TableBuilderController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Controllers/TableBuilderController.cs @@ -1,12 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Cache; using GovUk.Education.ExploreEducationStatistics.Common.Cancellation; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Model; using GovUk.Education.ExploreEducationStatistics.Common.Requests; -using GovUk.Education.ExploreEducationStatistics.Common.Utils; using GovUk.Education.ExploreEducationStatistics.Content.Model; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; using GovUk.Education.ExploreEducationStatistics.Data.Api.Cache; using GovUk.Education.ExploreEducationStatistics.Data.Api.Services.Interfaces; using GovUk.Education.ExploreEducationStatistics.Data.Api.ViewModels; @@ -15,39 +18,22 @@ using GovUk.Education.ExploreEducationStatistics.Data.ViewModels.Meta; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using static GovUk.Education.ExploreEducationStatistics.Common.Cancellation.RequestTimeoutConfigurationKeys; namespace GovUk.Education.ExploreEducationStatistics.Data.Api.Controllers { [Route("api")] [ApiController] - public class TableBuilderController : ControllerBase + public class TableBuilderController( + ContentDbContext contextDbContext, + IDataBlockService dataBlockService, + ITableBuilderService tableBuilderService) + : ControllerBase { // Change this whenever there is a breaking change // that requires cache invalidation. public const string ApiVersion = "1"; - private readonly IPersistenceHelper _contentPersistenceHelper; - private readonly IDataBlockService _dataBlockService; - private readonly IReleaseVersionRepository _releaseVersionRepository; - private readonly ITableBuilderService _tableBuilderService; - - public TableBuilderController( - IPersistenceHelper contentPersistenceHelper, - IDataBlockService dataBlockService, - IReleaseVersionRepository releaseVersionRepository, - ITableBuilderService tableBuilderService) - { - _contentPersistenceHelper = contentPersistenceHelper; - _dataBlockService = dataBlockService; - _releaseVersionRepository = releaseVersionRepository; - _tableBuilderService = tableBuilderService; - } - [HttpPost("tablebuilder")] [Produces("application/json", "text/csv")] [CancellationTokenTimeout(TableBuilderQuery)] @@ -59,7 +45,7 @@ public async Task Query( { Response.ContentDispositionAttachment(ContentTypes.Csv); - return await _tableBuilderService.QueryToCsvStream( + return await tableBuilderService.QueryToCsvStream( query: request.AsFullTableQuery(), stream: Response.BodyWriter.AsStream(), cancellationToken: cancellationToken @@ -67,7 +53,7 @@ public async Task Query( .HandleFailuresOrNoOp(); } - return await _tableBuilderService + return await tableBuilderService .Query(request.AsFullTableQuery(), cancellationToken) .HandleFailuresOr(Ok); } @@ -86,7 +72,7 @@ public async Task Query( contentType: ContentTypes.Csv, filename: $"{releaseVersionId}.csv"); - return await _tableBuilderService.QueryToCsvStream( + return await tableBuilderService.QueryToCsvStream( releaseVersionId: releaseVersionId, query: request.AsFullTableQuery(), stream: Response.BodyWriter.AsStream(), @@ -95,7 +81,7 @@ public async Task Query( .HandleFailuresOrNoOp(); } - return await _tableBuilderService + return await tableBuilderService .Query(releaseVersionId, request.AsFullTableQuery(), boundaryLevelId: null, cancellationToken) .HandleFailuresOr(Ok); } @@ -147,9 +133,14 @@ public async Task> QueryForFastTrack(Guid dataB { return await GetLatestPublishedDataBlockVersion(dataBlockParentId) .OnSuccessCombineWith(dataBlockVersion => GetDataBlockTableResult(dataBlockVersion)) - .OnSuccessCombineWith(tuple => - _releaseVersionRepository.GetLatestPublishedReleaseVersion(tuple.Item1.ReleaseVersion.PublicationId) - .OrNotFound()) + .OnSuccessCombineWith(async tuple => + { + var (dataBlockVersion, _) = tuple; + return await contextDbContext.Publications + .Where(p => p.Id == dataBlockVersion.ReleaseVersion.PublicationId) + .Select(p => p.LatestPublishedReleaseVersion) + .SingleOrNotFoundAsync(); + }) .OnSuccess(tuple => { var (dataBlockVersion, tableResult, latestReleaseVersion) = tuple; @@ -163,7 +154,7 @@ private Task> GetDataBlockTabl DataBlockVersion dataBlockVersion, long? boundaryLevelId = null) { - return _dataBlockService.GetDataBlockTableResult( + return dataBlockService.GetDataBlockTableResult( releaseVersionId: dataBlockVersion.ReleaseVersionId, dataBlockVersionId: dataBlockVersion.Id, boundaryLevelId); @@ -174,7 +165,7 @@ private Task> GetLatestPublishedDataBlockVersion(Guid dataBlockParentId) + private async Task> GetLatestPublishedDataBlockVersion( + Guid dataBlockParentId) { - return _contentPersistenceHelper - .CheckEntityExists(dataBlockParentId, q => q - .Include(dataBlockParent => dataBlockParent.LatestPublishedVersion) - .ThenInclude(dataBlockVersion => dataBlockVersion.ReleaseVersion) - .ThenInclude(releaseVersion => releaseVersion.Publication)) - .OnSuccess(dataBlock => dataBlock.LatestPublishedVersion) + return await contextDbContext.DataBlockParents + .Include(dbp => dbp.LatestPublishedVersion) + .ThenInclude(dbv => dbv.ReleaseVersion) + .ThenInclude(rv => rv.Publication) + .SingleOrNotFoundAsync(dbp => dbp.Id == dataBlockParentId) + .OnSuccess(dbp => dbp.LatestPublishedVersion) .OrNotFound(); } } From 2d04ca5559609378f62fe659fe0424fa4f8bcc5f Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Mon, 16 Dec 2024 17:43:05 +0000 Subject: [PATCH 16/21] EES-5656 On completing publishing, set the latest published release version for publication based on the latest release with a published version --- .../ReleaseVersionRepositoryTests.cs | 133 +++--------------- .../Interfaces/IReleaseVersionRepository.cs | 10 -- .../Repository/ReleaseVersionRepository.cs | 10 -- .../Services/PublishingCompletionService.cs | 41 ++++-- 4 files changed, 49 insertions(+), 145 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs index d4446fa2cc..3d410fc849 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs @@ -21,92 +21,6 @@ public class GetLatestPublishedReleaseVersionTests : ReleaseVersionRepositoryTes { [Fact] public async Task Success() - { - var publications = _dataFixture - .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2022), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2021), - _dataFixture - .DefaultRelease(publishedVersions: 2, year: 2020))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - var result = await repository.GetLatestPublishedReleaseVersion(publications[0].Id); - - // Expect the result to be the latest published version taken from releases of the specified publication in - // reverse chronological order - var expectedReleaseVersion = publications[0].ReleaseVersions - .Single(rv => rv is { Published: not null, Year: 2021, Version: 1 }); - - Assert.NotNull(result); - Assert.Equal(expectedReleaseVersion.Id, result.Id); - } - - [Fact] - public async Task PublicationHasNoPublishedReleaseVersions_ReturnsNull() - { - var publications = _dataFixture - .DefaultPublication() - // Index 0 has an unpublished release version - // Index 1 has a published release version - .ForIndex(0, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true) - .Generate(1))) - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publications[0].Id)); - } - - [Fact] - public async Task PublicationHasNoReleaseVersions_ReturnsNull() - { - var publications = _dataFixture - .DefaultPublication() - // Index 0 has no release versions - // Index 1 has a published release version - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publications[0].Id)); - } - - [Fact] - public async Task PublicationDoesNotExist_ReturnsNull() - { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); - - var contextId = await AddTestData(publication); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.Null(await repository.GetLatestPublishedReleaseVersion(Guid.NewGuid())); - } - - [Fact] - public async Task SpecificReleaseSlug_Success() { var publications = _dataFixture .DefaultPublication() @@ -121,7 +35,7 @@ public async Task SpecificReleaseSlug_Success() await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.GetLatestPublishedReleaseVersion(publications[0].Id, "2021-22"); + var result = await repository.GetLatestPublishedReleaseVersion(publications[0].Id, releaseSlug: "2021-22"); // Expect the result to be the latest published version for the 2021-22 release of the specified publication var expectedReleaseVersion = publications[0].ReleaseVersions @@ -132,76 +46,70 @@ public async Task SpecificReleaseSlug_Success() } [Fact] - public async Task SpecificReleaseSlug_PublicationHasNoPublishedReleaseVersions_ReturnsNull() + public async Task PublicationHasNoPublishedReleaseVersions_ReturnsNull() { var publications = _dataFixture .DefaultPublication() // Index 0 has an unpublished release version // Index 1 has a published release version - .ForIndex(0, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021) - .Generate(1))) - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1, year: 2021) - .Generate(1))) + .ForIndex(0, p => p.SetReleases([ + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021) + ])) + .ForIndex(1, p => p.SetReleases([ + _dataFixture.DefaultRelease(publishedVersions: 1, year: 2021) + ])) .GenerateList(2); var contextId = await AddTestData(publications); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publications[0].Id, "2021-22")); + Assert.Null(await repository.GetLatestPublishedReleaseVersion(publications[0].Id, releaseSlug: "2021-22")); } [Fact] - public async Task SpecificReleaseSlug_PublicationHasNoPublishedReleaseVersionsMatchingSlug_ReturnsNull() + public async Task PublicationHasNoPublishedReleaseVersionsMatchingSlug_ReturnsNull() { Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1, year: 2020) - .Generate(1)); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1, year: 2020)]); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publication.Id, "2021-22")); + Assert.Null(await repository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug: "2021-22")); } [Fact] - public async Task SpecificReleaseSlug_PublicationHasNoReleaseVersions_ReturnsNull() + public async Task PublicationHasNoReleaseVersions_ReturnsNull() { var publications = _dataFixture .DefaultPublication() // Index 0 has no release versions // Index 1 has a published release version - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1, year: 2021) - .Generate(1))) + .ForIndex(1, p => p.SetReleases([_dataFixture.DefaultRelease(publishedVersions: 1, year: 2021)])) .GenerateList(2); var contextId = await AddTestData(publications); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publications[0].Id, "2021-22")); + Assert.Null(await repository.GetLatestPublishedReleaseVersion(publications[0].Id, releaseSlug: "2021-22")); } [Fact] - public async Task SpecificReleaseSlug_PublicationDoesNotExist_ReturnsNull() + public async Task PublicationDoesNotExist_ReturnsNull() { Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1, year: 2021) - .Generate(1)); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1, year: 2021)]); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(Guid.NewGuid(), "2021-22")); + Assert.Null(await repository.GetLatestPublishedReleaseVersion(Guid.NewGuid(), releaseSlug: "2021-22")); } } @@ -243,9 +151,8 @@ public async Task PublicationHasNoReleaseVersions_ReturnsNull() .DefaultPublication() // Index 0 has no release versions // Index 1 has a release version - .ForIndex(1, p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) + .ForIndex(1, p => p.SetReleases([ + _dataFixture.DefaultRelease(publishedVersions: 1)])) .GenerateList(2); var contextId = await AddTestData(publications); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs index 8cc5dd3d21..8a01061242 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs @@ -12,16 +12,6 @@ Task GetPublishedDate( Guid releaseVersionId, DateTime actualPublishedDate); - /// - /// Retrieves the latest published version from all releases in reverse chronological order that are associated with a publication. - /// - /// The unique identifier of the publication. - /// A to observe while waiting for the task to complete. - /// The latest published version from all releases in reverse chronological order that are associated with a publication. - Task GetLatestPublishedReleaseVersion( - Guid publicationId, - CancellationToken cancellationToken = default); - /// /// Retrieves the latest published version of a release matching a given slug associated with a publication. /// diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs index 6a167df5e3..f07444f434 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs @@ -54,16 +54,6 @@ await _contentDbContext.Entry(releaseVersion) return releaseVersion.PreviousVersion.Published.Value; } - public async Task GetLatestPublishedReleaseVersion( - Guid publicationId, - CancellationToken cancellationToken = default) - { - return (await _contentDbContext.ReleaseVersions.LatestReleaseVersions(publicationId, publishedOnly: true) - .ToListAsync(cancellationToken: cancellationToken)) - .OrderByReverseChronologicalOrder() - .FirstOrDefault(); - } - public async Task GetLatestReleaseVersion( Guid publicationId, CancellationToken cancellationToken = default) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs index 2269a2b936..48728b8416 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Predicates; using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces.Cache; using GovUk.Education.ExploreEducationStatistics.Publisher.Model; using GovUk.Education.ExploreEducationStatistics.Publisher.Services.Interfaces; @@ -18,7 +18,6 @@ public class PublishingCompletionService( INotificationsService notificationsService, IReleasePublishingStatusService releasePublishingStatusService, IPublicationCacheService publicationCacheService, - IReleaseVersionRepository releaseVersionRepository, IReleaseService releaseService, IRedirectsCacheService redirectsCacheService, IDataSetPublishingService dataSetPublishingService) @@ -29,10 +28,7 @@ public async Task CompletePublishingIfAllPriorStagesComplete( { var releaseStatuses = await releasePublishingKeys .ToAsyncEnumerable() - .SelectAwait(async key => - { - return await releasePublishingStatusService.Get(key); - }) + .SelectAwait(async key => await releasePublishingStatusService.Get(key)) .ToListAsync(); var prePublishingStagesComplete = releaseStatuses @@ -72,7 +68,6 @@ await releaseVersionIdsToUpdate foreach (var methodologyVersion in methodologyVersions) { - // WARN: This must be called before PublicationRepository#UpdateLatestPublishedRelease if (await methodologyService.IsBeingPublishedAlongsideRelease(methodologyVersion, releaseVersion)) { await methodologyService.Publish(methodologyVersion); @@ -89,7 +84,7 @@ await releaseVersionIdsToUpdate await directlyRelatedPublicationIds .ToAsyncEnumerable() - .ForEachAwaitAsync(UpdateLatestPublishedRelease); + .ForEachAwaitAsync(UpdateLatestPublishedReleaseVersionForPublication); // Update the cached publication and any cached superseded publications. // If this is the first live release of the publication, the superseding is now enforced @@ -124,14 +119,36 @@ await releasePublishingStatusService .UpdatePublishingStage(status.AsTableRowKey(), ReleasePublishingStatusPublishingStage.Complete)); } - private async Task UpdateLatestPublishedRelease(Guid publicationId) + private async Task UpdateLatestPublishedReleaseVersionForPublication(Guid publicationId) { var publication = await contentDbContext.Publications .SingleAsync(p => p.Id == publicationId); - var latestPublishedReleaseVersion = - await releaseVersionRepository.GetLatestPublishedReleaseVersion(publicationId); - publication.LatestPublishedReleaseVersionId = latestPublishedReleaseVersion!.Id; + // Get the publications release id's by the order they appear in the release series + var releaseSeriesReleaseIds = publication.ReleaseSeries + .Where(rsi => !rsi.IsLegacyLink) + .Select(rs => rs.ReleaseId!.Value) + .ToList(); + + // Work out the publication's new latest published release version. + // This is the latest published version of the first release which has a published version + Guid? latestPublishedReleaseVersionId = null; + foreach (var releaseId in releaseSeriesReleaseIds) + { + latestPublishedReleaseVersionId = (await contentDbContext.ReleaseVersions + .LatestReleaseVersion(releaseId: releaseId, publishedOnly: true) + .SingleOrDefaultAsync())?.Id; + + if (latestPublishedReleaseVersionId != null) + { + break; + } + } + + publication.LatestPublishedReleaseVersionId = + latestPublishedReleaseVersionId ?? + throw new InvalidOperationException( + $"No latest published release version found for publication {publicationId}"); contentDbContext.Update(publication); await contentDbContext.SaveChangesAsync(); From f3012b1a60743c5929bb03d5169a7e1a7baf7786 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Tue, 17 Dec 2024 16:05:04 +0000 Subject: [PATCH 17/21] EES-5656 Refactor logic to extract release id's from a release series to be reusable. --- .../Extensions/ReleaseSeriesItemExtensions.cs | 23 +++++++++++++++++++ .../Services/PublishingCompletionService.cs | 6 ++--- 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/GovUk.Education.ExploreEducationStatistics.Content.Model/Extensions/ReleaseSeriesItemExtensions.cs diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Extensions/ReleaseSeriesItemExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Extensions/ReleaseSeriesItemExtensions.cs new file mode 100644 index 0000000000..3581291c11 --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Extensions/ReleaseSeriesItemExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; + +public static class ReleaseSeriesItemExtensions +{ + /// + /// Retrieves the release id's by the order they appear in a of type , + /// ignoring any legacy links. + /// + /// + /// A of type containing the release id's in the order + /// they appear in the release series. + public static IReadOnlyList ReleaseIds(this List releaseSeriesItems) + { + return releaseSeriesItems + .Where(rsi => !rsi.IsLegacyLink) + .Select(rs => rs.ReleaseId!.Value) + .ToList(); + } +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs index 48728b8416..9bd2031ff2 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; using GovUk.Education.ExploreEducationStatistics.Content.Model.Predicates; using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces.Cache; using GovUk.Education.ExploreEducationStatistics.Publisher.Model; @@ -125,10 +126,7 @@ private async Task UpdateLatestPublishedReleaseVersionForPublication(Guid public .SingleAsync(p => p.Id == publicationId); // Get the publications release id's by the order they appear in the release series - var releaseSeriesReleaseIds = publication.ReleaseSeries - .Where(rsi => !rsi.IsLegacyLink) - .Select(rs => rs.ReleaseId!.Value) - .ToList(); + var releaseSeriesReleaseIds = publication.ReleaseSeries.ReleaseIds(); // Work out the publication's new latest published release version. // This is the latest published version of the first release which has a published version From 9c873761b94c160a4eccf1151cd9b54d477536bc Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Wed, 18 Dec 2024 08:26:29 +0000 Subject: [PATCH 18/21] EES-5656 Update methods of ReleaseVersionRepository to use release series --- .../ReleaseVersionRepositoryTests.cs | 618 +++++++++--------- .../Extensions/ReleaseSeriesItemExtensions.cs | 26 +- .../Interfaces/IReleaseVersionRepository.cs | 8 +- .../Repository/ReleaseVersionRepository.cs | 41 +- 4 files changed, 375 insertions(+), 318 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs index 3d410fc849..88ecdddf47 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs @@ -7,7 +7,6 @@ using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository; using GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; using Xunit; -using static GovUk.Education.ExploreEducationStatistics.Common.Services.CollectionUtils; using static GovUk.Education.ExploreEducationStatistics.Common.Utils.ComparerUtils; using static GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Utils.ContentDbUtils; @@ -22,57 +21,54 @@ public class GetLatestPublishedReleaseVersionTests : ReleaseVersionRepositoryTes [Fact] public async Task Success() { - var publications = _dataFixture + Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 1, year: 2022), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2021))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases( + [ + _dataFixture.DefaultRelease(publishedVersions: 1, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2021) + ]); + + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.GetLatestPublishedReleaseVersion(publications[0].Id, releaseSlug: "2021-22"); + var result = await repository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug: "2021-22"); - // Expect the result to be the latest published version for the 2021-22 release of the specified publication - var expectedReleaseVersion = publications[0].ReleaseVersions - .Single(rv => rv is { Published: not null, Year: 2021, Version: 1 }); + // Expect the result to be the latest published version for the 2021-22 release + var expectedReleaseVersion = publication.Releases.Single(r => r is { Year: 2021 }).Versions[1]; Assert.NotNull(result); Assert.Equal(expectedReleaseVersion.Id, result.Id); } [Fact] - public async Task PublicationHasNoPublishedReleaseVersions_ReturnsNull() + public async Task MultiplePublications_ReturnsReleaseVersionAssociatedWithPublication() { - var publications = _dataFixture + var (publication1, publication2) = _dataFixture .DefaultPublication() - // Index 0 has an unpublished release version - // Index 1 has a published release version - .ForIndex(0, p => p.SetReleases([ - _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021) - ])) - .ForIndex(1, p => p.SetReleases([ - _dataFixture.DefaultRelease(publishedVersions: 1, year: 2021) - ])) - .GenerateList(2); + .WithReleases(_ => [_dataFixture.DefaultRelease(publishedVersions: 1, year: 2021)]) + .GenerateTuple2(); - var contextId = await AddTestData(publications); + var contextId = await AddTestData(publication1, publication2); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publications[0].Id, releaseSlug: "2021-22")); + var result = await repository.GetLatestPublishedReleaseVersion(publication1.Id, releaseSlug: "2021-22"); + + // Expect the result to be from the specified publication + var expectedReleaseVersion = publication1.Releases.Single().Versions.Single(); + + Assert.NotNull(result); + Assert.Equal(expectedReleaseVersion.Id, result.Id); } [Fact] - public async Task PublicationHasNoPublishedReleaseVersionsMatchingSlug_ReturnsNull() + public async Task PublicationHasNoPublishedReleaseVersions_ReturnsNull() { Publication publication = _dataFixture .DefaultPublication() - .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1, year: 2020)]); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021)]); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); @@ -82,34 +78,38 @@ public async Task PublicationHasNoPublishedReleaseVersionsMatchingSlug_ReturnsNu } [Fact] - public async Task PublicationHasNoReleaseVersions_ReturnsNull() + public async Task PublicationHasNoPublishedReleaseVersionsMatchingSlug_ReturnsNull() { - var publications = _dataFixture + Publication publication = _dataFixture .DefaultPublication() - // Index 0 has no release versions - // Index 1 has a published release version - .ForIndex(1, p => p.SetReleases([_dataFixture.DefaultRelease(publishedVersions: 1, year: 2021)])) - .GenerateList(2); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1, year: 2020)]); - var contextId = await AddTestData(publications); + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publications[0].Id, releaseSlug: "2021-22")); + Assert.Null(await repository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug: "2021-22")); } [Fact] - public async Task PublicationDoesNotExist_ReturnsNull() + public async Task PublicationHasNoReleaseVersions_ReturnsNull() { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 1, year: 2021)]); + Publication publication = _dataFixture.DefaultPublication(); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(Guid.NewGuid(), releaseSlug: "2021-22")); + Assert.Null(await repository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug: "2021-22")); + } + + [Fact] + public async Task PublicationDoesNotExist_ReturnsNull() + { + var repository = BuildRepository(); + + Assert.Null(await repository.GetLatestPublishedReleaseVersion(publicationId: Guid.NewGuid(), + releaseSlug: "2021-22")); } } @@ -118,64 +118,68 @@ public class GetLatestReleaseVersionTests : ReleaseVersionRepositoryTests [Fact] public async Task Success() { - var publications = _dataFixture + Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2022), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2021), - _dataFixture - .DefaultRelease(publishedVersions: 2, year: 2020))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases( + [ + _dataFixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + _dataFixture.DefaultRelease(publishedVersions: 2, year: 2020) + ]) + .FinishWith(p => p.ReleaseSeries = GenerateReleaseSeries(p.Releases, 2021, 2020, 2022)); + + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.GetLatestReleaseVersion(publications[0].Id); + var result = await repository.GetLatestReleaseVersion(publication.Id); - // Expect the result to be the latest version taken from releases of the specified publication in - // reverse chronological order - var expectedReleaseVersion = publications[0].ReleaseVersions - .Single(rv => rv is { Year: 2022, Version: 0 }); + // Expect the result to be the latest version of the latest release in the release series + var expectedReleaseVersion = publication.Releases.Single(r => r is { Year: 2021 }).Versions[0]; Assert.NotNull(result); Assert.Equal(expectedReleaseVersion.Id, result.Id); } [Fact] - public async Task PublicationHasNoReleaseVersions_ReturnsNull() + public async Task MultiplePublications_ReturnsReleaseVersionAssociatedWithPublication() { - var publications = _dataFixture + var (publication1, publication2) = _dataFixture .DefaultPublication() - // Index 0 has no release versions - // Index 1 has a release version - .ForIndex(1, p => p.SetReleases([ - _dataFixture.DefaultRelease(publishedVersions: 1)])) - .GenerateList(2); + .WithReleases(_ => [_dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true)]) + .GenerateTuple2(); - var contextId = await AddTestData(publications); + var contextId = await AddTestData(publication1, publication2); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestReleaseVersion(publications[0].Id)); + var result = await repository.GetLatestReleaseVersion(publication1.Id); + + // Expect the result to be from the specified publication + var expectedReleaseVersion = publication1.Releases.Single().Versions.Single(); + + Assert.NotNull(result); + Assert.Equal(expectedReleaseVersion.Id, result.Id); } [Fact] - public async Task PublicationDoesNotExist_ReturnsNull() + public async Task PublicationHasNoReleaseVersions_ReturnsNull() { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + Publication publication = _dataFixture.DefaultPublication(); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestReleaseVersion(Guid.NewGuid())); + Assert.Null(await repository.GetLatestReleaseVersion(publication.Id)); + } + + [Fact] + public async Task PublicationDoesNotExist_ReturnsNull() + { + var repository = BuildRepository(); + + Assert.Null(await repository.GetLatestReleaseVersion(publicationId: Guid.NewGuid())); } } @@ -186,20 +190,18 @@ public async Task Success() { Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true) - .Generate(1)); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 2, draftVersion: true)]); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var releaseVersions = publication.ReleaseVersions; + var release = publication.Releases.Single(); - // Expect only the highest published version of the release to be the latest - Assert.False(await repository.IsLatestPublishedReleaseVersion(releaseVersions[0].Id)); - Assert.True(await repository.IsLatestPublishedReleaseVersion(releaseVersions[1].Id)); - Assert.False(await repository.IsLatestPublishedReleaseVersion(releaseVersions[2].Id)); + // Expect only the latest published version of the release to be returned as the latest + Assert.False(await repository.IsLatestPublishedReleaseVersion(release.Versions[0].Id)); + Assert.True(await repository.IsLatestPublishedReleaseVersion(release.Versions[1].Id)); + Assert.False(await repository.IsLatestPublishedReleaseVersion(release.Versions[2].Id)); } [Fact] @@ -207,15 +209,13 @@ public async Task ReleaseHasNoPublishedReleaseVersions_ReturnsFalse() { Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true) - .Generate(1)); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true)]); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var releaseVersion = publication.ReleaseVersions.Single(); + var releaseVersion = publication.Releases.Single().Versions.Single(); Assert.False(await repository.IsLatestPublishedReleaseVersion(releaseVersion.Id)); } @@ -223,17 +223,9 @@ public async Task ReleaseHasNoPublishedReleaseVersions_ReturnsFalse() [Fact] public async Task ReleaseVersionDoesNotExist_ReturnsFalse() { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + var repository = BuildRepository(); - var contextId = await AddTestData(publication); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); - - Assert.False(await repository.IsLatestPublishedReleaseVersion(Guid.NewGuid())); + Assert.False(await repository.IsLatestPublishedReleaseVersion(releaseVersionId: Guid.NewGuid())); } } @@ -244,36 +236,26 @@ public async Task Success() { Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true) - .Generate(1)); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 2, draftVersion: true)]); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var releaseVersions = publication.ReleaseVersions; + var release = publication.Releases.Single(); - // Expect only the highest version of the release to be the latest - Assert.False(await repository.IsLatestReleaseVersion(releaseVersions[0].Id)); - Assert.False(await repository.IsLatestReleaseVersion(releaseVersions[1].Id)); - Assert.True(await repository.IsLatestReleaseVersion(releaseVersions[2].Id)); + // Expect only the latest draft version of the release to be returned as the latest + Assert.False(await repository.IsLatestReleaseVersion(release.Versions[0].Id)); + Assert.False(await repository.IsLatestReleaseVersion(release.Versions[1].Id)); + Assert.True(await repository.IsLatestReleaseVersion(release.Versions[2].Id)); } [Fact] public async Task ReleaseVersionDoesNotExist_ReturnsFalse() { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); - - var contextId = await AddTestData(publication); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); + var repository = BuildRepository(); - Assert.False(await repository.IsLatestReleaseVersion(Guid.NewGuid())); + Assert.False(await repository.IsLatestReleaseVersion(releaseVersionId: Guid.NewGuid())); } } @@ -284,29 +266,56 @@ public class AnyPublishedStateTests : ListLatestReleaseVersionIdsTests [Fact] public async Task Success() { - var publications = _dataFixture + Publication publication = _dataFixture + .DefaultPublication() + .WithReleases( + [ + _dataFixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + _dataFixture.DefaultRelease(publishedVersions: 2, year: 2020) + ]) + .FinishWith(p => p.ReleaseSeries = GenerateReleaseSeries(p.Releases, 2021, 2020, 2022)); + + var contextId = await AddTestData(publication); + await using var contentDbContext = InMemoryContentDbContext(contextId); + var repository = BuildRepository(contentDbContext); + + var result = await ListLatestReleaseVersions(repository, publication.Id); + + // Expect the latest versions of each release, ordered by release series + Guid[] expectedReleaseVersionIds = + [ + publication.Releases.Single(r => r is { Year: 2021 }).Versions[0].Id, + publication.Releases.Single(r => r is { Year: 2020 }).Versions[1].Id, + publication.Releases.Single(r => r is { Year: 2022 }).Versions[2].Id + ]; + + Assert.Equal(expectedReleaseVersionIds, result.Select(rv => rv.Id)); + } + + [Fact] + public async Task MultiplePublications_ReturnsReleaseVersionsAssociatedWithPublication() + { + var (publication1, publication2) = _dataFixture .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases(_ => + [ + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021) + ]) + .GenerateTuple2(); + + var contextId = await AddTestData(publication1, publication2); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: false); + var result = await ListLatestReleaseVersions(repository, publication1.Id); - // Expect the result to contain the highest version of each release for the specified publication + // Expect the results to be from the specified publication AssertIdsAreEqualIgnoringOrder( [ - publications[0].ReleaseVersions[0].Id, - publications[0].ReleaseVersions[3].Id, - publications[0].ReleaseVersions[5].Id + publication1.Releases.Single(r => r is { Year: 2022 }).Versions[0].Id, + publication1.Releases.Single(r => r is { Year: 2021 }).Versions[0].Id ], result); } @@ -314,68 +323,85 @@ public async Task Success() [Fact] public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() { - var publications = _dataFixture - .DefaultPublication() - // Index 0 has no release versions - // Index 1 has a published release version - .ForIndex(1, - p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + Publication publication = _dataFixture.DefaultPublication(); + + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty(await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: false)); + Assert.Empty(await ListLatestReleaseVersions(repository, publication.Id)); } [Fact] public async Task PublicationDoesNotExist_ReturnsEmpty() + { + var repository = BuildRepository(); + + Assert.Empty(await ListLatestReleaseVersions(repository, publicationId: Guid.NewGuid())); + } + + private static async Task> ListLatestReleaseVersions( + ReleaseVersionRepository repository, + Guid publicationId) + { + return await repository.ListLatestReleaseVersions(publicationId, publishedOnly: false); + } + } + + public class PublishedOnlyTests : ListLatestReleaseVersionsTests + { + [Fact] + public async Task Success() { Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + .WithReleases( + [ + _dataFixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + _dataFixture.DefaultRelease(publishedVersions: 2, year: 2020) + ]) + .FinishWith(p => p.ReleaseSeries = GenerateReleaseSeries(p.Releases, 2021, 2020, 2022)); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty(await repository.ListLatestReleaseVersions(publicationId: Guid.NewGuid(), - publishedOnly: false)); + var result = await ListLatestReleaseVersions(repository, publication.Id); + + // Expect the latest published version of each release, ordered by release series + Guid[] expectedReleaseVersionIds = + [ + publication.Releases.Single(r => r is { Year: 2020 }).Versions[1].Id, + publication.Releases.Single(r => r is { Year: 2022 }).Versions[1].Id + ]; + + Assert.Equal(expectedReleaseVersionIds, result.Select(rv => rv.Id)); } - } - public class PublishedOnlyTests : ListLatestReleaseVersionsTests - { [Fact] - public async Task Success() + public async Task MultiplePublications_ReturnsReleaseVersionsAssociatedWithPublication() { - var publications = _dataFixture + var (publication1, publication2) = _dataFixture .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases(_ => + [ + _dataFixture.DefaultRelease(publishedVersions: 1, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 1, year: 2021) + ]) + .GenerateTuple2(); + + var contextId = await AddTestData(publication1, publication2); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: true); + var result = await ListLatestReleaseVersions(repository, publication1.Id); - // Expect the result to contain the highest published version of each release for the specified publication + // Expect the results to be from the specified publication AssertIdsAreEqualIgnoringOrder( [ - publications[0].ReleaseVersions[2].Id, - publications[0].ReleaseVersions[5].Id + publication1.Releases.Single(r => r is { Year: 2022 }).Versions[0].Id, + publication1.Releases.Single(r => r is { Year: 2021 }).Versions[0].Id ], result); } @@ -383,62 +409,42 @@ public async Task Success() [Fact] public async Task PublicationHasNoPublishedReleaseVersions_ReturnsEmpty() { - var publications = _dataFixture + Publication publication = _dataFixture .DefaultPublication() - // Index 0 has an unpublished release version - // Index 1 has a published release version - .ForIndex(0, - p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true) - .Generate(1))) - .ForIndex(1, - p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true)]); + + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty(await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: true)); + Assert.Empty(await ListLatestReleaseVersions(repository, publication.Id)); } [Fact] public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() { - var publications = _dataFixture - .DefaultPublication() - // Index 0 has no release versions - // Index 1 has a published release version - .ForIndex(1, - p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + Publication publication = _dataFixture.DefaultPublication(); + + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty(await repository.ListLatestReleaseVersions(publications[0].Id, publishedOnly: true)); + Assert.Empty(await ListLatestReleaseVersions(repository, publication.Id)); } [Fact] public async Task PublicationDoesNotExist_ReturnsEmpty() { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + var repository = BuildRepository(); - var contextId = await AddTestData(publication); - await using var contentDbContext = InMemoryContentDbContext(contextId); - var repository = BuildRepository(contentDbContext); + Assert.Empty(await ListLatestReleaseVersions(repository, publicationId: Guid.NewGuid())); + } - Assert.Empty( - await repository.ListLatestReleaseVersions(publicationId: Guid.NewGuid(), publishedOnly: true)); + private static async Task> ListLatestReleaseVersions( + ReleaseVersionRepository repository, + Guid publicationId) + { + return await repository.ListLatestReleaseVersions(publicationId, publishedOnly: true); } } } @@ -450,163 +456,194 @@ public class AnyPublishedStateTests : ListLatestReleaseVersionIdsTests [Fact] public async Task Success() { - var publications = _dataFixture + Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases( + [ + _dataFixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + _dataFixture.DefaultRelease(publishedVersions: 2, year: 2020) + ]); + + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: false); + var result = await ListLatestReleaseVersionIds(repository, publication.Id); - // Expect the result to contain the highest version of each release for the specified publication + // Expect the latest version id's of each release AssertIdsAreEqualIgnoringOrder( [ - publications[0].ReleaseVersions[0].Id, - publications[0].ReleaseVersions[3].Id, - publications[0].ReleaseVersions[5].Id + publication.Releases.Single(r => r is { Year: 2022 }).Versions[2].Id, + publication.Releases.Single(r => r is { Year: 2021 }).Versions[0].Id, + publication.Releases.Single(r => r is { Year: 2020 }).Versions[1].Id ], result); } [Fact] - public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() + public async Task MultiplePublications_ReturnsReleaseVersionsAssociatedWithPublication() { - var publications = _dataFixture + var (publication1, publication2) = _dataFixture .DefaultPublication() - // Index 0 has no release versions - // Index 1 has a published release version - .ForIndex(1, - p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases(_ => + [ + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021) + ]) + .GenerateTuple2(); + + var contextId = await AddTestData(publication1, publication2); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty(await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: false)); + var result = await ListLatestReleaseVersionIds(repository, publication1.Id); + + // Expect the results to be from the specified publication + AssertIdsAreEqualIgnoringOrder( + [ + publication1.Releases.Single(r => r is { Year: 2022 }).Versions[0].Id, + publication1.Releases.Single(r => r is { Year: 2021 }).Versions[0].Id + ], + result); } [Fact] - public async Task PublicationDoesNotExist_ReturnsEmpty() + public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + Publication publication = _dataFixture.DefaultPublication(); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty(await repository.ListLatestReleaseVersionIds(publicationId: Guid.NewGuid(), - publishedOnly: false)); + Assert.Empty(await ListLatestReleaseVersionIds(repository, publication.Id)); + } + + [Fact] + public async Task PublicationDoesNotExist_ReturnsEmpty() + { + var repository = BuildRepository(); + + Assert.Empty(await ListLatestReleaseVersionIds(repository, publicationId: Guid.NewGuid())); + } + + private static async Task> ListLatestReleaseVersionIds( + ReleaseVersionRepository repository, + Guid publicationId) + { + return await repository.ListLatestReleaseVersionIds(publicationId, publishedOnly: false); } } - public class PublishedOnlyTrueTests : ListLatestReleaseVersionIdsTests + public class PublishedOnlyTests : ListLatestReleaseVersionIdsTests { [Fact] public async Task Success() { - var publications = _dataFixture + Publication publication = _dataFixture .DefaultPublication() - .WithReleases(_ => ListOf( - _dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2, draftVersion: true), - _dataFixture - .DefaultRelease(publishedVersions: 2))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases( + [ + _dataFixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + _dataFixture.DefaultRelease(publishedVersions: 2, year: 2020) + ]); + + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: true); + var result = await ListLatestReleaseVersionIds(repository, publication.Id); - // Expect the result to contain the highest published version of each release for the specified publication + // Expect the latest published version id's of each release AssertIdsAreEqualIgnoringOrder( [ - publications[0].ReleaseVersions[2].Id, - publications[0].ReleaseVersions[5].Id + publication.Releases.Single(r => r is { Year: 2022 }).Versions[1].Id, + publication.Releases.Single(r => r is { Year: 2020 }).Versions[1].Id ], result); } [Fact] - public async Task PublicationHasNoPublishedReleaseVersions_ReturnsEmpty() + public async Task MultiplePublications_ReturnsReleaseVersionsAssociatedWithPublication() { - var publications = _dataFixture + var (publication1, publication2) = _dataFixture .DefaultPublication() - // Index 0 has an unpublished release version - // Index 1 has a published release version - .ForIndex(0, - p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 0, draftVersion: true) - .Generate(1))) - .ForIndex(1, - p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); - - var contextId = await AddTestData(publications); + .WithReleases(_ => + [ + _dataFixture.DefaultRelease(publishedVersions: 1, year: 2022), + _dataFixture.DefaultRelease(publishedVersions: 1, year: 2021) + ]) + .GenerateTuple2(); + + var contextId = await AddTestData(publication1, publication2); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty(await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: true)); + var result = await ListLatestReleaseVersionIds(repository, publication1.Id); + + // Expect the results to be from the specified publication + AssertIdsAreEqualIgnoringOrder( + [ + publication1.Releases.Single(r => r is { Year: 2022 }).Versions[0].Id, + publication1.Releases.Single(r => r is { Year: 2021 }).Versions[0].Id + ], + result); } [Fact] - public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() + public async Task PublicationHasNoPublishedReleaseVersions_ReturnsEmpty() { - var publications = _dataFixture + Publication publication = _dataFixture .DefaultPublication() - .ForIndex(1, - p => p.SetReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1))) - .GenerateList(2); + .WithReleases([_dataFixture.DefaultRelease(publishedVersions: 0, draftVersion: true)]); - var contextId = await AddTestData(publications); + var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty(await repository.ListLatestReleaseVersionIds(publications[0].Id, publishedOnly: true)); + Assert.Empty(await ListLatestReleaseVersionIds(repository, publication.Id)); } [Fact] - public async Task PublicationDoesNotExist_ReturnsEmpty() + public async Task PublicationHasNoReleaseVersions_ReturnsEmpty() { - Publication publication = _dataFixture - .DefaultPublication() - .WithReleases(_dataFixture - .DefaultRelease(publishedVersions: 1) - .Generate(1)); + Publication publication = _dataFixture.DefaultPublication(); var contextId = await AddTestData(publication); await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Empty( - await repository.ListLatestReleaseVersionIds(publicationId: Guid.NewGuid(), publishedOnly: true)); + Assert.Empty(await ListLatestReleaseVersionIds(repository, publication.Id)); + } + + [Fact] + public async Task PublicationDoesNotExist_ReturnsEmpty() + { + var repository = BuildRepository(); + + Assert.Empty(await ListLatestReleaseVersionIds(repository, publicationId: Guid.NewGuid())); + } + + private static async Task> ListLatestReleaseVersionIds( + ReleaseVersionRepository repository, + Guid publicationId) + { + return await repository.ListLatestReleaseVersionIds(publicationId, publishedOnly: true); } } } + private List GenerateReleaseSeries(IReadOnlyList releases, params int[] years) + { + return years.Select(year => + { + var release = releases.Single(r => r.Year == year); + return _dataFixture.DefaultReleaseSeriesItem().WithReleaseId(release.Id).Generate(); + }).ToList(); + } + private static void AssertIdsAreEqualIgnoringOrder( IReadOnlyCollection expectedIds, IReadOnlyCollection actualReleaseVersions) @@ -639,11 +676,10 @@ private static async Task AddTestData(IReadOnlyCollection p return contextId; } - private static ReleaseVersionRepository BuildRepository( - ContentDbContext contentDbContext) + private static ReleaseVersionRepository BuildRepository(ContentDbContext? contentDbContext = null) { return new ReleaseVersionRepository( - contentDbContext: contentDbContext + contentDbContext: contentDbContext ?? InMemoryContentDbContext() ); } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Extensions/ReleaseSeriesItemExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Extensions/ReleaseSeriesItemExtensions.cs index 3581291c11..a7a09eec58 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Extensions/ReleaseSeriesItemExtensions.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Extensions/ReleaseSeriesItemExtensions.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; @@ -7,17 +8,26 @@ namespace GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; public static class ReleaseSeriesItemExtensions { /// - /// Retrieves the release id's by the order they appear in a of type , + /// Retrieves the first release id in a of type , + /// ignoring any legacy links. + /// + /// + /// The first release id in the release series. + public static Guid? LatestReleaseId(this List releaseSeriesItems) => + SelectReleaseIds(releaseSeriesItems).FirstOrDefault(); + + /// + /// Retrieves the release id's in a of type , /// ignoring any legacy links. /// /// /// A of type containing the release id's in the order /// they appear in the release series. - public static IReadOnlyList ReleaseIds(this List releaseSeriesItems) - { - return releaseSeriesItems + public static IReadOnlyList ReleaseIds(this List releaseSeriesItems) => + SelectReleaseIds(releaseSeriesItems).ToList(); + + private static IEnumerable SelectReleaseIds(List releaseSeriesItems) => + releaseSeriesItems .Where(rsi => !rsi.IsLegacyLink) - .Select(rs => rs.ReleaseId!.Value) - .ToList(); - } + .Select(rs => rs.ReleaseId!.Value); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs index 8a01061242..fb99630f1f 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs @@ -25,11 +25,11 @@ Task GetPublishedDate( CancellationToken cancellationToken = default); /// - /// Retrieves the latest version from all releases in reverse chronological order that are associated with a publication. + /// Retrieves the latest version of the latest release in release series order associated with a publication. /// /// The unique identifier of the publication. /// A to observe while waiting for the task to complete. - /// The latest version from all releases in reverse chronological order that are associated with a publication. + /// The latest version of the latest release in release series order associated with the publication. Task GetLatestReleaseVersion( Guid publicationId, CancellationToken cancellationToken = default); @@ -67,12 +67,12 @@ Task> ListLatestReleaseVersionIds( CancellationToken cancellationToken = default); /// - /// Retrieves the latest versions of all releases associated with a given publication in reverse chronological order. + /// Retrieves the latest versions of all releases in release series order associated with a given publication. /// /// The unique identifier of the publication. /// Flag to only include published release versions. /// A to observe while waiting for the task to complete. - /// A collection of the latest version id's of all releases associated with the publication. + /// A collection of the latest versions of all releases in release series order associated with the publication. Task> ListLatestReleaseVersions( Guid publicationId, bool publishedOnly = false, diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs index f07444f434..18ba9720d8 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; using GovUk.Education.ExploreEducationStatistics.Content.Model.Predicates; using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; using Microsoft.EntityFrameworkCore; @@ -58,10 +59,16 @@ await _contentDbContext.Entry(releaseVersion) Guid publicationId, CancellationToken cancellationToken = default) { - return (await _contentDbContext.ReleaseVersions.LatestReleaseVersions(publicationId) - .ToListAsync(cancellationToken: cancellationToken)) - .OrderByReverseChronologicalOrder() - .FirstOrDefault(); + var latestReleaseId = await _contentDbContext.Publications + .Where(p => p.Id == publicationId) + .Select(p => p.ReleaseSeries.LatestReleaseId()) + .SingleOrDefaultAsync(cancellationToken: cancellationToken); + + return latestReleaseId.HasValue + ? await _contentDbContext.ReleaseVersions + .LatestReleaseVersion(releaseId: latestReleaseId.Value) + .SingleOrDefaultAsync(cancellationToken: cancellationToken) + : null; } public async Task GetLatestPublishedReleaseVersion( @@ -109,10 +116,24 @@ public async Task> ListLatestReleaseVersions( bool publishedOnly = false, CancellationToken cancellationToken = default) { + var publication = await _contentDbContext.Publications + .SingleOrDefaultAsync(p => p.Id == publicationId, cancellationToken: cancellationToken); + + if (publication == null) + { + return []; + } + + var publicationReleaseSeriesReleaseIds = publication.ReleaseSeries.ReleaseIds(); + + var releaseIdIndexMap = publicationReleaseSeriesReleaseIds + .Select((releaseId, index) => (releaseId, index)) + .ToDictionary(tuple => tuple.releaseId, tuple => tuple.index); + return (await _contentDbContext.ReleaseVersions .LatestReleaseVersions(publicationId, publishedOnly: publishedOnly) .ToListAsync(cancellationToken: cancellationToken)) - .OrderByReverseChronologicalOrder() + .OrderBy(rv => releaseIdIndexMap[rv.ReleaseId]) .ToList(); } @@ -156,13 +177,3 @@ private async Task IsLatestReleaseVersion( private record ReleaseIdVersion(Guid ReleaseId, int Version); } - -internal static class ReleaseVersionIEnumerableExtensions -{ - internal static IOrderedEnumerable OrderByReverseChronologicalOrder( - this IEnumerable query) - { - return query.OrderByDescending(releaseVersion => releaseVersion.Year) - .ThenByDescending(releaseVersion => releaseVersion.TimePeriodCoverage); - } -} From 2e744b2770a77b01b9a1fb2278f6a7fc74588c88 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Thu, 19 Dec 2024 12:15:27 +0000 Subject: [PATCH 19/21] EES-5656 Rename method GetLatestPublishedReleaseVersion to GetLatestPublishedReleaseVersionByReleaseSlug --- .../ReleaseVersionRepositoryTests.cs | 19 ++++++++++++------- .../Interfaces/IReleaseVersionRepository.cs | 2 +- .../Repository/ReleaseVersionRepository.cs | 4 ++-- .../ReleaseService.cs | 2 +- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs index 88ecdddf47..54a95d1034 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Repository/ReleaseVersionRepositoryTests.cs @@ -16,7 +16,7 @@ public class ReleaseVersionRepositoryTests { private readonly DataFixture _dataFixture = new(); - public class GetLatestPublishedReleaseVersionTests : ReleaseVersionRepositoryTests + public class GetLatestPublishedReleaseVersionByReleaseSlugTests : ReleaseVersionRepositoryTests { [Fact] public async Task Success() @@ -33,7 +33,8 @@ public async Task Success() await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug: "2021-22"); + var result = + await repository.GetLatestPublishedReleaseVersionByReleaseSlug(publication.Id, releaseSlug: "2021-22"); // Expect the result to be the latest published version for the 2021-22 release var expectedReleaseVersion = publication.Releases.Single(r => r is { Year: 2021 }).Versions[1]; @@ -54,7 +55,8 @@ public async Task MultiplePublications_ReturnsReleaseVersionAssociatedWithPublic await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - var result = await repository.GetLatestPublishedReleaseVersion(publication1.Id, releaseSlug: "2021-22"); + var result = + await repository.GetLatestPublishedReleaseVersionByReleaseSlug(publication1.Id, releaseSlug: "2021-22"); // Expect the result to be from the specified publication var expectedReleaseVersion = publication1.Releases.Single().Versions.Single(); @@ -74,7 +76,8 @@ public async Task PublicationHasNoPublishedReleaseVersions_ReturnsNull() await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug: "2021-22")); + Assert.Null( + await repository.GetLatestPublishedReleaseVersionByReleaseSlug(publication.Id, releaseSlug: "2021-22")); } [Fact] @@ -88,7 +91,8 @@ public async Task PublicationHasNoPublishedReleaseVersionsMatchingSlug_ReturnsNu await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug: "2021-22")); + Assert.Null( + await repository.GetLatestPublishedReleaseVersionByReleaseSlug(publication.Id, releaseSlug: "2021-22")); } [Fact] @@ -100,7 +104,8 @@ public async Task PublicationHasNoReleaseVersions_ReturnsNull() await using var contentDbContext = InMemoryContentDbContext(contextId); var repository = BuildRepository(contentDbContext); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publication.Id, releaseSlug: "2021-22")); + Assert.Null( + await repository.GetLatestPublishedReleaseVersionByReleaseSlug(publication.Id, releaseSlug: "2021-22")); } [Fact] @@ -108,7 +113,7 @@ public async Task PublicationDoesNotExist_ReturnsNull() { var repository = BuildRepository(); - Assert.Null(await repository.GetLatestPublishedReleaseVersion(publicationId: Guid.NewGuid(), + Assert.Null(await repository.GetLatestPublishedReleaseVersionByReleaseSlug(publicationId: Guid.NewGuid(), releaseSlug: "2021-22")); } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs index fb99630f1f..fb1e740046 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/Interfaces/IReleaseVersionRepository.cs @@ -19,7 +19,7 @@ Task GetPublishedDate( /// The slug of the release. /// A to observe while waiting for the task to complete. /// The latest published version of the release associated with the publication. - Task GetLatestPublishedReleaseVersion( + Task GetLatestPublishedReleaseVersionByReleaseSlug( Guid publicationId, string releaseSlug, CancellationToken cancellationToken = default); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs index 18ba9720d8..685151bbb7 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Repository/ReleaseVersionRepository.cs @@ -71,7 +71,7 @@ await _contentDbContext.Entry(releaseVersion) : null; } - public async Task GetLatestPublishedReleaseVersion( + public async Task GetLatestPublishedReleaseVersionByReleaseSlug( Guid publicationId, string releaseSlug, CancellationToken cancellationToken = default) @@ -79,7 +79,7 @@ await _contentDbContext.Entry(releaseVersion) // There should only ever be one latest published release version with a given slug return await _contentDbContext.ReleaseVersions .LatestReleaseVersions(publicationId, releaseSlug, publishedOnly: true) - .FirstOrDefaultAsync(cancellationToken: cancellationToken); + .SingleOrDefaultAsync(cancellationToken: cancellationToken); } public async Task IsLatestPublishedReleaseVersion( diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs index 68bc9328b6..49bb95807e 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/ReleaseService.cs @@ -58,7 +58,7 @@ public async Task> GetRelease( // otherwise use the latest published version of the requested release var latestReleaseVersionId = releaseSlug == null ? publication.LatestPublishedReleaseVersionId - : (await _releaseVersionRepository.GetLatestPublishedReleaseVersion(publication.Id, + : (await _releaseVersionRepository.GetLatestPublishedReleaseVersionByReleaseSlug(publication.Id, releaseSlug))?.Id; return latestReleaseVersionId.HasValue From 23523e222dd4c28018d6d81586e87efc30eb4d6d Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Thu, 19 Dec 2024 17:38:58 +0000 Subject: [PATCH 20/21] EES-5656 Remove ManageContentPageViewModel.PublicationViewModel.Releases --- .../ManageContentPageServiceTests.cs | 6 ------ .../Mappings/MappingProfiles.cs | 17 ----------------- .../ManageContent/ManageContentPageViewModel.cs | 11 ----------- .../__tests__/ReleaseContentPage.test.tsx | 1 - .../__tests__/PreReleaseContentPage.test.tsx | 1 - .../src/prototypes/data/releaseContentData.ts | 7 ------- .../test/generators/releaseContentGenerators.ts | 1 - .../src/services/publicationService.ts | 5 ----- .../find-statistics/PublicationReleasePage.tsx | 6 +++++- .../__tests__/PublicationReleasePage.test.tsx | 10 ---------- .../__tests__/__data__/testReleaseData.ts | 12 ------------ .../components/__tests__/__data__/tableData.ts | 1 - 12 files changed, 5 insertions(+), 73 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/ManageContent/ManageContentPageServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/ManageContent/ManageContentPageServiceTests.cs index f4dea4b73a..f5906b91e7 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/ManageContent/ManageContentPageServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin.Tests/Services/ManageContent/ManageContentPageServiceTests.cs @@ -324,12 +324,6 @@ public async Task GetManageContentPageViewModel() Assert.Equal(publication.ReleaseSeries[2].LegacyLinkUrl, contentPublicationReleaseSeries[2].LegacyLinkUrl); - var contentPublicationReleases = contentPublication.Releases; - Assert.Single(contentPublicationReleases); - Assert.Equal(otherReleaseVersion.Id, contentPublicationReleases[0].Id); - Assert.Equal(otherReleaseVersion.Slug, contentPublicationReleases[0].Slug); - Assert.Equal(otherReleaseVersion.Title, contentPublicationReleases[0].Title); - Assert.Equal(2, contentPublication.Methodologies.Count); Assert.Equal(methodology.Versions[0].Id, contentPublication.Methodologies[0].Id); Assert.Equal(methodology.Versions[0].Title, contentPublication.Methodologies[0].Title); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Mappings/MappingProfiles.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Mappings/MappingProfiles.cs index 3f802dd7f0..affee167e3 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin/Mappings/MappingProfiles.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Mappings/MappingProfiles.cs @@ -128,18 +128,6 @@ public MappingProfiles() Title = rv.Publication.Title, Slug = rv.Publication.Slug, Contact = rv.Publication.Contact, - Releases = rv.Publication.ReleaseVersions - .FindAll(otherReleaseVersion => rv.Id != otherReleaseVersion.Id && - IsLatestVersionOfRelease(rv.Publication.ReleaseVersions, otherReleaseVersion.Id)) - .OrderByDescending(otherReleaseVersion => otherReleaseVersion.Year) - .ThenByDescending(otherReleaseVersion => otherReleaseVersion.TimePeriodCoverage) - .Select(otherReleaseVersion => new PreviousReleaseViewModel - { - Id = otherReleaseVersion.Id, - Slug = otherReleaseVersion.Slug, - Title = otherReleaseVersion.Title, - }) - .ToList(), ReleaseSeries = new List(), // Must be hydrated after mapping ExternalMethodology = rv.Publication.ExternalMethodology != null ? new ExternalMethodology @@ -225,10 +213,5 @@ private void CreateContentBlockMap() CreateMap(); } - - private static bool IsLatestVersionOfRelease(IEnumerable releaseVersions, Guid releaseVersionId) - { - return !releaseVersions.Any(rv => rv.PreviousVersionId == releaseVersionId && rv.Id != releaseVersionId); - } } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/ViewModels/ManageContent/ManageContentPageViewModel.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/ViewModels/ManageContent/ManageContentPageViewModel.cs index f91481a35d..be3e5d8802 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin/ViewModels/ManageContent/ManageContentPageViewModel.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/ViewModels/ManageContent/ManageContentPageViewModel.cs @@ -80,8 +80,6 @@ public class PublicationViewModel public string Slug { get; set; } - public List Releases { get; set; } - public List ReleaseSeries { get; set; } public Contact Contact { get; set; } @@ -100,13 +98,4 @@ public class ReleaseNoteViewModel public DateTime On { get; set; } } - - public class PreviousReleaseViewModel - { - public Guid Id { get; set; } - - public string Slug { get; set; } - - public string Title { get; set; } - } } diff --git a/src/explore-education-statistics-admin/src/pages/release/content/__tests__/ReleaseContentPage.test.tsx b/src/explore-education-statistics-admin/src/pages/release/content/__tests__/ReleaseContentPage.test.tsx index bf84627fec..cd8fcd1955 100644 --- a/src/explore-education-statistics-admin/src/pages/release/content/__tests__/ReleaseContentPage.test.tsx +++ b/src/explore-education-statistics-admin/src/pages/release/content/__tests__/ReleaseContentPage.test.tsx @@ -129,7 +129,6 @@ describe('ReleaseContentPage', () => { id: 'publication-id', title: 'Publication 1', slug: 'publication-1', - releases: [], releaseSeries: [], theme: { id: 'theme-1', title: 'Theme 1' }, contact: { diff --git a/src/explore-education-statistics-admin/src/pages/release/pre-release/__tests__/PreReleaseContentPage.test.tsx b/src/explore-education-statistics-admin/src/pages/release/pre-release/__tests__/PreReleaseContentPage.test.tsx index c8c4e512df..892b34e7b1 100644 --- a/src/explore-education-statistics-admin/src/pages/release/pre-release/__tests__/PreReleaseContentPage.test.tsx +++ b/src/explore-education-statistics-admin/src/pages/release/pre-release/__tests__/PreReleaseContentPage.test.tsx @@ -91,7 +91,6 @@ describe('PreReleaseContentPage', () => { id: 'publication-id', title: 'Publication 1', slug: 'publication-1', - releases: [], releaseSeries: [], theme: { id: 'theme-1', title: 'Theme 1' }, contact: { diff --git a/src/explore-education-statistics-admin/src/prototypes/data/releaseContentData.ts b/src/explore-education-statistics-admin/src/prototypes/data/releaseContentData.ts index 6b5c0352dd..abc3df8550 100644 --- a/src/explore-education-statistics-admin/src/prototypes/data/releaseContentData.ts +++ b/src/explore-education-statistics-admin/src/prototypes/data/releaseContentData.ts @@ -128,13 +128,6 @@ const prototypeReleaseContent: ReleaseContent = { slug: 'methodology-slug', }, ], - releases: [ - { - id: 'previous-release-id', - slug: 'previous-release-slug', - title: 'Previous release title', - }, - ], releaseSeries: [], slug: 'publication-slug', title: 'Initial Teacher Training Census', diff --git a/src/explore-education-statistics-admin/test/generators/releaseContentGenerators.ts b/src/explore-education-statistics-admin/test/generators/releaseContentGenerators.ts index ab2c4eb9fa..5e71a6747b 100644 --- a/src/explore-education-statistics-admin/test/generators/releaseContentGenerators.ts +++ b/src/explore-education-statistics-admin/test/generators/releaseContentGenerators.ts @@ -92,7 +92,6 @@ const defaultPublication: Publication = { slug: 'methodology-slug', }, ], - releases: [], releaseSeries: [ { isLegacyLink: true, diff --git a/src/explore-education-statistics-common/src/services/publicationService.ts b/src/explore-education-statistics-common/src/services/publicationService.ts index d32c4c8898..6a91919241 100644 --- a/src/explore-education-statistics-common/src/services/publicationService.ts +++ b/src/explore-education-statistics-common/src/services/publicationService.ts @@ -20,11 +20,6 @@ export interface Publication { id: string; slug: string; title: string; - releases: { - id: string; - slug: string; - title: string; - }[]; releaseSeries: ReleaseSeriesItem[]; theme: { id: string; diff --git a/src/explore-education-statistics-frontend/src/modules/find-statistics/PublicationReleasePage.tsx b/src/explore-education-statistics-frontend/src/modules/find-statistics/PublicationReleasePage.tsx index ac3a4d19f6..fb89cf32d8 100644 --- a/src/explore-education-statistics-frontend/src/modules/find-statistics/PublicationReleasePage.tsx +++ b/src/explore-education-statistics-frontend/src/modules/find-statistics/PublicationReleasePage.tsx @@ -130,7 +130,11 @@ const PublicationReleasePage: NextPage = ({ release }) => { > View latest data:{' '} - {release.publication.releases[0].title} + { + release.publication.releaseSeries.find( + rsi => !rsi.isLegacyLink, + )?.description + } )} diff --git a/src/explore-education-statistics-frontend/src/modules/find-statistics/__tests__/PublicationReleasePage.test.tsx b/src/explore-education-statistics-frontend/src/modules/find-statistics/__tests__/PublicationReleasePage.test.tsx index 4dbfc47091..1522c2a7a7 100644 --- a/src/explore-education-statistics-frontend/src/modules/find-statistics/__tests__/PublicationReleasePage.test.tsx +++ b/src/explore-education-statistics-frontend/src/modules/find-statistics/__tests__/PublicationReleasePage.test.tsx @@ -342,16 +342,6 @@ describe('PublicationReleasePage', () => { month: 2, year: 2022, }, - publication: { - ...testRelease.publication, - releases: [ - { - id: 'latest-release', - title: 'Latest Release Title', - slug: 'latest-release-slug', - }, - ], - }, }} />, ); diff --git a/src/explore-education-statistics-frontend/src/modules/find-statistics/__tests__/__data__/testReleaseData.ts b/src/explore-education-statistics-frontend/src/modules/find-statistics/__tests__/__data__/testReleaseData.ts index c7c697c1db..b36cccdaa9 100644 --- a/src/explore-education-statistics-frontend/src/modules/find-statistics/__tests__/__data__/testReleaseData.ts +++ b/src/explore-education-statistics-frontend/src/modules/find-statistics/__tests__/__data__/testReleaseData.ts @@ -4,18 +4,6 @@ export const testPublication: Publication = { id: 'publication-1', title: 'Pupil absence in schools in England', slug: 'pupil-absence-in-schools-in-england', - releases: [ - { - id: 'release-2', - slug: '2018-19', - title: 'Academic year 2018/19', - }, - { - id: 'release-1', - slug: '2017-18', - title: 'Academic year 2017/18', - }, - ], releaseSeries: [ { isLegacyLink: false, diff --git a/src/explore-education-statistics-frontend/src/modules/table-tool/components/__tests__/__data__/tableData.ts b/src/explore-education-statistics-frontend/src/modules/table-tool/components/__tests__/__data__/tableData.ts index eaa670625a..9f7823af6a 100644 --- a/src/explore-education-statistics-frontend/src/modules/table-tool/components/__tests__/__data__/tableData.ts +++ b/src/explore-education-statistics-frontend/src/modules/table-tool/components/__tests__/__data__/tableData.ts @@ -273,7 +273,6 @@ export const testPublicationRelease: Release = { id: '', slug: '', title: '', - releases: [], releaseSeries: [], theme: { title: '', From 9412715a81c7dd64b0fd38ec21496222fe2e8fe0 Mon Sep 17 00:00:00 2001 From: Ben Outram Date: Fri, 20 Dec 2024 10:07:13 +0000 Subject: [PATCH 21/21] EES-5656 Change ContentService UpdateContent/UpdateContentStaged methods to use release series when working out the latest published release version of publication --- .../Extensions/EnumerableExtensionsTests.cs | 17 -- .../Extensions/EnumerableExtensions.cs | 26 -- .../Predicates/ReleaseVersionPredicates.cs | 16 +- .../Extensions/PublisherExtensionTests.cs | 175 ----------- .../Services/ReleaseServiceTests.cs | 287 ++++++++++++------ .../Extensions/PublisherExtensions.cs | 42 --- .../PublisherHostBuilderExtensions.cs | 1 + .../Services/ContentService.cs | 77 ++--- .../Services/Interfaces/IContentService.cs | 2 +- .../Services/Interfaces/IReleaseService.cs | 4 +- .../Services/PublishingCompletionService.cs | 26 +- .../Services/ReleaseService.cs | 86 +++--- 12 files changed, 301 insertions(+), 458 deletions(-) delete mode 100644 src/GovUk.Education.ExploreEducationStatistics.Publisher.Tests/Extensions/PublisherExtensionTests.cs delete mode 100644 src/GovUk.Education.ExploreEducationStatistics.Publisher/Extensions/PublisherExtensions.cs diff --git a/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/Extensions/EnumerableExtensionsTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/Extensions/EnumerableExtensionsTests.cs index d70fa90b55..acdb9ab102 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/Extensions/EnumerableExtensionsTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/Extensions/EnumerableExtensionsTests.cs @@ -218,23 +218,6 @@ public void ToDictionaryIndexed() Assert.Equal(expected, result); } - [Fact] - public void DistinctByProperty() - { - var list = new List - { - new(1), - new(1), - new(2), - }; - - var distinct = list.DistinctByProperty(x => x.Value).ToList(); - - Assert.Equal(2, distinct.Count); - Assert.Equal(1, distinct[0].Value); - Assert.Equal(2, distinct[1].Value); - } - [Fact] public void IndexOfFirst() { diff --git a/src/GovUk.Education.ExploreEducationStatistics.Common/Extensions/EnumerableExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Common/Extensions/EnumerableExtensions.cs index 5ff99b81af..48d1210fcc 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Common/Extensions/EnumerableExtensions.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Common/Extensions/EnumerableExtensions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using GovUk.Education.ExploreEducationStatistics.Common.Utils; using GovUk.Education.ExploreEducationStatistics.Common.Model; using NaturalSort.Extension; @@ -229,31 +228,6 @@ public static IAsyncEnumerable WhereNotNull(this IAsyncEnumerable sour public static IEnumerable<(T item, int index)> WithIndex(this IEnumerable self) => self.Select((item, index) => (item, index)); - /// - /// Filter a list down to distinct elements based on a property of the type. - /// - /// - /// - /// As IEqualityComparers (as used in Linq's Distinct() method) compare with GetHashCode() rather than with - /// Equals(), the property being used to compare distinctions against needs to produce a reliable hash code - /// that we can use for equality. A good property type then could be a Guid Id field, as two identical Guid Ids - /// can then represent that 2 or more entities in the list are duplicates as they will have the same hash code. - /// - /// - /// Sequence of elements to filter on a distinct property - /// A supplier of a property from each entity to check for equality. The property - /// chosen must produce the same hash code for any two elements in the source list that are considered - /// duplicates. A good example would be a Guid Id. - /// - /// - public static IEnumerable DistinctByProperty( - this IEnumerable source, - Func propertyGetter) - where T : class - { - return source.Distinct(ComparerUtils.CreateComparerByProperty(propertyGetter)); - } - public static bool IsSameAsIgnoringOrder(this IEnumerable first, IEnumerable second) { var firstList = first.ToList(); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Predicates/ReleaseVersionPredicates.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Predicates/ReleaseVersionPredicates.cs index 5756080918..30d7023a26 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Predicates/ReleaseVersionPredicates.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Predicates/ReleaseVersionPredicates.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Collections.Generic; using System.Linq; namespace GovUk.Education.ExploreEducationStatistics.Content.Model.Predicates; @@ -55,17 +56,26 @@ public static IQueryable LatestReleaseVersions(this IQueryableThe source of type to filter. /// Unique identifier of a release to filter by. /// Flag to only include published release versions. + /// Optional list of unpublished release version ids to also consider. + /// Applicable when is true. /// An of type that contains elements from the input /// sequence filtered to only include the latest version of the release. - public static IQueryable LatestReleaseVersion(this IQueryable releaseVersions, + public static IQueryable LatestReleaseVersion( + this IQueryable releaseVersions, Guid releaseId, - bool publishedOnly = false) + bool publishedOnly = false, + IReadOnlyList? includeUnpublishedVersionIds = null) { return releaseVersions .Where(releaseVersion => releaseVersion.ReleaseId == releaseId) .Where(releaseVersion => releaseVersion.Version == releaseVersions .Where(latestVersion => latestVersion.ReleaseId == releaseId) - .Where(latestVersion => !publishedOnly || latestVersion.Published.HasValue) + .Where(latestVersion => !publishedOnly || + latestVersion.Published.HasValue || + ( + includeUnpublishedVersionIds != null && + includeUnpublishedVersionIds.Contains(latestVersion.Id) + )) .Select(latestVersion => (int?)latestVersion.Version) .Max()); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher.Tests/Extensions/PublisherExtensionTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher.Tests/Extensions/PublisherExtensionTests.cs deleted file mode 100644 index 6fd5e204a1..0000000000 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher.Tests/Extensions/PublisherExtensionTests.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Collections.Generic; -using GovUk.Education.ExploreEducationStatistics.Content.Model; -using GovUk.Education.ExploreEducationStatistics.Publisher.Extensions; -using Xunit; - -namespace GovUk.Education.ExploreEducationStatistics.Publisher.Tests.Extensions -{ - public class PublisherExtensionTests - { - [Fact] - public void IsReleasePublished_ReleasePublished() - { - var publication = new Publication(); - - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - Published = DateTime.UtcNow.AddSeconds(-1) - }; - - publication.ReleaseVersions = new List - { - releaseVersion - }; - - Assert.True(releaseVersion.IsReleasePublished()); - } - - [Fact] - public void IsReleasePublished_ReleaseNotPublished() - { - var publication = new Publication(); - - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - Published = null - }; - - publication.ReleaseVersions = new List - { - releaseVersion - }; - - Assert.False(releaseVersion.IsReleasePublished()); - } - - [Fact] - public void IsReleasePublished_ReleaseNotPublishedButIncluded() - { - var publication = new Publication(); - - var releaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - Published = null - }; - - publication.ReleaseVersions = new List - { - releaseVersion - }; - - Assert.True(releaseVersion.IsReleasePublished(new List - { - releaseVersion.Id - })); - } - - [Fact] - public void IsReleasePublished_AmendmentReleaseNotPublished() - { - var publication = new Publication(); - - var originalReleaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - Version = 0, - Published = DateTime.UtcNow.AddSeconds(-1) - }; - - var amendmentReleaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - PreviousVersionId = originalReleaseVersion.Id, - Version = 1 - }; - - publication.ReleaseVersions = new List - { - originalReleaseVersion, - amendmentReleaseVersion - }; - - Assert.True(originalReleaseVersion.IsReleasePublished()); - Assert.False(amendmentReleaseVersion.IsReleasePublished()); - } - - [Fact] - public void IsReleasePublished_AmendmentReleasePublished() - { - var publication = new Publication(); - - var originalReleaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - Version = 0, - Published = DateTime.UtcNow.AddSeconds(-1) - }; - - var amendmentReleaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - PreviousVersionId = originalReleaseVersion.Id, - Version = 1, - Published = DateTime.UtcNow.AddSeconds(-1) - }; - - publication.ReleaseVersions = new List - { - originalReleaseVersion, - amendmentReleaseVersion - }; - - Assert.False(originalReleaseVersion.IsReleasePublished()); - Assert.True(amendmentReleaseVersion.IsReleasePublished()); - } - - [Fact] - public void IsReleasePublished_AmendmentReleaseNotPublishedButIncluded() - { - var publication = new Publication(); - - var originalReleaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - Version = 0, - Published = DateTime.UtcNow.AddSeconds(-1) - }; - - var amendmentReleaseVersion = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - PreviousVersionId = originalReleaseVersion.Id, - Version = 1 - }; - - publication.ReleaseVersions = new List - { - originalReleaseVersion, - amendmentReleaseVersion - }; - - Assert.False(originalReleaseVersion.IsReleasePublished(new List - { - amendmentReleaseVersion.Id - })); - - Assert.True(amendmentReleaseVersion.IsReleasePublished(new List - { - amendmentReleaseVersion.Id - })); - } - } -} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher.Tests/Services/ReleaseServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher.Tests/Services/ReleaseServiceTests.cs index b8b4821373..9524492242 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher.Tests/Services/ReleaseServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher.Tests/Services/ReleaseServiceTests.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Model; using GovUk.Education.ExploreEducationStatistics.Common.Tests.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Tests.Fixtures; @@ -7,14 +11,8 @@ using GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; using GovUk.Education.ExploreEducationStatistics.Publisher.Services; using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Xunit; using static GovUk.Education.ExploreEducationStatistics.Common.Model.FileType; -using static GovUk.Education.ExploreEducationStatistics.Common.Model.TimeIdentifier; -using static GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseApprovalStatus; using static GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Utils.ContentDbUtils; namespace GovUk.Education.ExploreEducationStatistics.Publisher.Tests.Services @@ -95,108 +93,136 @@ public async Task GetFiles() } [Fact] - public async Task GetLatestRelease() + public async Task GetLatestPublishedReleaseVersion_Success() { - var publication = new Publication(); + Publication publication = _fixture.DefaultPublication() + .WithReleases(_ => + [ + _fixture.DefaultRelease(publishedVersions: 1, year: 2020), + _fixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2021), + _fixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2022) + ]); - var release1V0 = new ReleaseVersion - { - Id = Guid.NewGuid(), - Publication = publication, - ReleaseName = "2017", - TimePeriodCoverage = AcademicYearQ4, - Slug = "2017-18-q4", - Published = new DateTime(2019, 4, 1), - ApprovalStatus = Approved - }; + var release2021 = publication.Releases.Single(r => r.Year == 2021); - var release2V0 = new ReleaseVersion + var contentDbContextId = Guid.NewGuid().ToString(); + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { - Id = new Guid("e7e1aae3-a0a1-44b7-bdf3-3df4a363ce20"), - Publication = publication, - ReleaseName = "2018", - TimePeriodCoverage = AcademicYearQ1, - Slug = "2018-19-q1", - Published = new DateTime(2019, 3, 1), - ApprovalStatus = Approved, - }; + contentDbContext.Publications.Add(publication); + await contentDbContext.SaveChangesAsync(); + } - var release3V0 = new ReleaseVersion + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { - Id = Guid.NewGuid(), - Publication = publication, - ReleaseName = "2018", - TimePeriodCoverage = AcademicYearQ2, - Slug = "2018-19-q2", - Published = new DateTime(2019, 1, 1), - ApprovalStatus = Approved, - Version = 0, - PreviousVersionId = null - }; + var service = BuildReleaseService(contentDbContext); + + var result = await service.GetLatestPublishedReleaseVersion( + publication.Id, + includeUnpublishedVersionIds: []); + + Assert.Equal(release2021.Versions[1].Id, result.Id); + } + } - var release3V1 = new ReleaseVersion + [Fact] + public async Task GetLatestPublishedReleaseVersion_NonDefaultReleaseOrder() + { + Publication publication = _fixture.DefaultPublication() + .WithReleases(_ => + [ + _fixture.DefaultRelease(publishedVersions: 1, year: 2020), + _fixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2021), + _fixture.DefaultRelease(publishedVersions: 1, year: 2022) + ]) + .FinishWith(p => + { + // Adjust the generated LatestPublishedReleaseVersion to make 2020 the latest published release + var release2020Version0 = p.Releases.Single(r => r.Year == 2020).Versions[0]; + p.LatestPublishedReleaseVersion = release2020Version0; + p.LatestPublishedReleaseVersionId = release2020Version0.Id; + + // Apply a different release series order rather than using the default + p.ReleaseSeries = + [.. GenerateReleaseSeries(p.Releases, 2021, 2020, 2022)]; + }); + + var release2020 = publication.Releases.Single(r => r.Year == 2020); + + var contentDbContextId = Guid.NewGuid().ToString(); + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { - Id = Guid.NewGuid(), - Publication = publication, - ReleaseName = "2018", - TimePeriodCoverage = AcademicYearQ2, - Slug = "2018-19-q2", - Published = new DateTime(2019, 2, 1), - ApprovalStatus = Approved, - Version = 1, - PreviousVersionId = release3V0.Id - }; + contentDbContext.Publications.Add(publication); + await contentDbContext.SaveChangesAsync(); + } - var release3V2Deleted = new ReleaseVersion + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { - Id = Guid.NewGuid(), - Publication = publication, - ReleaseName = "2018", - TimePeriodCoverage = AcademicYearQ2, - Slug = "2018-19-q2", - Published = null, - ApprovalStatus = Approved, - Version = 2, - PreviousVersionId = release3V1.Id, - SoftDeleted = true - }; + var service = BuildReleaseService(contentDbContext); - var release3V3NotPublished = new ReleaseVersion + var result = await service.GetLatestPublishedReleaseVersion( + publication.Id, + includeUnpublishedVersionIds: []); + + // Check the 2020 release version is considered to be the latest published release version, + // since 2020 is the first release in the release series with a published version + Assert.Equal(release2020.Versions[0].Id, result.Id); + } + } + + [Fact] + public async Task GetLatestPublishedReleaseVersion_IncludeUnpublishedReleaseVersion() + { + Publication publication = _fixture.DefaultPublication() + .WithReleases(_ => + [ + _fixture.DefaultRelease(publishedVersions: 1, year: 2020), + _fixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2021), + _fixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2022) + ]); + + var release2021 = publication.Releases.Single(r => r.Year == 2021); + + var contentDbContextId = Guid.NewGuid().ToString(); + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { - Id = Guid.NewGuid(), - Publication = publication, - ReleaseName = "2018", - TimePeriodCoverage = AcademicYearQ2, - Slug = "2018-19-q2", - Published = null, - ApprovalStatus = Approved, - Version = 3, - PreviousVersionId = release3V1.Id - }; + contentDbContext.Publications.Add(publication); + await contentDbContext.SaveChangesAsync(); + } - var release4V0 = new ReleaseVersion + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { - Id = Guid.NewGuid(), - Publication = publication, - ReleaseName = "2018", - TimePeriodCoverage = AcademicYearQ3, - Slug = "2018-19-q3", - Published = null, - ApprovalStatus = Approved - }; + var service = BuildReleaseService(contentDbContext); - var contentDbContextId = Guid.NewGuid().ToString(); + // Include the unpublished 2021 release version id in the call + // to test the scenario where this version is about to be published + var result = await service.GetLatestPublishedReleaseVersion( + publication.Id, + includeUnpublishedVersionIds: [release2021.Versions.Single(rv => rv.Published == null).Id]); + + // Check the unpublished 2021 release version is considered to be the latest published release version, + // despite the fact that it is not published yet + Assert.Equal(release2021.Versions.Single(rv => rv.Published == null).Id, result.Id); + } + } + + [Fact] + public async Task GetLatestPublishedReleaseVersion_IncludeUnpublishedReleaseVersions() + { + Publication publication = _fixture.DefaultPublication() + .WithReleases(_ => + [ + _fixture.DefaultRelease(publishedVersions: 1, year: 2020), + _fixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2021), + _fixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2022) + ]); + + var release2021 = publication.Releases.Single(r => r.Year == 2021); + var release2022 = publication.Releases.Single(r => r.Year == 2022); + var contentDbContextId = Guid.NewGuid().ToString(); await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { - await contentDbContext.AddRangeAsync(publication); - await contentDbContext.AddRangeAsync(release1V0, - release2V0, - release3V0, - release3V1, - release3V2Deleted, - release3V3NotPublished, - release4V0); + contentDbContext.Publications.Add(publication); await contentDbContext.SaveChangesAsync(); } @@ -204,10 +230,73 @@ await contentDbContext.AddRangeAsync(release1V0, { var service = BuildReleaseService(contentDbContext); - var result = await service.GetLatestReleaseVersion(publication.Id, Enumerable.Empty()); + // Include the unpublished 2021 and 2022 release version id's in the call + // to test the scenario where both versions are about to be published together + var result = await service.GetLatestPublishedReleaseVersion( + publication.Id, + includeUnpublishedVersionIds: + [ + release2021.Versions.Single(rv => rv.Published == null).Id, + release2022.Versions.Single(rv => rv.Published == null).Id + ]); + + // Check the unpublished 2022 release version is considered to be the latest published release version, + // despite the fact that it is not published yet + Assert.Equal(release2022.Versions.Single(rv => rv.Published == null).Id, result.Id); + } + } + + [Fact] + public async Task GetLatestPublishedReleaseVersion_IgnoresIncludedUnpublishedReleaseVersions() + { + Publication publication = _fixture.DefaultPublication() + .WithReleases(_ => + [ + _fixture.DefaultRelease(publishedVersions: 1, year: 2020), + _fixture.DefaultRelease(publishedVersions: 2, draftVersion: true, year: 2021), + _fixture.DefaultRelease(publishedVersions: 0, draftVersion: true, year: 2022) + ]) + .FinishWith(p => + { + // Adjust the generated LatestPublishedReleaseVersion to make 2020 the latest published release + var release2020Version0 = p.Releases.Single(r => r.Year == 2020).Versions[0]; + p.LatestPublishedReleaseVersion = release2020Version0; + p.LatestPublishedReleaseVersionId = release2020Version0.Id; + + // Apply a different release series order rather than using the default + p.ReleaseSeries = + [.. GenerateReleaseSeries(p.Releases, 2020, 2021, 2022)]; + }); + + var release2020 = publication.Releases.Single(r => r.Year == 2020); + var release2021 = publication.Releases.Single(r => r.Year == 2021); + var release2022 = publication.Releases.Single(r => r.Year == 2022); - Assert.Equal(release3V1.Id, result.Id); - Assert.Equal("Academic year Q2 2018/19", result.Title); + var contentDbContextId = Guid.NewGuid().ToString(); + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) + { + contentDbContext.Publications.Add(publication); + await contentDbContext.SaveChangesAsync(); + } + + await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) + { + var service = BuildReleaseService(contentDbContext); + + // Include the unpublished 2021 and 2022 release version id's in the call + // to test the scenario where both versions are about to be published together + var result = await service.GetLatestPublishedReleaseVersion( + publication.Id, + includeUnpublishedVersionIds: + [ + release2021.Versions.Single(rv => rv.Published == null).Id, + release2022.Versions.Single(rv => rv.Published == null).Id + ]); + + // Check the 2020 release version is considered to be the latest published release version, + // despite the fact that versions for 2021 and 2022 are about to be published, + // since 2020 is the first release in the release series and has a published version + Assert.Equal(release2020.Versions[0].Id, result.Id); } } @@ -535,8 +624,16 @@ public async Task CompletePublishing_AmendedReleaseAndUpdatePublishedDateIsTrue( } } - private static ReleaseService BuildReleaseService( - ContentDbContext? contentDbContext = null) + private List GenerateReleaseSeries(IReadOnlyList releases, params int[] years) + { + return years.Select(year => + { + var release = releases.Single(r => r.Year == year); + return _fixture.DefaultReleaseSeriesItem().WithReleaseId(release.Id).Generate(); + }).ToList(); + } + + private static ReleaseService BuildReleaseService(ContentDbContext? contentDbContext = null) { contentDbContext ??= InMemoryContentDbContext(); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Extensions/PublisherExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Extensions/PublisherExtensions.cs deleted file mode 100644 index cc405bd6ea..0000000000 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Extensions/PublisherExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using GovUk.Education.ExploreEducationStatistics.Content.Model; - -namespace GovUk.Education.ExploreEducationStatistics.Publisher.Extensions; - -public static class PublisherExtensions -{ - /// - /// Determines whether a release version should be published or not. - /// - /// The to test - /// Release version id's which are not published yet but are in the process of being published - /// True if the release version is the latest published version of a release or is one of the included releases - public static bool IsReleasePublished(this ReleaseVersion releaseVersion, - IEnumerable includedReleaseVersionIds = null) - { - return includedReleaseVersionIds != null && - includedReleaseVersionIds.Contains(releaseVersion.Id) || - releaseVersion.IsLatestPublishedVersionOfRelease(includedReleaseVersionIds); - } - - private static bool IsLatestPublishedVersionOfRelease(this ReleaseVersion releaseVersion, - IEnumerable includedReleaseIds) - { - if (releaseVersion.Publication?.ReleaseVersions == null || !releaseVersion.Publication.ReleaseVersions.Any()) - { - throw new ArgumentException( - "All release versions of the publication must be hydrated to test the latest published version"); - } - - return - // Release version itself must be live - releaseVersion.Live - // It must also be the latest version unless the later version is a draft not included for publishing - && !releaseVersion.Publication.ReleaseVersions.Any(rv => - (rv.Live || includedReleaseIds != null && includedReleaseIds.Contains(rv.Id)) - && rv.PreviousVersionId == releaseVersion.Id - && rv.Id != releaseVersion.Id); - } -} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/PublisherHostBuilderExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/PublisherHostBuilderExtensions.cs index 395fe57831..bb95202612 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/PublisherHostBuilderExtensions.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/PublisherHostBuilderExtensions.cs @@ -96,6 +96,7 @@ public static IHostBuilder ConfigurePublisherHostBuilder(this IHostBuilder hostB provider.GetRequiredService>())) .AddScoped(provider => new ContentService( + contentDbContext: provider.GetRequiredService(), publicBlobStorageService: provider.GetRequiredService(), privateBlobCacheService: new BlobCacheService( provider.GetRequiredService(), diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/ContentService.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/ContentService.cs index 72987829a0..8cddbea8b2 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/ContentService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/ContentService.cs @@ -4,10 +4,11 @@ using GovUk.Education.ExploreEducationStatistics.Common; using GovUk.Education.ExploreEducationStatistics.Common.Cache; using GovUk.Education.ExploreEducationStatistics.Common.Cache.Interfaces; -using GovUk.Education.ExploreEducationStatistics.Common.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Services.Interfaces; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces.Cache; using GovUk.Education.ExploreEducationStatistics.Publisher.Services.Interfaces; +using Microsoft.EntityFrameworkCore; using static GovUk.Education.ExploreEducationStatistics.Common.BlobContainers; using static GovUk.Education.ExploreEducationStatistics.Common.Services.FileStoragePathUtils; @@ -15,6 +16,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Publisher.Services { public class ContentService : IContentService { + private readonly ContentDbContext _contentDbContext; private readonly IBlobCacheService _privateBlobCacheService; private readonly IBlobCacheService _publicBlobCacheService; private readonly IBlobStorageService _publicBlobStorageService; @@ -24,6 +26,7 @@ public class ContentService : IContentService private readonly IPublicationCacheService _publicationCacheService; public ContentService( + ContentDbContext contentDbContext, IBlobCacheService privateBlobCacheService, IBlobCacheService publicBlobCacheService, IBlobStorageService publicBlobStorageService, @@ -32,6 +35,7 @@ public ContentService( IReleaseCacheService releaseCacheService, IPublicationCacheService publicationCacheService) { + _contentDbContext = contentDbContext; _privateBlobCacheService = privateBlobCacheService; _publicBlobCacheService = publicBlobCacheService; _publicBlobStorageService = publicBlobStorageService; @@ -94,60 +98,61 @@ await _publicBlobStorageService.DeleteBlobs( } } - public async Task UpdateContent(params Guid[] releaseVersionIds) + public async Task UpdateContent(Guid releaseVersionId) { - var releaseVersions = (await _releaseService - .List(releaseVersionIds)) - .ToList(); - - foreach (var releaseVersion in releaseVersions) - { - await _releaseCacheService.UpdateRelease( - releaseVersion.Id, - publicationSlug: releaseVersion.Publication.Slug, - releaseSlug: releaseVersion.Slug); - } - - var publications = releaseVersions - .Select(rv => rv.Publication) - .DistinctByProperty(publication => publication.Id) - .ToList(); - - foreach (var publication in publications) - { - // Cache the latest release version for the publication as a separate cache entry - var latestReleaseVersion = await _releaseService.GetLatestReleaseVersion(publication.Id, releaseVersionIds); - await _releaseCacheService.UpdateRelease( - latestReleaseVersion.Id, - publicationSlug: publication.Slug); - } + var releaseVersion = await _contentDbContext.ReleaseVersions + .Include(rv => rv.Release) + .ThenInclude(r => r.Publication) + .SingleAsync(rv => rv.Id == releaseVersionId); + + await _releaseCacheService.UpdateRelease( + releaseVersion.Id, + publicationSlug: releaseVersion.Release.Publication.Slug, + releaseSlug: releaseVersion.Release.Slug); + + var publication = releaseVersion.Release.Publication; + + // Cache the latest release version for the publication as a separate cache entry + var latestReleaseVersion = await _releaseService.GetLatestPublishedReleaseVersion( + publicationId: publication.Id, + includeUnpublishedVersionIds: [releaseVersion.Id]); + + await _releaseCacheService.UpdateRelease( + releaseVersionId: latestReleaseVersion.Id, + publicationSlug: publication.Slug); } - public async Task UpdateContentStaged(DateTime expectedPublishDate, + public async Task UpdateContentStaged( + DateTime expectedPublishDate, params Guid[] releaseVersionIds) { - var releaseVersions = (await _releaseService - .List(releaseVersionIds)) - .ToList(); + var releaseVersions = await _contentDbContext.ReleaseVersions + .Where(rv => releaseVersionIds.Contains(rv.Id)) + .Include(rv => rv.Release) + .ThenInclude(r => r.Publication) + .ToListAsync(); foreach (var releaseVersion in releaseVersions) { await _releaseCacheService.UpdateReleaseStaged( releaseVersion.Id, expectedPublishDate, - publicationSlug: releaseVersion.Publication.Slug, - releaseSlug: releaseVersion.Slug); + publicationSlug: releaseVersion.Release.Publication.Slug, + releaseSlug: releaseVersion.Release.Slug); } var publications = releaseVersions - .Select(rv => rv.Publication) - .DistinctByProperty(publication => publication.Id) + .Select(rv => rv.Release.Publication) + .DistinctBy(p => p.Id) .ToList(); foreach (var publication in publications) { // Cache the latest release version for the publication as a separate cache entry - var latestReleaseVersion = await _releaseService.GetLatestReleaseVersion(publication.Id, releaseVersionIds); + var latestReleaseVersion = await _releaseService.GetLatestPublishedReleaseVersion( + publicationId: publication.Id, + includeUnpublishedVersionIds: releaseVersionIds); + await _releaseCacheService.UpdateReleaseStaged( latestReleaseVersion.Id, expectedPublishDate, diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/Interfaces/IContentService.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/Interfaces/IContentService.cs index 9e80eb6122..28792d63f3 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/Interfaces/IContentService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/Interfaces/IContentService.cs @@ -9,7 +9,7 @@ public interface IContentService Task DeletePreviousVersionsContent(params Guid[] releaseVersionIds); - Task UpdateContent(params Guid[] releaseVersionIds); + Task UpdateContent(Guid releaseVersionId); Task UpdateContentStaged(DateTime expectedPublishDate, params Guid[] releaseVersionIds); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/Interfaces/IReleaseService.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/Interfaces/IReleaseService.cs index 83fc632e26..51a60318f1 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/Interfaces/IReleaseService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/Interfaces/IReleaseService.cs @@ -16,7 +16,9 @@ public interface IReleaseService Task> GetFiles(Guid releaseVersionId, params FileType[] types); - Task GetLatestReleaseVersion(Guid publicationId, IEnumerable includedReleaseVersionIds); + Task GetLatestPublishedReleaseVersion( + Guid publicationId, + IReadOnlyList? includeUnpublishedVersionIds = null); Task CompletePublishing(Guid releaseVersionId, DateTime actualPublishedDate); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs index 9bd2031ff2..bc469d819d 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/PublishingCompletionService.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; -using GovUk.Education.ExploreEducationStatistics.Content.Model.Predicates; using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces.Cache; using GovUk.Education.ExploreEducationStatistics.Publisher.Model; using GovUk.Education.ExploreEducationStatistics.Publisher.Services.Interfaces; @@ -125,30 +123,10 @@ private async Task UpdateLatestPublishedReleaseVersionForPublication(Guid public var publication = await contentDbContext.Publications .SingleAsync(p => p.Id == publicationId); - // Get the publications release id's by the order they appear in the release series - var releaseSeriesReleaseIds = publication.ReleaseSeries.ReleaseIds(); + var latestPublishedReleaseVersion = await releaseService.GetLatestPublishedReleaseVersion(publicationId); - // Work out the publication's new latest published release version. - // This is the latest published version of the first release which has a published version - Guid? latestPublishedReleaseVersionId = null; - foreach (var releaseId in releaseSeriesReleaseIds) - { - latestPublishedReleaseVersionId = (await contentDbContext.ReleaseVersions - .LatestReleaseVersion(releaseId: releaseId, publishedOnly: true) - .SingleOrDefaultAsync())?.Id; - - if (latestPublishedReleaseVersionId != null) - { - break; - } - } - - publication.LatestPublishedReleaseVersionId = - latestPublishedReleaseVersionId ?? - throw new InvalidOperationException( - $"No latest published release version found for publication {publicationId}"); + publication.LatestPublishedReleaseVersionId = latestPublishedReleaseVersion.Id; - contentDbContext.Update(publication); await contentDbContext.SaveChangesAsync(); } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/ReleaseService.cs b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/ReleaseService.cs index dcf55d60ce..52b3465b01 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/ReleaseService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Publisher/Services/ReleaseService.cs @@ -1,39 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Model; using GovUk.Education.ExploreEducationStatistics.Content.Model; using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Extensions; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Predicates; using GovUk.Education.ExploreEducationStatistics.Content.Model.Repository.Interfaces; using GovUk.Education.ExploreEducationStatistics.Publisher.Services.Interfaces; using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using static GovUk.Education.ExploreEducationStatistics.Publisher.Extensions.PublisherExtensions; namespace GovUk.Education.ExploreEducationStatistics.Publisher.Services { - public class ReleaseService : IReleaseService + public class ReleaseService( + ContentDbContext contentDbContext, + IReleaseVersionRepository releaseVersionRepository + ) : IReleaseService { - private readonly ContentDbContext _contentDbContext; - private readonly IReleaseVersionRepository _releaseVersionRepository; - - public ReleaseService( - ContentDbContext contentDbContext, - IReleaseVersionRepository releaseVersionRepository) - { - _contentDbContext = contentDbContext; - _releaseVersionRepository = releaseVersionRepository; - } - public async Task Get(Guid releaseVersionId) { - return await _contentDbContext.ReleaseVersions + return await contentDbContext.ReleaseVersions .SingleAsync(releaseVersion => releaseVersion.Id == releaseVersionId); } public async Task> List(IEnumerable releaseVersionIds) { - return await _contentDbContext.ReleaseVersions + return await contentDbContext.ReleaseVersions .Where(rv => releaseVersionIds.Contains(rv.Id)) .Include(rv => rv.Publication) .Include(rv => rv.PreviousVersion) @@ -42,31 +35,48 @@ public async Task> List(IEnumerable releaseVer public async Task> GetAmendedReleases(IEnumerable releaseVersionIds) { - return await _contentDbContext.ReleaseVersions + return await contentDbContext.ReleaseVersions .Include(rv => rv.PreviousVersion) .Include(rv => rv.Publication) .Where(rv => releaseVersionIds.Contains(rv.Id) && rv.PreviousVersionId != null) .ToListAsync(); } - public async Task GetLatestReleaseVersion(Guid publicationId, - IEnumerable includedReleaseVersionIds) + public async Task GetLatestPublishedReleaseVersion( + Guid publicationId, + IReadOnlyList? includeUnpublishedVersionIds = null) { - var releases = await _contentDbContext.ReleaseVersions - .Include(rv => rv.Publication) - .Where(rv => rv.PublicationId == publicationId) - .ToListAsync(); + var publication = await contentDbContext.Publications + .SingleAsync(p => p.Id == publicationId); + + // Get the publications release id's by the order they appear in the release series + var releaseSeriesReleaseIds = publication.ReleaseSeries.ReleaseIds(); + + // Work out the publication's latest published release version. + // This is the latest published version of the first release which has either a published version + // or one of the included (about to be published) release version ids + ReleaseVersion? latestPublishedReleaseVersion = null; + foreach (var releaseId in releaseSeriesReleaseIds) + { + latestPublishedReleaseVersion = await contentDbContext.ReleaseVersions + .LatestReleaseVersion(releaseId: releaseId, + publishedOnly: true, + includeUnpublishedVersionIds: includeUnpublishedVersionIds) + .SingleOrDefaultAsync(); + + if (latestPublishedReleaseVersion != null) + { + break; + } + } - return releases - .Where(rv => rv.IsReleasePublished(includedReleaseVersionIds)) - .OrderBy(rv => rv.Year) - .ThenBy(rv => rv.TimePeriodCoverage) - .Last(); + return latestPublishedReleaseVersion ?? throw new InvalidOperationException( + $"No latest published release version found for publication {publicationId}"); } public async Task> GetFiles(Guid releaseVersionId, params FileType[] types) { - return await _contentDbContext + return await contentDbContext .ReleaseFiles .Include(rf => rf.File) .Where(rf => rf.ReleaseVersionId == releaseVersionId) @@ -77,15 +87,15 @@ public async Task> GetFiles(Guid releaseVersionId, params FileType[] public async Task CompletePublishing(Guid releaseVersionId, DateTime actualPublishedDate) { - var releaseVersion = await _contentDbContext + var releaseVersion = await contentDbContext .ReleaseVersions .Include(rv => rv.DataBlockVersions) .ThenInclude(dataBlockVersion => dataBlockVersion.DataBlockParent) .SingleAsync(rv => rv.Id == releaseVersionId); - _contentDbContext.ReleaseVersions.Update(releaseVersion); + contentDbContext.ReleaseVersions.Update(releaseVersion); - var publishedDate = await _releaseVersionRepository.GetPublishedDate(releaseVersion.Id, actualPublishedDate); + var publishedDate = await releaseVersionRepository.GetPublishedDate(releaseVersion.Id, actualPublishedDate); releaseVersion.Published = publishedDate; @@ -93,14 +103,14 @@ public async Task CompletePublishing(Guid releaseVersionId, DateTime actualPubli await UpdatePublishedDataBlockVersions(releaseVersion); - await _contentDbContext.SaveChangesAsync(); + await contentDbContext.SaveChangesAsync(); } private async Task UpdateReleaseFilePublishedDate( ReleaseVersion releaseVersion, DateTime publishedDate) { - var dataReleaseFiles = _contentDbContext.ReleaseFiles + var dataReleaseFiles = contentDbContext.ReleaseFiles .Where(releaseFile => releaseFile.ReleaseVersionId == releaseVersion.Id) .Include(rf => rf.File); @@ -143,7 +153,7 @@ private async Task UpdatePublishedDataBlockVersions(ReleaseVersion releaseVersio { var latestDataBlockParentIds = latestDataBlockParents.Select(dataBlockParent => dataBlockParent.Id); - var removedDataBlockVersions = await _contentDbContext + var removedDataBlockVersions = await contentDbContext .DataBlockVersions .Where(dataBlockVersion => dataBlockVersion.ReleaseVersionId == releaseVersion.PreviousVersionId && !latestDataBlockParentIds.Contains(dataBlockVersion.DataBlockParentId))