Skip to content

Commit

Permalink
EES-4389 - added memory caching to List Puplications endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
duncan-at-hiveit committed Jul 6, 2023
1 parent f860fb8 commit 7f0ab67
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Moq;
using NCrontab;
using Xunit;
using static GovUk.Education.ExploreEducationStatistics.Common.Cache.CronSchedules;
using static GovUk.Education.ExploreEducationStatistics.Common.Services.CollectionUtils;
using static GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils.MockUtils;

Expand All @@ -18,9 +19,6 @@ namespace GovUk.Education.ExploreEducationStatistics.Common.Tests.Cache;
[Collection(CacheTestFixture.CollectionName)]
public class MemoryCacheAttributeTests : IClassFixture<CacheTestFixture>, IDisposable
{
private const string HourlyExpirySchedule = "0 * * * *";
private const string HalfHourlyExpirySchedule = "*/30 * * * *";

private readonly Mock<IMemoryCacheService> _memoryCacheService = new(MockBehavior.Strict);

public MemoryCacheAttributeTests()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
using NCrontab;
using Xunit;
using static System.Globalization.DateTimeStyles;
using static GovUk.Education.ExploreEducationStatistics.Common.Cache.CronSchedules;
using static GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils.MockUtils;
using static Moq.MockBehavior;

namespace GovUk.Education.ExploreEducationStatistics.Common.Tests.Services;

public class MemoryCacheServiceTests
{
private readonly CrontabSchedule _hourlyExpirySchedule = CrontabSchedule.Parse("0 * * * *");
private readonly CrontabSchedule _halfHourlyExpirySchedule = CrontabSchedule.Parse("*/30 * * * *");
private readonly CrontabSchedule _hourlyExpirySchedule = CrontabSchedule.Parse(HourlyExpirySchedule);
private readonly CrontabSchedule _halfHourlyExpirySchedule = CrontabSchedule.Parse(HalfHourlyExpirySchedule);

private record SampleClassSuperclass;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace GovUk.Education.ExploreEducationStatistics.Common.Cache;

public static class CronSchedules
{
public const string HourlyExpirySchedule = "0 * * * *";
public const string HalfHourlyExpirySchedule = "*/30 * * * *";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#nullable enable
using System;
using System.Threading.Tasks;
using GovUk.Education.ExploreEducationStatistics.Common.Cache;
using GovUk.Education.ExploreEducationStatistics.Common.Model;
using GovUk.Education.ExploreEducationStatistics.Common.Tests.Extensions;
using GovUk.Education.ExploreEducationStatistics.Common.Tests.Fixtures;
using GovUk.Education.ExploreEducationStatistics.Common.ViewModels;
using GovUk.Education.ExploreEducationStatistics.Content.Api.Cache;
using GovUk.Education.ExploreEducationStatistics.Content.Api.Controllers;
using GovUk.Education.ExploreEducationStatistics.Content.Api.Requests;
using GovUk.Education.ExploreEducationStatistics.Content.Model;
using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces;
using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces.Cache;
using GovUk.Education.ExploreEducationStatistics.Content.Services.ViewModels;
using Moq;
using NCrontab;
using Xunit;
using static GovUk.Education.ExploreEducationStatistics.Common.Cache.CronSchedules;
using static GovUk.Education.ExploreEducationStatistics.Common.Services.CollectionUtils;
using static GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils.MockUtils;
using static Moq.MockBehavior;
using static Newtonsoft.Json.JsonConvert;

namespace GovUk.Education.ExploreEducationStatistics.Content.Api.Tests.Controllers;

[Collection(CacheServiceTests)]
public class PublicationControllerCachingTests : CacheServiceTestFixture
{
private readonly PublicationsListRequest _query = new(
ReleaseType.ExperimentalStatistics,
ThemeId: Guid.Empty,
Search: "",
IPublicationService.PublicationsSortBy.Published,
SortOrder.Asc,
Page: 1,
PageSize: 10
);

private readonly PaginatedListViewModel<PublicationSearchResultViewModel> _publications = new(
ListOf(new PublicationSearchResultViewModel
{
Id = Guid.NewGuid(),
Published = DateTime.UtcNow,
Rank = 4,
Slug = "slug",
Summary = "summary",
Theme = "theme",
Title = "title",
Type = ReleaseType.ExperimentalStatistics
}), 5, 1, 10);

[Fact]
public async Task ListPublications_NoCachedEntryExists()
{
var publicationService = new Mock<IPublicationService>(Strict);

MemoryCacheService
.Setup(s => s.GetItem(
new GetPublicationListCacheKey(_query),
typeof(PaginatedListViewModel<PublicationSearchResultViewModel>)))
.Returns(null);

var expectedCacheConfiguration = new MemoryCacheConfiguration(
10, CrontabSchedule.Parse(HalfHourlyExpirySchedule));

MemoryCacheService
.Setup(s => s.SetItem<object>(
new GetPublicationListCacheKey(_query),
_publications,
ItIs.DeepEqualTo(expectedCacheConfiguration),
null));

publicationService
.Setup(s => s.ListPublications(
_query.ReleaseType,
_query.ThemeId,
_query.Search,
_query.Sort,
_query.Order,
_query.Page,
_query.PageSize))
.ReturnsAsync(_publications);

var controller = BuildController(publicationService.Object);

var result = await controller.ListPublications(_query);

VerifyAllMocks(MemoryCacheService, publicationService);

result.AssertOkResult(_publications);
}

[Fact]
public async Task ListPublications_CachedEntryExists()
{
MemoryCacheService
.Setup(s => s.GetItem(
new GetPublicationListCacheKey(_query),
typeof(PaginatedListViewModel<PublicationSearchResultViewModel>)))
.Returns(_publications);

var controller = BuildController();

var result = await controller.ListPublications(_query);

VerifyAllMocks(MemoryCacheService);

result.AssertOkResult(_publications);
}

[Fact]
public void PublicationSearchResults_SerializeAndDeserialize()
{
var converted = DeserializeObject<PaginatedListViewModel<PublicationSearchResultViewModel>>(
SerializeObject(_publications));

converted.AssertDeepEqualTo(_publications);
}

private static PublicationController BuildController(
IPublicationService? publicationService = null
)
{
return new(
Mock.Of<IPublicationCacheService>(Strict),
publicationService ?? Mock.Of<IPublicationService>(Strict)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Moq;
using NCrontab;
using Xunit;
using static GovUk.Education.ExploreEducationStatistics.Common.Cache.CronSchedules;
using static GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils.MockUtils;
using static Moq.MockBehavior;

Expand All @@ -21,8 +22,6 @@ namespace GovUk.Education.ExploreEducationStatistics.Content.Api.Tests.Controlle
[Collection(CacheServiceTests)]
public class ReleaseControllerCachingTests : CacheServiceTestFixture
{
private const string HalfHourlyExpirySchedule = "*/30 * * * *";

private const string PublicationSlug = "publication-a";
private const string ReleaseSlug = "200";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using GovUk.Education.ExploreEducationStatistics.Common.Cache.Interfaces;
using GovUk.Education.ExploreEducationStatistics.Content.Api.Requests;

namespace GovUk.Education.ExploreEducationStatistics.Content.Api.Cache;

public record GetPublicationListCacheKey(PublicationsListRequest PublicationQuery) : IMemoryCacheKey
{
public string Key => $"{GetType().Name}:{PublicationQuery}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
using System.Collections.Generic;
using System.Net.Mime;
using System.Threading.Tasks;
using GovUk.Education.ExploreEducationStatistics.Common.Cache;
using GovUk.Education.ExploreEducationStatistics.Common.Extensions;
using GovUk.Education.ExploreEducationStatistics.Common.Model;
using GovUk.Education.ExploreEducationStatistics.Common.ViewModels;
using GovUk.Education.ExploreEducationStatistics.Content.Api.Cache;
using GovUk.Education.ExploreEducationStatistics.Content.Api.Requests;
using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces;
using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces.Cache;
using GovUk.Education.ExploreEducationStatistics.Content.Services.Requests;
using GovUk.Education.ExploreEducationStatistics.Content.Services.ViewModels;
using Microsoft.AspNetCore.Mvc;
using static GovUk.Education.ExploreEducationStatistics.Common.Cache.CronSchedules;

namespace GovUk.Education.ExploreEducationStatistics.Content.Api.Controllers
{
Expand Down Expand Up @@ -45,6 +48,7 @@ public async Task<ActionResult<IList<PublicationTreeThemeViewModel>>> GetPublica
.HandleFailuresOrOk();
}

[MemoryCache(typeof(GetPublicationListCacheKey), durationInSeconds: 10, expiryScheduleCron: HalfHourlyExpirySchedule)]
[HttpGet("publications")]
public async Task<ActionResult<PaginatedListViewModel<PublicationSearchResultViewModel>>> ListPublications(
[FromQuery] PublicationsListRequest request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@
using GovUk.Education.ExploreEducationStatistics.Content.Services.Interfaces.Cache;
using GovUk.Education.ExploreEducationStatistics.Content.Services.ViewModels;
using Microsoft.AspNetCore.Mvc;
using static GovUk.Education.ExploreEducationStatistics.Common.Cache.CronSchedules;

namespace GovUk.Education.ExploreEducationStatistics.Content.Api.Controllers
{
[Route("api")]
[Produces(MediaTypeNames.Application.Json)]
public class ReleaseController : ControllerBase
{
private const string HalfHourlyExpirySchedule = "*/30 * * * *";

private readonly IMethodologyCacheService _methodologyCacheService;
private readonly IPublicationCacheService _publicationCacheService;
private readonly IReleaseCacheService _releaseCacheService;
Expand Down
2 changes: 1 addition & 1 deletion tests/robot-tests/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ANALYST_PASSWORD=ANALYST_PASSWORD # Analyst1 user password
EXPIRED_INVITE_USER_EMAIL= # Expired Invite user email
EXPIRED_INVITE_USER_PASSWORD= # Expired Invite user password
RELEASE_COMPLETE_WAIT=120 # Time to wait for a release to publish (in seconds)
WAIT_MEMORY_CACHE_EXPIRY=2 # Time to wait for the memory cache to expire (in minutes. Local = 2, Dev = 10)
WAIT_MEMORY_CACHE_EXPIRY=2 # Time to wait for the memory cache to expire (in seconds. Local = 2, Dev = 10)
WAIT_MEDIUM=120 # Variable used throughout tests (in seconds)
WAIT_SMALL=45 # Variable used throughout tests (in seconds)
WAIT_LONG=180 # Variable used throughout tests (in seconds)
Expand Down

0 comments on commit 7f0ab67

Please sign in to comment.