From e042f3b3a37dbf8005977e3d641cb56e7480a974 Mon Sep 17 00:00:00 2001 From: Sam Carter Date: Mon, 8 Jul 2024 16:03:27 +0100 Subject: [PATCH 1/7] Initial commit of the contentful filter changes for the qualification list. Still a WIP --- ...Dfe.EarlyYearsQualification.Content.csproj | 1 + .../ContentfulContentFilterService.cs | 53 +++++++++++++++---- .../Services/IContentFilterService.cs | 2 +- .../Content/MockContentfulFilterService.cs | 2 +- .../QualificationDetailsController.cs | 12 ++++- .../Controllers/QuestionsController.cs | 2 +- .../Models/Content/QualificationListModel.cs | 2 + .../Models/UserJourneyModel.cs | 2 + .../IUserJourneyCookieService.cs | 4 ++ .../UserJourneyCookieService.cs | 21 ++++++++ .../Views/QualificationDetails/Get.cshtml | 11 ++-- .../QualificationDetailsControllerTests.cs | 2 +- .../Controllers/QuestionsControllerTests.cs | 7 +-- .../Mocks/MockContentfulFilterServiceTests.cs | 2 +- .../ContentfulContentFilterServiceTests.cs | 24 ++++----- 15 files changed, 111 insertions(+), 36 deletions(-) diff --git a/src/Dfe.EarlyYearsQualification.Content/Dfe.EarlyYearsQualification.Content.csproj b/src/Dfe.EarlyYearsQualification.Content/Dfe.EarlyYearsQualification.Content.csproj index 918dcade..12d63b77 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Dfe.EarlyYearsQualification.Content.csproj +++ b/src/Dfe.EarlyYearsQualification.Content/Dfe.EarlyYearsQualification.Content.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs b/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs index 5d71f692..cbaa2f0b 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs +++ b/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs @@ -5,6 +5,7 @@ using Contentful.Core.Search; using Dfe.EarlyYearsQualification.Content.Constants; using Dfe.EarlyYearsQualification.Content.Entities; +using FuzzySharp; using Microsoft.Extensions.Logging; namespace Dfe.EarlyYearsQualification.Content.Services; @@ -38,13 +39,14 @@ private static readonly ReadOnlyDictionary public QueryBuilder QueryBuilder { get; init; } = QueryBuilder.New; public async Task> GetFilteredQualifications(int? level, int? startDateMonth, - int? startDateYear, string? awardingOrganisation) + int? startDateYear, string? awardingOrganisation, string? qualificationName) { - logger.LogInformation("Filtering options passed in - level: {Level}, startDateMonth: {StartDateMonth}, startDateYear: {StartDateYear}, awardingOrganisation: {AwardingOrganisation}", + logger.LogInformation("Filtering options passed in - level: {Level}, startDateMonth: {StartDateMonth}, startDateYear: {StartDateYear}, awardingOrganisation: {AwardingOrganisation}, qualificationName: {qualificationName}", level, startDateMonth, startDateYear, - awardingOrganisation); + awardingOrganisation, + qualificationName); // create query builder var queryBuilder = QueryBuilder.ContentTypeIs(ContentTypes.Qualification); @@ -56,7 +58,13 @@ public async Task> GetFilteredQualifications(int? level, int if (!string.IsNullOrEmpty(awardingOrganisation)) { - queryBuilder = queryBuilder.FieldEquals("fields.awardingOrganisationTitle", awardingOrganisation); + var awardingOrganisations = new List + { + awardingOrganisation, + "All Higher Education Institutes", + "Various Awarding Organisations" + }; + queryBuilder = queryBuilder.FieldIncludes("fields.awardingOrganisationTitle", awardingOrganisations); } // get qualifications @@ -70,20 +78,43 @@ public async Task> GetFilteredQualifications(int? level, int logger.LogError(e, "Error getting qualifications"); return []; } + + // apply start date filtering + var filteredQualifications = FilterQualificationsByDate(startDateMonth, startDateYear, qualifications.ToList()); + + // Filter based on qualification name + filteredQualifications = FilterQualificationsByName(filteredQualifications, qualificationName); - if (!startDateMonth.HasValue || !startDateYear.HasValue) return qualifications.ToList(); + return filteredQualifications; + } - // apply start date filtering - var results = FilterQualificationsByDate(startDateMonth.Value, startDateYear.Value, qualifications); + private List FilterQualificationsByName(List qualifications, string? qualificationName) + { + if (string.IsNullOrEmpty(qualificationName)) + { + return qualifications; + } - return results; + var matchedQualifications = new List(); + foreach (var qualification in qualifications) + { + var weight = Fuzz.PartialRatio(qualificationName, qualification.QualificationName); + if (weight > 70) + { + matchedQualifications.Add(qualification); + } + } + + return matchedQualifications; } - private List FilterQualificationsByDate(int startDateMonth, int startDateYear, - ContentfulCollection qualifications) + private List FilterQualificationsByDate(int? startDateMonth, int? startDateYear, + List qualifications) { + if (!startDateMonth.HasValue || !startDateYear.HasValue) return qualifications.ToList(); + var results = new List(); - var enteredStartDate = new DateOnly(startDateYear, startDateMonth, Day); + var enteredStartDate = new DateOnly(startDateYear.Value, startDateMonth.Value, Day); foreach (var qualification in qualifications) { var qualificationStartDate = GetQualificationDate(qualification.FromWhichYear); diff --git a/src/Dfe.EarlyYearsQualification.Content/Services/IContentFilterService.cs b/src/Dfe.EarlyYearsQualification.Content/Services/IContentFilterService.cs index 8480a55f..d6feeba5 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Services/IContentFilterService.cs +++ b/src/Dfe.EarlyYearsQualification.Content/Services/IContentFilterService.cs @@ -4,5 +4,5 @@ namespace Dfe.EarlyYearsQualification.Content.Services; public interface IContentFilterService { - Task> GetFilteredQualifications(int? level, int? startDateMonth, int? startDateYear, string? awardingOrganisation); + Task> GetFilteredQualifications(int? level, int? startDateMonth, int? startDateYear, string? awardingOrganisation, string? qualificationName); } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Mock/Content/MockContentfulFilterService.cs b/src/Dfe.EarlyYearsQualification.Mock/Content/MockContentfulFilterService.cs index 0ed1e5c3..f5ff5948 100644 --- a/src/Dfe.EarlyYearsQualification.Mock/Content/MockContentfulFilterService.cs +++ b/src/Dfe.EarlyYearsQualification.Mock/Content/MockContentfulFilterService.cs @@ -5,7 +5,7 @@ namespace Dfe.EarlyYearsQualification.Mock.Content; public class MockContentfulFilterService : IContentFilterService { - public Task> GetFilteredQualifications(int? level, int? startDateMonth, int? startDateYear, string? awardingOrganisation) + public Task> GetFilteredQualifications(int? level, int? startDateMonth, int? startDateYear, string? awardingOrganisation, string? qualificationName) { var qualifications = new List diff --git a/src/Dfe.EarlyYearsQualification.Web/Controllers/QualificationDetailsController.cs b/src/Dfe.EarlyYearsQualification.Web/Controllers/QualificationDetailsController.cs index 98888d83..e658f6ef 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Controllers/QualificationDetailsController.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Controllers/QualificationDetailsController.cs @@ -34,6 +34,14 @@ public async Task Get() return View(model); } + + [HttpPost] + public IActionResult Refine(string refineSearch) + { + userJourneyCookieService.SetNameSearchCriteria(refineSearch); + + return RedirectToAction("Get"); + } [HttpGet("qualification-details/{qualificationId}")] public async Task Index(string qualificationId) @@ -69,9 +77,10 @@ private async Task> GetFilteredQualifications() var level = userJourneyCookieService.GetLevelOfQualification(); var (startDateMonth, startDateYear) = userJourneyCookieService.GetWhenWasQualificationAwarded(); var awardingOrganisation = userJourneyCookieService.GetAwardingOrganisation(); + var searchCriteria = userJourneyCookieService.GetSearchCriteria(); return await contentFilterService.GetFilteredQualifications(level, startDateMonth, startDateYear, - awardingOrganisation); + awardingOrganisation, searchCriteria); } private async Task MapList(QualificationListPage content, @@ -95,6 +104,7 @@ private async Task MapList(QualificationListPage content PostSearchCriteriaContent = await htmlRenderer.ToHtml(content.PostSearchCriteriaContent), PostQualificationListContent = await htmlRenderer.ToHtml(content.PostQualificationListContent), SearchCriteriaHeading = content.SearchCriteriaHeading, + SearchCriteria = userJourneyCookieService.GetSearchCriteria(), Qualifications = basicQualificationsModels.OrderBy(x => x.QualificationName).ToList() }; } diff --git a/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs b/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs index 29970c20..a65ff6e5 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs @@ -187,7 +187,7 @@ private async Task> GetFilteredQualifications() { int? level = userJourneyCookieService.GetLevelOfQualification(); (int? startDateMonth, int? startDateYear) = userJourneyCookieService.GetWhenWasQualificationAwarded(); - return await contentFilterService.GetFilteredQualifications(level, startDateMonth, startDateYear, null); + return await contentFilterService.GetFilteredQualifications(level, startDateMonth, startDateYear, null, null); } private async Task GetRadioView(string questionPageId, string actionName, string controllerName) diff --git a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QualificationListModel.cs b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QualificationListModel.cs index 39d87fb4..47790ece 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QualificationListModel.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QualificationListModel.cs @@ -28,5 +28,7 @@ public class QualificationListModel public string PostSearchCriteriaContent { get; init; } = string.Empty; + public string? SearchCriteria { get; set; } = string.Empty; + public List Qualifications { get; init; } = []; } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Models/UserJourneyModel.cs b/src/Dfe.EarlyYearsQualification.Web/Models/UserJourneyModel.cs index 13d23509..bdfaae72 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Models/UserJourneyModel.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Models/UserJourneyModel.cs @@ -6,4 +6,6 @@ public class UserJourneyModel public string WhenWasQualificationAwarded { get; set; } = string.Empty; public string LevelOfQualification { get; set; } = string.Empty; public string WhatIsTheAwardingOrganisation { get; set; } = string.Empty; + + public string SearchCriteria { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs index 2d3d0209..ab078edc 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs @@ -8,6 +8,8 @@ public interface IUserJourneyCookieService public void SetWhenWasQualificationAwarded(string date); public void SetLevelOfQualification(string level); public void SetAwardingOrganisation(string awardingOrganisation); + + public void SetNameSearchCriteria(string searchCriteria); public UserJourneyModel GetUserJourneyModelFromCookie(); public void ResetUserJourneyCookie(); @@ -15,4 +17,6 @@ public interface IUserJourneyCookieService public (int? startMonth, int? startYear) GetWhenWasQualificationAwarded(); public int? GetLevelOfQualification(); public string? GetAwardingOrganisation(); + + public string? GetSearchCriteria(); } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs index 3a85696d..ee94caf9 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs @@ -51,6 +51,15 @@ public void SetAwardingOrganisation(string awardingOrganisation) SetJourneyCookie(model); } + public void SetNameSearchCriteria(string searchCriteria) + { + var model = GetUserJourneyModelFromCookie(); + + model.SearchCriteria = searchCriteria; + + SetJourneyCookie(model); + } + public UserJourneyModel GetUserJourneyModelFromCookie() { var cookie = context.HttpContext?.Request.Cookies[CookieKeyNames.UserJourneyKey]; @@ -132,6 +141,18 @@ public void ResetUserJourneyCookie() return awardingOrganisation; } + public string? GetSearchCriteria() + { + var cookie = GetUserJourneyModelFromCookie(); + string? searchCriteria = null; + if (!string.IsNullOrEmpty(cookie.SearchCriteria)) + { + searchCriteria = cookie.SearchCriteria; + } + + return searchCriteria; + } + private void SetJourneyCookie(UserJourneyModel model) { var serializedCookie = JsonSerializer.Serialize(model); diff --git a/src/Dfe.EarlyYearsQualification.Web/Views/QualificationDetails/Get.cshtml b/src/Dfe.EarlyYearsQualification.Web/Views/QualificationDetails/Get.cshtml index f33f9119..5081b733 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Views/QualificationDetails/Get.cshtml +++ b/src/Dfe.EarlyYearsQualification.Web/Views/QualificationDetails/Get.cshtml @@ -33,10 +33,13 @@
@Html.Raw(Model.PreSearchBoxContent)
- - + @using (Html.BeginForm("Refine", "QualificationDetails", FormMethod.Post)) + { + + + }
@foreach (var qualification in Model.Qualifications) { diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QualificationDetailsControllerTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QualificationDetailsControllerTests.cs index 05ff4c39..ba04ae71 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QualificationDetailsControllerTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QualificationDetailsControllerTests.cs @@ -174,7 +174,7 @@ public async Task Get_ReturnsView() mockContentFilterService .Setup(x => x.GetFilteredQualifications(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())).ReturnsAsync(new List()); + It.IsAny(), It.IsAny())).ReturnsAsync(new List()); mockUserJourneyCookieService.Setup(x => x.GetUserJourneyModelFromCookie()).Returns(new UserJourneyModel()); diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QuestionsControllerTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QuestionsControllerTests.cs index c8f2f82b..b0b1ae31 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QuestionsControllerTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QuestionsControllerTests.cs @@ -605,6 +605,7 @@ public async Task WhatIsTheAwardingOrganisation_ContentServiceReturnsQuestionPag It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) .ReturnsAsync([]); @@ -672,7 +673,7 @@ public async Task mockUserJourneyCookieService.Setup(x => x.GetUserJourneyModelFromCookie()).Returns(new UserJourneyModel()); mockContentFilterService .Setup(x => x.GetFilteredQualifications(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(listOfQualifications); var controller = new QuestionsController(mockLogger.Object, mockContentService.Object, mockRenderer.Object, @@ -738,7 +739,7 @@ public async Task mockUserJourneyCookieService.Setup(x => x.GetUserJourneyModelFromCookie()).Returns(new UserJourneyModel()); mockContentFilterService .Setup(x => x.GetFilteredQualifications(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(listOfQualifications); var controller = new QuestionsController(mockLogger.Object, mockContentService.Object, mockRenderer.Object, @@ -789,7 +790,7 @@ public async Task Post_WhatIsTheAwardingOrganisation_InvalidModel_ReturnsQuestio mockUserJourneyCookieService.Setup(x => x.GetUserJourneyModelFromCookie()).Returns(new UserJourneyModel()); mockContentFilterService .Setup(x => x.GetFilteredQualifications(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync([]); controller.ModelState.AddModelError("option", "test error"); diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Mocks/MockContentfulFilterServiceTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Mocks/MockContentfulFilterServiceTests.cs index cd161eec..f438d990 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Mocks/MockContentfulFilterServiceTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Mocks/MockContentfulFilterServiceTests.cs @@ -22,7 +22,7 @@ public async Task GetFilteredQualifications_PassInLevel_ReturnsQualifications( int level, string[] expectedQualifications) { var mockContentFilterService = new MockContentfulFilterService(); - var results = await mockContentFilterService.GetFilteredQualifications(level, null, null, null); + var results = await mockContentFilterService.GetFilteredQualifications(level, null, null, null, null); results.Count.Should().Be(2); results[0].QualificationId.Should().Be(expectedQualifications[0]); diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs index b302cba1..85bf54e9 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs @@ -53,7 +53,7 @@ public async Task GetFilteredQualifications_PassInNullParameters_ReturnsAllQuali QueryBuilder = mockQueryBuilder }; - var filteredQualifications = await filterService.GetFilteredQualifications(null, null, null, null); + var filteredQualifications = await filterService.GetFilteredQualifications(null, null, null, null, null); filteredQualifications.Should().NotBeNull(); filteredQualifications.Count.Should().Be(2); @@ -94,7 +94,7 @@ public async Task GetFilteredQualifications_PassInLevel_ClientContainsLevelInQue QueryBuilder = mockQueryBuilder }; - await filterService.GetFilteredQualifications(4, null, null, null); + await filterService.GetFilteredQualifications(4, null, null, null, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); @@ -134,12 +134,12 @@ public async Task GetFilteredQualifications_PassInAwardingOrganisation_ClientCon QueryBuilder = mockQueryBuilder }; - await filterService.GetFilteredQualifications(null, null, null, "NCFE"); + await filterService.GetFilteredQualifications(null, null, null, "NCFE", null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle", "NCFE"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "NCFE,All Higher Education Institutes,Various Awarding Organisations"); } [TestMethod] @@ -191,7 +191,7 @@ public async Task GetFilteredQualifications_PassInLevelMonthAndNullParameters_Re QueryBuilder = mockQueryBuilder }; - var filteredQualifications = await filterService.GetFilteredQualifications(4, 9, null, null); + var filteredQualifications = await filterService.GetFilteredQualifications(4, 9, null, null, null); filteredQualifications.Should().NotBeNull(); filteredQualifications.Count.Should().Be(3); @@ -249,7 +249,7 @@ public async Task GetFilteredQualifications_PassInLevelNullAndYearParameters_Ret QueryBuilder = mockQueryBuilder }; - var filteredQualifications = await filterService.GetFilteredQualifications(4, null, 2014, null); + var filteredQualifications = await filterService.GetFilteredQualifications(4, null, 2014, null, null); filteredQualifications.Should().NotBeNull(); filteredQualifications.Count.Should().Be(3); @@ -316,7 +316,7 @@ public async Task GetFilteredQualifications_FilterOnDates_ReturnsFilteredQualifi QueryBuilder = mockQueryBuilder }; - var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null); + var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null, null); filteredQualifications.Should().NotBeNull(); filteredQualifications.Count.Should().Be(3); @@ -383,7 +383,7 @@ public async Task GetFilteredQualifications_FilterOnDates_MonthIsCaseInsensitive QueryBuilder = mockQueryBuilder }; - var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null); + var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null, null); filteredQualifications.Should().NotBeNull(); filteredQualifications.Count.Should().Be(3); @@ -404,7 +404,7 @@ public async Task GetFilteredQualifications_ContentfulClientThrowsException_Retu var mockLogger = new Mock>(); var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object); - var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null); + var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null, null); filteredQualifications.Should().NotBeNull(); filteredQualifications.Should().BeEmpty(); @@ -442,7 +442,7 @@ public async Task GetFilteredQualifications_DataContainsInvalidDateFormat_LogsEr QueryBuilder = mockQueryBuilder }; - await filterService.GetFilteredQualifications(4, 5, 2016, null); + await filterService.GetFilteredQualifications(4, 5, 2016, null, null); mockLogger.VerifyError("Qualification date Sep15 has unexpected format"); } @@ -479,7 +479,7 @@ public async Task GetFilteredQualifications_DataContainsInvalidMonth_LogsError() QueryBuilder = mockQueryBuilder }; - await filterService.GetFilteredQualifications(4, 5, 2016, null); + await filterService.GetFilteredQualifications(4, 5, 2016, null, null); mockLogger.VerifyError("Qualification date Sept-15 contains unexpected month value"); } @@ -516,7 +516,7 @@ public async Task GetFilteredQualifications_DataContainsInvalidYear_LogsError() QueryBuilder = mockQueryBuilder }; - await filterService.GetFilteredQualifications(4, 5, 2016, null); + await filterService.GetFilteredQualifications(4, 5, 2016, null, null); mockLogger.VerifyError("Qualification date Aug-1a contains unexpected year value"); } From 8259ac89d1f4985aef794408875fec97a7f340df Mon Sep 17 00:00:00 2001 From: Sam Carter Date: Mon, 8 Jul 2024 16:15:00 +0100 Subject: [PATCH 2/7] Moved qualifications to pagesWithForms --- .../cypress/e2e/shared/urls-to-check.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Dfe.EarlyYearsQualification.E2ETests/cypress/e2e/shared/urls-to-check.js b/tests/Dfe.EarlyYearsQualification.E2ETests/cypress/e2e/shared/urls-to-check.js index 94d957d1..305b2f97 100644 --- a/tests/Dfe.EarlyYearsQualification.E2ETests/cypress/e2e/shared/urls-to-check.js +++ b/tests/Dfe.EarlyYearsQualification.E2ETests/cypress/e2e/shared/urls-to-check.js @@ -16,6 +16,7 @@ export const pagesWithForms = [ "/questions/when-was-the-qualification-started", "/questions/what-level-is-the-qualification", "/cookies", + "/qualifications", ]; export const pagesWithoutForms = [ @@ -23,6 +24,5 @@ export const pagesWithoutForms = [ "/accessibility-statement", "/advice/qualification-outside-the-united-kingdom", "/advice/level-2-qualifications-started-between-1-sept-2014-and-31-aug-2019", - "/qualifications", "/qualifications/qualification-details/EYQ-240" ] \ No newline at end of file From d8c22b1e472a1a4ef0b6192ebf9ca907ab203018 Mon Sep 17 00:00:00 2001 From: Sam Carter Date: Tue, 9 Jul 2024 09:02:36 +0100 Subject: [PATCH 3/7] Added additional unit tests for the filters --- .../Constants/AwardingOrganisations.cs | 12 + .../ContentfulContentFilterService.cs | 38 +- .../cypress/e2e/shared/urls-to-check.js | 2 + .../ContentfulContentFilterServiceTests.cs | 370 +++++++++++++++++- 4 files changed, 416 insertions(+), 6 deletions(-) create mode 100644 src/Dfe.EarlyYearsQualification.Content/Constants/AwardingOrganisations.cs diff --git a/src/Dfe.EarlyYearsQualification.Content/Constants/AwardingOrganisations.cs b/src/Dfe.EarlyYearsQualification.Content/Constants/AwardingOrganisations.cs new file mode 100644 index 00000000..83aa21ad --- /dev/null +++ b/src/Dfe.EarlyYearsQualification.Content/Constants/AwardingOrganisations.cs @@ -0,0 +1,12 @@ +namespace Dfe.EarlyYearsQualification.Content.Constants; + +public static class AwardingOrganisations +{ + public const string Edexcel = "Edexcel (now Pearson Education Ltd)"; + + public const string Pearson = "Pearson Education Ltd"; + + public const string Ncfe = "NCFE"; + + public const string Cache = "CACHE Council for Awards in Care Health and Education"; +} \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs b/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs index cbaa2f0b..82495018 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs +++ b/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs @@ -60,10 +60,11 @@ public async Task> GetFilteredQualifications(int? level, int { var awardingOrganisations = new List { - awardingOrganisation, "All Higher Education Institutes", "Various Awarding Organisations" }; + awardingOrganisations.AddRange(IncludeLinkedOrganisations(awardingOrganisation, startDateMonth, startDateYear)); + queryBuilder = queryBuilder.FieldIncludes("fields.awardingOrganisationTitle", awardingOrganisations); } @@ -88,12 +89,39 @@ public async Task> GetFilteredQualifications(int? level, int return filteredQualifications; } - private List FilterQualificationsByName(List qualifications, string? qualificationName) + private static List IncludeLinkedOrganisations(string awardingOrganisation, int? startDateMonth, int? startDateYear) { - if (string.IsNullOrEmpty(qualificationName)) + var result = new List(); + + if (awardingOrganisation is AwardingOrganisations.Edexcel or AwardingOrganisations.Pearson) + { + result.AddRange(new List{AwardingOrganisations.Edexcel, AwardingOrganisations.Pearson}); + } + else if (awardingOrganisation is AwardingOrganisations.Ncfe or AwardingOrganisations.Cache + && startDateMonth.HasValue && startDateYear.HasValue) + { + var cutOffDate = new DateOnly(2014, 9, 1); + var date = new DateOnly(startDateYear.Value, startDateMonth.Value, 1); + if (date >= cutOffDate) + { + result.AddRange(new List{AwardingOrganisations.Ncfe, AwardingOrganisations.Cache}); + } + else + { + result.Add(awardingOrganisation); + } + } + else { - return qualifications; + result.Add(awardingOrganisation); } + + return result; + } + + private static List FilterQualificationsByName(List qualifications, string? qualificationName) + { + if (string.IsNullOrEmpty(qualificationName)) return qualifications; var matchedQualifications = new List(); foreach (var qualification in qualifications) @@ -111,7 +139,7 @@ private List FilterQualificationsByName(List quali private List FilterQualificationsByDate(int? startDateMonth, int? startDateYear, List qualifications) { - if (!startDateMonth.HasValue || !startDateYear.HasValue) return qualifications.ToList(); + if (!startDateMonth.HasValue || !startDateYear.HasValue) return qualifications; var results = new List(); var enteredStartDate = new DateOnly(startDateYear.Value, startDateMonth.Value, Day); diff --git a/tests/Dfe.EarlyYearsQualification.E2ETests/cypress/e2e/shared/urls-to-check.js b/tests/Dfe.EarlyYearsQualification.E2ETests/cypress/e2e/shared/urls-to-check.js index 305b2f97..038cfdcf 100644 --- a/tests/Dfe.EarlyYearsQualification.E2ETests/cypress/e2e/shared/urls-to-check.js +++ b/tests/Dfe.EarlyYearsQualification.E2ETests/cypress/e2e/shared/urls-to-check.js @@ -7,6 +7,7 @@ export const pages = [ "/questions/where-was-the-qualification-awarded", "/questions/when-was-the-qualification-started", "/questions/what-level-is-the-qualification", + "/questions/what-is-the-awarding-organisation", "/qualifications", "/qualifications/qualification-details/EYQ-240" ]; @@ -15,6 +16,7 @@ export const pagesWithForms = [ "/questions/where-was-the-qualification-awarded", "/questions/when-was-the-qualification-started", "/questions/what-level-is-the-qualification", + "/questions/what-is-the-awarding-organisation", "/cookies", "/qualifications", ]; diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs index 85bf54e9..2a4b4b2e 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs @@ -1,6 +1,7 @@ using Contentful.Core; using Contentful.Core.Models; using Contentful.Core.Search; +using Dfe.EarlyYearsQualification.Content.Constants; using Dfe.EarlyYearsQualification.Content.Entities; using Dfe.EarlyYearsQualification.Content.Services; using Dfe.EarlyYearsQualification.UnitTests.Extensions; @@ -139,7 +140,7 @@ public async Task GetFilteredQualifications_PassInAwardingOrganisation_ClientCon var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "NCFE,All Higher Education Institutes,Various Awarding Organisations"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE"); } [TestMethod] @@ -520,6 +521,373 @@ public async Task GetFilteredQualifications_DataContainsInvalidYear_LogsError() mockLogger.VerifyError("Qualification date Aug-1a contains unexpected year value"); } + + [TestMethod] + public async Task GetFilteredQualifications_PassInEdexcel_QueryIncludesPearson() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "test", + "Pearson", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Edexcel, null); + + var queryString = mockQueryBuilder.GetQueryString(); + queryString.Count.Should().Be(2); + queryString.Should().Contain("content_type", "Qualification"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,Edexcel (now Pearson Education Ltd),Pearson Education Ltd"); + } + + [TestMethod] + public async Task GetFilteredQualifications_PassInPearson_QueryIncludesEdexcel() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "test", + "Pearson", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Pearson, null); + + var queryString = mockQueryBuilder.GetQueryString(); + queryString.Count.Should().Be(2); + queryString.Should().Contain("content_type", "Qualification"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,Edexcel (now Pearson Education Ltd),Pearson Education Ltd"); + } + + [TestMethod] + public async Task GetFilteredQualifications_PassInNCFEAndNoStartDate_QueryDoesntContainCache() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "test", + "NCFE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Ncfe, null); + + var queryString = mockQueryBuilder.GetQueryString(); + queryString.Count.Should().Be(2); + queryString.Should().Contain("content_type", "Qualification"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE"); + } + + [TestMethod] + public async Task GetFilteredQualifications_PassInCACHEAndNoStartDate_QueryDoesntContainNCFE() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "test", + "CACHE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Cache, null); + + var queryString = mockQueryBuilder.GetQueryString(); + queryString.Count.Should().Be(2); + queryString.Should().Contain("content_type", "Qualification"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,CACHE Council for Awards in Care Health and Education"); + } + + [TestMethod] + public async Task GetFilteredQualifications_PassInNCFEAndStartDateLessThanSept14_QueryDoesntContainCache() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "test", + "NCFE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + await filterService.GetFilteredQualifications(null, 8, 2014, AwardingOrganisations.Ncfe, null); + + var queryString = mockQueryBuilder.GetQueryString(); + queryString.Count.Should().Be(2); + queryString.Should().Contain("content_type", "Qualification"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE"); + } + + [TestMethod] + public async Task GetFilteredQualifications_PassInCACHEAndStartDateLessThanSept14_QueryDoesntContainNCFE() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "test", + "CACHE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + await filterService.GetFilteredQualifications(null, 8, 2014, AwardingOrganisations.Cache, null); + + var queryString = mockQueryBuilder.GetQueryString(); + queryString.Count.Should().Be(2); + queryString.Should().Contain("content_type", "Qualification"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,CACHE Council for Awards in Care Health and Education"); + } + + [TestMethod] + public async Task GetFilteredQualifications_PassInNCFEAndStartDateGreaterThanSept14_QueryContainsCache() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "test", + "NCFE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + await filterService.GetFilteredQualifications(null, 9, 2014, AwardingOrganisations.Ncfe, null); + + var queryString = mockQueryBuilder.GetQueryString(); + queryString.Count.Should().Be(2); + queryString.Should().Contain("content_type", "Qualification"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE,CACHE Council for Awards in Care Health and Education"); + } + + [TestMethod] + public async Task GetFilteredQualifications_PassInCACHEAndStartDateGreaterThanSept14_QueryContainsNCFE() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "test", + "CACHE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + await filterService.GetFilteredQualifications(null, 9, 2014, AwardingOrganisations.Cache, null); + + var queryString = mockQueryBuilder.GetQueryString(); + queryString.Count.Should().Be(2); + queryString.Should().Contain("content_type", "Qualification"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE,CACHE Council for Awards in Care Health and Education"); + } + + [TestMethod] + public async Task GetFilteredQualifications_PassInQualificationNameThatProducesWeightAbove70_ReturnsQualification() + { + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + "Technical Diploma in Child Care", + "CACHE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements"), + new Qualification( + "EYQ-123", + "Diploma in Early Years Child Care", + "CACHE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + var qualifications = await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Cache, "tecnical"); + + qualifications.Should().NotBeNull(); + qualifications.Count.Should().Be(1); + } } public class MockQueryBuilder : QueryBuilder From 93d2f644750064b5b705045d6948107a8f58cad2 Mon Sep 17 00:00:00 2001 From: Sam Carter Date: Tue, 9 Jul 2024 11:09:12 +0100 Subject: [PATCH 4/7] Added additional unit tests --- .../QualificationDetailsController.cs | 2 +- .../IUserJourneyCookieService.cs | 2 +- .../UserJourneyCookieService.cs | 2 +- .../QualificationDetailsControllerTests.cs | 30 ++++++++++ .../Services/UserJourneyCookieServiceTests.cs | 60 ++++++++++++++++++- 5 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/Dfe.EarlyYearsQualification.Web/Controllers/QualificationDetailsController.cs b/src/Dfe.EarlyYearsQualification.Web/Controllers/QualificationDetailsController.cs index e658f6ef..6e4bc97f 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Controllers/QualificationDetailsController.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Controllers/QualificationDetailsController.cs @@ -38,7 +38,7 @@ public async Task Get() [HttpPost] public IActionResult Refine(string refineSearch) { - userJourneyCookieService.SetNameSearchCriteria(refineSearch); + userJourneyCookieService.SetQualificationNameSearchCriteria(refineSearch); return RedirectToAction("Get"); } diff --git a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs index ab078edc..9387fd2c 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs @@ -9,7 +9,7 @@ public interface IUserJourneyCookieService public void SetLevelOfQualification(string level); public void SetAwardingOrganisation(string awardingOrganisation); - public void SetNameSearchCriteria(string searchCriteria); + public void SetQualificationNameSearchCriteria(string searchCriteria); public UserJourneyModel GetUserJourneyModelFromCookie(); public void ResetUserJourneyCookie(); diff --git a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs index ee94caf9..cee77ac0 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs @@ -51,7 +51,7 @@ public void SetAwardingOrganisation(string awardingOrganisation) SetJourneyCookie(model); } - public void SetNameSearchCriteria(string searchCriteria) + public void SetQualificationNameSearchCriteria(string searchCriteria) { var model = GetUserJourneyModelFromCookie(); diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QualificationDetailsControllerTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QualificationDetailsControllerTests.cs index ba04ae71..9f4799ee 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QualificationDetailsControllerTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Controllers/QualificationDetailsControllerTests.cs @@ -238,4 +238,34 @@ public async Task Get_NoContent_LogsAndRedirectsToError() mockLogger.VerifyError("No content for the qualification list page"); } + + [TestMethod] + public void Refine_SaveQualificationName_RedirectsToGet() + { + var mockLogger = new Mock>(); + var mockContentService = new Mock(); + var mockContentFilterService = new Mock(); + var mockInsetTextRenderer = new Mock(); + var mockHtmlRenderer = new Mock(); + var mockUserJourneyCookieService = new Mock(); + + var controller = + new QualificationDetailsController(mockLogger.Object, mockContentService.Object, mockContentFilterService.Object, mockInsetTextRenderer.Object, + mockHtmlRenderer.Object, mockUserJourneyCookieService.Object) + { + ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext() + } + }; + + var result = controller.Refine("Test"); + + result.Should().BeOfType(); + + var actionResult = (RedirectToActionResult)result; + + actionResult.ActionName.Should().Be("Get"); + mockUserJourneyCookieService.Verify(x => x.SetQualificationNameSearchCriteria("Test"), Times.Once); + } } \ No newline at end of file diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/UserJourneyCookieServiceTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/UserJourneyCookieServiceTests.cs index 541b1c6e..48f63b97 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/UserJourneyCookieServiceTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/UserJourneyCookieServiceTests.cs @@ -293,7 +293,7 @@ public void GetWhenWasQualificationAwarded_CookieValueIsEmpty_ReturnsNull() var service = new UserJourneyCookieService(mockHttpContextAccessor.Object, mockLogger.Object); - (int? startMonth, int? startYear) = service.GetWhenWasQualificationAwarded(); + (int? startMonth, int? startYear) = service.GetWhenWasQualificationAwarded(); startMonth.Should().BeNull(); startYear.Should().BeNull(); @@ -311,7 +311,7 @@ public void GetWhenWasQualificationAwarded_CookieHasInvalidValue_ReturnsNull() var service = new UserJourneyCookieService(mockHttpContextAccessor.Object, mockLogger.Object); - (int? startMonth, int? startYear) = service.GetWhenWasQualificationAwarded(); + (int? startMonth, int? startYear) = service.GetWhenWasQualificationAwarded(); startMonth.Should().BeNull(); startYear.Should().BeNull(); @@ -329,11 +329,65 @@ public void GetWhenWasQualificationAwarded_CookieHasValidValue_ReturnsValue() var service = new UserJourneyCookieService(mockHttpContextAccessor.Object, mockLogger.Object); - (int? startMonth, int? startYear) = service.GetWhenWasQualificationAwarded(); + (int? startMonth, int? startYear) = service.GetWhenWasQualificationAwarded(); startMonth.Should().Be(4); startYear.Should().Be(2015); } + + [TestMethod] + public void SetNameSearchCriteria_StringProvided_SetsCookieCorrectly() + { + var modelInCookie = new UserJourneyModel(); + var mockHttpContextAccessor = SetHttpContextWithExistingCookie(modelInCookie); + var mockLogger = new Mock>(); + + var service = new UserJourneyCookieService(mockHttpContextAccessor.Object, mockLogger.Object); + + var searchCriteria = "This is a test"; + service.SetQualificationNameSearchCriteria(searchCriteria); + + var serialisedModelToCheck = JsonSerializer.Serialize(new UserJourneyModel + { + SearchCriteria = searchCriteria + }); + + CheckSerializedModelWasSet(mockHttpContextAccessor, serialisedModelToCheck); + } + + [TestMethod] + public void GetSearchCriteria_CookieHasInvalidValue_ReturnsNull() + { + var existingModel = new UserJourneyModel + { + SearchCriteria = "" + }; + var mockHttpContextAccessor = SetHttpContextWithExistingCookie(existingModel); + var mockLogger = new Mock>(); + + var service = new UserJourneyCookieService(mockHttpContextAccessor.Object, mockLogger.Object); + + var searchCriteria = service.GetSearchCriteria(); + + searchCriteria.Should().BeNull(); + } + + [TestMethod] + public void GetSearchCriteria_CookieHasValidValue_ReturnsValue() + { + var existingModel = new UserJourneyModel + { + SearchCriteria = "Test" + }; + var mockHttpContextAccessor = SetHttpContextWithExistingCookie(existingModel); + var mockLogger = new Mock>(); + + var service = new UserJourneyCookieService(mockHttpContextAccessor.Object, mockLogger.Object); + + var searchCriteria = service.GetSearchCriteria(); + + searchCriteria.Should().Be("Test"); + } private static Mock SetHttpContextWithExistingCookie(object? model) { From 9d02989100de68551d2635ce6087cf05e01c8b79 Mon Sep 17 00:00:00 2001 From: RobertGHippo Date: Tue, 9 Jul 2024 11:20:25 +0100 Subject: [PATCH 5/7] Abstract static Fuzzy into injected adapter service. --- .../ContentfulContentFilterService.cs | 33 ++- .../Services/FuzzyAdapter.cs | 11 + .../Services/IFuzzyAdapter.cs | 6 + .../Models/Content/QualificationListModel.cs | 4 +- .../Program.cs | 3 +- .../ContentfulContentFilterServiceTests.cs | 271 +++++++++++------- 6 files changed, 214 insertions(+), 114 deletions(-) create mode 100644 src/Dfe.EarlyYearsQualification.Content/Services/FuzzyAdapter.cs create mode 100644 src/Dfe.EarlyYearsQualification.Content/Services/IFuzzyAdapter.cs diff --git a/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs b/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs index 82495018..99026cd3 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs +++ b/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentFilterService.cs @@ -5,13 +5,13 @@ using Contentful.Core.Search; using Dfe.EarlyYearsQualification.Content.Constants; using Dfe.EarlyYearsQualification.Content.Entities; -using FuzzySharp; using Microsoft.Extensions.Logging; namespace Dfe.EarlyYearsQualification.Content.Services; public class ContentfulContentFilterService( IContentfulClient contentfulClient, + IFuzzyAdapter fuzzyAdapter, ILogger logger) : IContentFilterService { @@ -39,9 +39,10 @@ private static readonly ReadOnlyDictionary public QueryBuilder QueryBuilder { get; init; } = QueryBuilder.New; public async Task> GetFilteredQualifications(int? level, int? startDateMonth, - int? startDateYear, string? awardingOrganisation, string? qualificationName) + int? startDateYear, string? awardingOrganisation, + string? qualificationName) { - logger.LogInformation("Filtering options passed in - level: {Level}, startDateMonth: {StartDateMonth}, startDateYear: {StartDateYear}, awardingOrganisation: {AwardingOrganisation}, qualificationName: {qualificationName}", + logger.LogInformation("Filtering options passed in - level: {Level}, startDateMonth: {StartDateMonth}, startDateYear: {StartDateYear}, awardingOrganisation: {AwardingOrganisation}, qualificationName: {QualificationName}", level, startDateMonth, startDateYear, @@ -63,8 +64,9 @@ public async Task> GetFilteredQualifications(int? level, int "All Higher Education Institutes", "Various Awarding Organisations" }; - awardingOrganisations.AddRange(IncludeLinkedOrganisations(awardingOrganisation, startDateMonth, startDateYear)); - + awardingOrganisations.AddRange(IncludeLinkedOrganisations(awardingOrganisation, startDateMonth, + startDateYear)); + queryBuilder = queryBuilder.FieldIncludes("fields.awardingOrganisationTitle", awardingOrganisations); } @@ -79,23 +81,24 @@ public async Task> GetFilteredQualifications(int? level, int logger.LogError(e, "Error getting qualifications"); return []; } - + // apply start date filtering var filteredQualifications = FilterQualificationsByDate(startDateMonth, startDateYear, qualifications.ToList()); - + // Filter based on qualification name filteredQualifications = FilterQualificationsByName(filteredQualifications, qualificationName); return filteredQualifications; } - private static List IncludeLinkedOrganisations(string awardingOrganisation, int? startDateMonth, int? startDateYear) + private static List IncludeLinkedOrganisations(string awardingOrganisation, int? startDateMonth, + int? startDateYear) { var result = new List(); if (awardingOrganisation is AwardingOrganisations.Edexcel or AwardingOrganisations.Pearson) { - result.AddRange(new List{AwardingOrganisations.Edexcel, AwardingOrganisations.Pearson}); + result.AddRange(new List { AwardingOrganisations.Edexcel, AwardingOrganisations.Pearson }); } else if (awardingOrganisation is AwardingOrganisations.Ncfe or AwardingOrganisations.Cache && startDateMonth.HasValue && startDateYear.HasValue) @@ -104,7 +107,7 @@ private static List IncludeLinkedOrganisations(string awardingOrganisati var date = new DateOnly(startDateYear.Value, startDateMonth.Value, 1); if (date >= cutOffDate) { - result.AddRange(new List{AwardingOrganisations.Ncfe, AwardingOrganisations.Cache}); + result.AddRange(new List { AwardingOrganisations.Ncfe, AwardingOrganisations.Cache }); } else { @@ -115,18 +118,20 @@ private static List IncludeLinkedOrganisations(string awardingOrganisati { result.Add(awardingOrganisation); } - + return result; } - private static List FilterQualificationsByName(List qualifications, string? qualificationName) + private List FilterQualificationsByName( + List qualifications, + string? qualificationName) { if (string.IsNullOrEmpty(qualificationName)) return qualifications; var matchedQualifications = new List(); foreach (var qualification in qualifications) { - var weight = Fuzz.PartialRatio(qualificationName, qualification.QualificationName); + var weight = fuzzyAdapter.PartialRatio(qualificationName, qualification.QualificationName); if (weight > 70) { matchedQualifications.Add(qualification); @@ -140,7 +145,7 @@ private List FilterQualificationsByDate(int? startDateMonth, int? List qualifications) { if (!startDateMonth.HasValue || !startDateYear.HasValue) return qualifications; - + var results = new List(); var enteredStartDate = new DateOnly(startDateYear.Value, startDateMonth.Value, Day); foreach (var qualification in qualifications) diff --git a/src/Dfe.EarlyYearsQualification.Content/Services/FuzzyAdapter.cs b/src/Dfe.EarlyYearsQualification.Content/Services/FuzzyAdapter.cs new file mode 100644 index 00000000..c72e897d --- /dev/null +++ b/src/Dfe.EarlyYearsQualification.Content/Services/FuzzyAdapter.cs @@ -0,0 +1,11 @@ +using FuzzySharp; + +namespace Dfe.EarlyYearsQualification.Content.Services; + +public class FuzzyAdapter : IFuzzyAdapter +{ + public int PartialRatio(string input1, string input2) + { + return Fuzz.PartialRatio(input1, input2); + } +} \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Content/Services/IFuzzyAdapter.cs b/src/Dfe.EarlyYearsQualification.Content/Services/IFuzzyAdapter.cs new file mode 100644 index 00000000..0727ad90 --- /dev/null +++ b/src/Dfe.EarlyYearsQualification.Content/Services/IFuzzyAdapter.cs @@ -0,0 +1,6 @@ +namespace Dfe.EarlyYearsQualification.Content.Services; + +public interface IFuzzyAdapter +{ + public int PartialRatio(string input1, string input2); +} \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QualificationListModel.cs b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QualificationListModel.cs index 47790ece..19a722c8 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QualificationListModel.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QualificationListModel.cs @@ -9,7 +9,7 @@ public class QualificationListModel public FilterModel Filters { get; init; } = new(); public NavigationLink? BackButton { get; init; } - + public string SingleQualificationFoundText { get; init; } = string.Empty; public string MultipleQualificationsFoundText { get; init; } = string.Empty; @@ -28,7 +28,7 @@ public class QualificationListModel public string PostSearchCriteriaContent { get; init; } = string.Empty; - public string? SearchCriteria { get; set; } = string.Empty; + public string? SearchCriteria { get; init; } = string.Empty; public List Qualifications { get; init; } = []; } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Program.cs b/src/Dfe.EarlyYearsQualification.Web/Program.cs index a2bc4026..5698e261 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Program.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Program.cs @@ -64,6 +64,8 @@ return factory.GetUrlHelper(actionContext!); }); +builder.Services.AddSingleton(); + var accessIsChallenged = !builder.Configuration.GetValue("ServiceAccess:IsPublic"); // ...by default, challenge the user for the secret value unless that's explicitly turned off @@ -105,7 +107,6 @@ await app.RunAsync(); - [ExcludeFromCodeCoverage] // ReSharper disable once UnusedType.Global // ...declared partial so we can exclude it from code coverage calculations diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs index 2a4b4b2e..7e83380a 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs @@ -47,12 +47,15 @@ public async Task GetFilteredQualifications_PassInNullParameters_ReturnsAllQuali It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; var filteredQualifications = await filterService.GetFilteredQualifications(null, null, null, null, null); @@ -88,12 +91,15 @@ public async Task GetFilteredQualifications_PassInLevel_ClientContainsLevelInQue It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(4, null, null, null, null); @@ -102,7 +108,7 @@ public async Task GetFilteredQualifications_PassInLevel_ClientContainsLevelInQue queryString.Should().Contain("content_type", "Qualification"); queryString.Should().Contain("fields.qualificationLevel", "4"); } - + [TestMethod] public async Task GetFilteredQualifications_PassInAwardingOrganisation_ClientContainsAwardingOrganisationInQuery() { @@ -128,19 +134,23 @@ public async Task GetFilteredQualifications_PassInAwardingOrganisation_ClientCon It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, null, null, "NCFE", null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,NCFE"); } [TestMethod] @@ -185,12 +195,15 @@ public async Task GetFilteredQualifications_PassInLevelMonthAndNullParameters_Re It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; var filteredQualifications = await filterService.GetFilteredQualifications(4, 9, null, null, null); @@ -243,12 +256,15 @@ public async Task GetFilteredQualifications_PassInLevelNullAndYearParameters_Ret It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; var filteredQualifications = await filterService.GetFilteredQualifications(4, null, 2014, null, null); @@ -310,12 +326,15 @@ public async Task GetFilteredQualifications_FilterOnDates_ReturnsFilteredQualifi It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null, null); @@ -377,12 +396,15 @@ public async Task GetFilteredQualifications_FilterOnDates_MonthIsCaseInsensitive It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null, null); @@ -402,8 +424,11 @@ public async Task GetFilteredQualifications_ContentfulClientThrowsException_Retu It.IsAny())) .ThrowsAsync(new Exception()); + var mockFuzzyAdapter = new Mock(); + var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object); + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object); var filteredQualifications = await filterService.GetFilteredQualifications(4, 5, 2016, null, null); @@ -436,12 +461,15 @@ public async Task GetFilteredQualifications_DataContainsInvalidDateFormat_LogsEr It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(4, 5, 2016, null, null); @@ -473,12 +501,15 @@ public async Task GetFilteredQualifications_DataContainsInvalidMonth_LogsError() It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(4, 5, 2016, null, null); @@ -510,12 +541,15 @@ public async Task GetFilteredQualifications_DataContainsInvalidYear_LogsError() It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(4, 5, 2016, null, null); @@ -547,21 +581,25 @@ public async Task GetFilteredQualifications_PassInEdexcel_QueryIncludesPearson() It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Edexcel, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,Edexcel (now Pearson Education Ltd),Pearson Education Ltd"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,Edexcel (now Pearson Education Ltd),Pearson Education Ltd"); } - + [TestMethod] public async Task GetFilteredQualifications_PassInPearson_QueryIncludesEdexcel() { @@ -587,21 +625,25 @@ public async Task GetFilteredQualifications_PassInPearson_QueryIncludesEdexcel() It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Pearson, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,Edexcel (now Pearson Education Ltd),Pearson Education Ltd"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,Edexcel (now Pearson Education Ltd),Pearson Education Ltd"); } - + [TestMethod] public async Task GetFilteredQualifications_PassInNCFEAndNoStartDate_QueryDoesntContainCache() { @@ -627,21 +669,25 @@ public async Task GetFilteredQualifications_PassInNCFEAndNoStartDate_QueryDoesnt It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Ncfe, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,NCFE"); } - + [TestMethod] public async Task GetFilteredQualifications_PassInCACHEAndNoStartDate_QueryDoesntContainNCFE() { @@ -667,21 +713,25 @@ public async Task GetFilteredQualifications_PassInCACHEAndNoStartDate_QueryDoesn It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Cache, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,CACHE Council for Awards in Care Health and Education"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,CACHE Council for Awards in Care Health and Education"); } - + [TestMethod] public async Task GetFilteredQualifications_PassInNCFEAndStartDateLessThanSept14_QueryDoesntContainCache() { @@ -707,21 +757,25 @@ public async Task GetFilteredQualifications_PassInNCFEAndStartDateLessThanSept14 It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, 8, 2014, AwardingOrganisations.Ncfe, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,NCFE"); } - + [TestMethod] public async Task GetFilteredQualifications_PassInCACHEAndStartDateLessThanSept14_QueryDoesntContainNCFE() { @@ -747,21 +801,25 @@ public async Task GetFilteredQualifications_PassInCACHEAndStartDateLessThanSept1 It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, 8, 2014, AwardingOrganisations.Cache, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,CACHE Council for Awards in Care Health and Education"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,CACHE Council for Awards in Care Health and Education"); } - + [TestMethod] public async Task GetFilteredQualifications_PassInNCFEAndStartDateGreaterThanSept14_QueryContainsCache() { @@ -787,21 +845,25 @@ public async Task GetFilteredQualifications_PassInNCFEAndStartDateGreaterThanSep It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, 9, 2014, AwardingOrganisations.Ncfe, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE,CACHE Council for Awards in Care Health and Education"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,NCFE,CACHE Council for Awards in Care Health and Education"); } - + [TestMethod] public async Task GetFilteredQualifications_PassInCACHEAndStartDateGreaterThanSept14_QueryContainsNCFE() { @@ -827,31 +889,40 @@ public async Task GetFilteredQualifications_PassInCACHEAndStartDateGreaterThanSe It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; await filterService.GetFilteredQualifications(null, 9, 2014, AwardingOrganisations.Cache, null); var queryString = mockQueryBuilder.GetQueryString(); queryString.Count.Should().Be(2); queryString.Should().Contain("content_type", "Qualification"); - queryString.Should().Contain("fields.awardingOrganisationTitle[in]", "All Higher Education Institutes,Various Awarding Organisations,NCFE,CACHE Council for Awards in Care Health and Education"); + queryString.Should().Contain("fields.awardingOrganisationTitle[in]", + "All Higher Education Institutes,Various Awarding Organisations,NCFE,CACHE Council for Awards in Care Health and Education"); } [TestMethod] public async Task GetFilteredQualifications_PassInQualificationNameThatProducesWeightAbove70_ReturnsQualification() { + // ReSharper disable once StringLiteralTypo + const string qualificationSearch = "teknical"; + + const string technicalDiplomaInChildCare = "Technical Diploma in Child Care"; + var results = new ContentfulCollection { Items = new[] { new Qualification( "EYQ-123", - "Technical Diploma in Child Care", + technicalDiplomaInChildCare, "CACHE", 4, "Apr-15", @@ -876,14 +947,20 @@ public async Task GetFilteredQualifications_PassInQualificationNameThatProducesW It.IsAny())) .ReturnsAsync(results); + var mockFuzzyAdapter = new Mock(); + mockFuzzyAdapter.Setup(a => a.PartialRatio(qualificationSearch, technicalDiplomaInChildCare)).Returns(80); + var mockQueryBuilder = new MockQueryBuilder(); var mockLogger = new Mock>(); - var filterService = new ContentfulContentFilterService(mockContentfulClient.Object, mockLogger.Object) - { - QueryBuilder = mockQueryBuilder - }; - - var qualifications = await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Cache, "tecnical"); + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + var qualifications = + await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Cache, + qualificationSearch); qualifications.Should().NotBeNull(); qualifications.Count.Should().Be(1); From 2af6548e20059bfdab9240de085339bf83dce568 Mon Sep 17 00:00:00 2001 From: RobertGHippo Date: Tue, 9 Jul 2024 11:40:17 +0100 Subject: [PATCH 6/7] Bake in value of 70 as the fuzzy cutoff. --- .../ContentfulContentFilterServiceTests.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs index 7e83380a..8e902106 100644 --- a/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs +++ b/tests/Dfe.EarlyYearsQualification.UnitTests/Services/ContentfulContentFilterServiceTests.cs @@ -965,6 +965,65 @@ await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisa qualifications.Should().NotBeNull(); qualifications.Count.Should().Be(1); } + + [TestMethod] + public async Task + GetFilteredQualifications_PassInQualificationNameThatProducesWeightEqual70_DoesNotReturnQualification() + { + // ReSharper disable once StringLiteralTypo + const string qualificationSearch = "teknical"; + + const string technicalDiplomaInChildCare = "Technical Diploma in Child Care"; + + var results = new ContentfulCollection + { + Items = new[] + { + new Qualification( + "EYQ-123", + technicalDiplomaInChildCare, + "CACHE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements"), + new Qualification( + "EYQ-123", + "Diploma in Early Years Child Care", + "CACHE", + 4, + "Apr-15", + "Aug-19", + "abc/123/987", + "requirements") + } + }; + + var mockContentfulClient = new Mock(); + mockContentfulClient.Setup(x => x.GetEntries( + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(results); + + var mockFuzzyAdapter = new Mock(); + mockFuzzyAdapter.Setup(a => a.PartialRatio(qualificationSearch, technicalDiplomaInChildCare)).Returns(70); + + var mockQueryBuilder = new MockQueryBuilder(); + var mockLogger = new Mock>(); + var filterService = + new ContentfulContentFilterService(mockContentfulClient.Object, mockFuzzyAdapter.Object, mockLogger.Object) + { + QueryBuilder = mockQueryBuilder + }; + + var qualifications = + await filterService.GetFilteredQualifications(null, null, null, AwardingOrganisations.Cache, + qualificationSearch); + + qualifications.Should().NotBeNull(); + qualifications.Should().BeEmpty(); + } } public class MockQueryBuilder : QueryBuilder From 162b5684bacf1471bcf8866e615a4aa9e7d34441 Mon Sep 17 00:00:00 2001 From: Sam Carter Date: Tue, 9 Jul 2024 14:28:29 +0100 Subject: [PATCH 7/7] Added various and all higher education consts --- .../Constants/AwardingOrganisations.cs | 4 ++++ .../Controllers/QuestionsController.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Dfe.EarlyYearsQualification.Content/Constants/AwardingOrganisations.cs b/src/Dfe.EarlyYearsQualification.Content/Constants/AwardingOrganisations.cs index 83aa21ad..c58da5de 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Constants/AwardingOrganisations.cs +++ b/src/Dfe.EarlyYearsQualification.Content/Constants/AwardingOrganisations.cs @@ -9,4 +9,8 @@ public static class AwardingOrganisations public const string Ncfe = "NCFE"; public const string Cache = "CACHE Council for Awards in Care Health and Education"; + + public const string Various = "Various Awarding Organisations"; + + public const string AllHigherEducation = "All Higher Education Institutes"; } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs b/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs index a65ff6e5..0ce6956f 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs @@ -240,7 +240,7 @@ private static DropdownQuestionModel MapDropdownModel(DropdownQuestionModel mode string controllerName) { var awardingOrganisationExclusions = - new[] { "All Higher Education Institutes", "Various Awarding Organisations" }; + new[] { AwardingOrganisations.AllHigherEducation, AwardingOrganisations.Various }; var uniqueAwardingOrganisations = qualifications.Select(x => x.AwardingOrganisationTitle) .Distinct() .Where(x => !awardingOrganisationExclusions.Any(x.Contains))