diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseAdminFieldsDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseAdminFieldsDataServiceTests.cs index 6a9b46cc6d..b20b3c1e87 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseAdminFieldsDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseAdminFieldsDataServiceTests.cs @@ -35,7 +35,7 @@ public void GetCourseAdminFields_returns_populated_CourseAdminFieldsResult() ); // When - var returnedCourseAdminFieldsResult = courseAdminFieldsDataService.GetCourseAdminFields(100, 101, 0); + var returnedCourseAdminFieldsResult = courseAdminFieldsDataService.GetCourseAdminFields(100, 101); // Then returnedCourseAdminFieldsResult.Should().BeEquivalentTo(expectedCourseAdminFieldsResult); @@ -52,7 +52,7 @@ public void UpdateCustomPromptForCourse_correctly_updates_custom_prompt() // When courseAdminFieldsDataService.UpdateCustomPromptForCourse(100, 1, 1, options); - var courseAdminFields = courseAdminFieldsDataService.GetCourseAdminFields(100, 101, 0); + var courseAdminFields = courseAdminFieldsDataService.GetCourseAdminFields(100, 101); // Then using (new AssertionScope()) @@ -87,7 +87,7 @@ public void UpdateCustomPromptForCourse_correctly_adds_custom_prompt() // When courseAdminFieldsDataService.UpdateCustomPromptForCourse(100, 3, 1, options); - var courseCustomPrompts = courseAdminFieldsDataService.GetCourseAdminFields(100, 101, 2); + var courseCustomPrompts = courseAdminFieldsDataService.GetCourseAdminFields(100, 101); var customPrompt = courseAdminFieldsDataService.GetCoursePromptsAlphabetical() .Single(c => c.id == 1) .name; diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs index 676963a8f0..540f100cdf 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs @@ -9,7 +9,6 @@ namespace DigitalLearningSolutions.Data.Tests.DataServices using DigitalLearningSolutions.Data.Tests.TestHelpers; using FakeItEasy; using FluentAssertions; - using FluentAssertions.Execution; using Microsoft.Extensions.Logging; using NUnit.Framework; @@ -364,5 +363,35 @@ public void GetCentrallyManagedAndCentreCourses_returns_expected_values() result.Should().HaveCount(260); result.First().Should().BeEquivalentTo(expectedFirstCourse); } + + [Test] + public void DoesCourseExistAtCentre_returns_true_if_course_exists() + { + // When + var result = courseDataService.DoesCourseExistAtCentre(100, 101, null); + + // Then + result.Should().BeTrue(); + } + + [Test] + public void DoesCourseExistAtCentre_returns_false_if_course_does_not_exist_at_centre() + { + // When + var result = courseDataService.DoesCourseExistAtCentre(100, 2, 0); + + // Then + result.Should().BeFalse(); + } + + [Test] + public void DoesCourseExistAtCentre_returns_false_if_course_does_not_exist_with_categoryId() + { + // When + var result = courseDataService.DoesCourseExistAtCentre(100, 101, 99); + + // Then + result.Should().BeFalse(); + } } } diff --git a/DigitalLearningSolutions.Data.Tests/Services/CourseAdminFieldsServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/CourseAdminFieldsServiceTests.cs index 52912fef57..6ecfb8f9a7 100644 --- a/DigitalLearningSolutions.Data.Tests/Services/CourseAdminFieldsServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Services/CourseAdminFieldsServiceTests.cs @@ -35,11 +35,11 @@ public void GetCustomPromptsForCourse_Returns_Populated_CourseAdminFields() var expectedPrompt2 = CustomPromptsTestHelper.GetDefaultCustomPrompt(2, "Priority Access"); var customPrompts = new List { expectedPrompt1, expectedPrompt2 }; var expectedCourseAdminFields = CustomPromptsTestHelper.GetDefaultCourseAdminFields(customPrompts); - A.CallTo(() => courseAdminFieldsDataService.GetCourseAdminFields(100, 101, 0)) + A.CallTo(() => courseAdminFieldsDataService.GetCourseAdminFields(100, 101)) .Returns(CustomPromptsTestHelper.GetDefaultCourseAdminFieldsResult()); // When - var result = courseAdminFieldsService.GetCustomPromptsForCourse(100, 101, 0); + var result = courseAdminFieldsService.GetCustomPromptsForCourse(100, 101); // Then result.Should().BeEquivalentTo(expectedCourseAdminFields); @@ -63,7 +63,7 @@ public void GetCustomPromptsWithAnswersForCourse_Returns_Populated_List_of_Custo answer: answer2 ); var expected = new List { expected1, expected2 }; - A.CallTo(() => courseAdminFieldsDataService.GetCourseAdminFields(100, 101, 0)) + A.CallTo(() => courseAdminFieldsDataService.GetCourseAdminFields(100, 101)) .Returns(CustomPromptsTestHelper.GetDefaultCourseAdminFieldsResult()); var delegateCourseInfo = new DelegateCourseInfo { Answer1 = answer1, Answer2 = answer2 }; @@ -112,11 +112,11 @@ public void AddCustomPromptToCourse_adds_prompt_to_course_at_next_prompt_number( ( () => courseAdminFieldsDataService.UpdateCustomPromptForCourse(100, A._, A._, null) ).DoesNothing(); - A.CallTo(() => courseAdminFieldsDataService.GetCourseAdminFields(100, 101, 0)) + A.CallTo(() => courseAdminFieldsDataService.GetCourseAdminFields(100, 101)) .Returns(CustomPromptsTestHelper.GetDefaultCourseAdminFieldsResult()); // When - var result = courseAdminFieldsService.AddCustomPromptToCourse(100, 101, 0, 3, null); + var result = courseAdminFieldsService.AddCustomPromptToCourse(100, 101, 3, null); // Then A.CallTo @@ -134,7 +134,7 @@ public void AddCustomPromptToCourse_does_not_add_prompt_if_course_has_all_prompt ( () => courseAdminFieldsDataService.UpdateCustomPromptForCourse(100, A._, A._, null) ).DoesNothing(); - A.CallTo(() => courseAdminFieldsDataService.GetCourseAdminFields(100, 101, 0)) + A.CallTo(() => courseAdminFieldsDataService.GetCourseAdminFields(100, 101)) .Returns( CustomPromptsTestHelper.GetDefaultCourseAdminFieldsResult( "System Access Granted", @@ -149,7 +149,6 @@ public void AddCustomPromptToCourse_does_not_add_prompt_if_course_has_all_prompt var result = courseAdminFieldsService.AddCustomPromptToCourse( 100, 101, - 0, 3, "Adding a fourth prompt" ); diff --git a/DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs index a2f8a38d5e..497e1b97bf 100644 --- a/DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs @@ -109,8 +109,7 @@ public void GetAllCoursesForDelegate_should_call_correct_data_service_and_helper () => courseAdminFieldsService.GetCustomPromptsWithAnswersForCourse( info, customisationId, - CentreId, - 0 + CentreId ) ).MustHaveHappened(1, Times.Exactly); A.CallTo(() => courseDataService.GetDelegateCourseAttemptStats(delegateId, customisationId)) @@ -140,8 +139,7 @@ public void GetAllCoursesForDelegate_should_not_fetch_attempt_stats_if_course_no () => courseAdminFieldsService.GetCustomPromptsWithAnswersForCourse( info, customisationId, - CentreId, - 0 + CentreId ) ).MustHaveHappened(1, Times.Exactly); A.CallTo(() => courseDataService.GetDelegateCourseAttemptStats(A._, A._)).MustNotHaveHappened(); @@ -149,5 +147,37 @@ public void GetAllCoursesForDelegate_should_not_fetch_attempt_stats_if_course_no results[0].DelegateCourseInfo.Should().BeEquivalentTo(info); results[0].AttemptStats.Should().Be((0, 0)); } + + [Test] + public void VerifyAdminUserCanAccessCourse_should_call_correct_data_service_method() + { + // Given + A.CallTo(() => courseDataService.DoesCourseExistAtCentre(A._, A._, A._)) + .Returns(true); + + // When + var result = courseService.VerifyAdminUserCanAccessCourse(1, 2, 2); + + // Then + A.CallTo(() => courseDataService.DoesCourseExistAtCentre(A._, A._, A._)) + .MustHaveHappened(1, Times.Exactly); + result.Should().BeTrue(); + } + + [Test] + public void VerifyAdminUserCanAccessCourse_should_return_return_false_with_incorrect_ids() + { + // Given + A.CallTo(() => courseDataService.DoesCourseExistAtCentre(A._, A._, A._)) + .Returns(false); + + // When + var result = courseService.VerifyAdminUserCanAccessCourse(1, 1, 1); + + // Then + A.CallTo(() => courseDataService.DoesCourseExistAtCentre(A._, A._, A._)) + .MustHaveHappened(1, Times.Exactly); + result.Should().BeFalse(); + } } } diff --git a/DigitalLearningSolutions.Data/DataServices/CourseAdminFieldsDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseAdminFieldsDataService.cs index f1a0e29fcd..5cf115918c 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseAdminFieldsDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseAdminFieldsDataService.cs @@ -8,7 +8,7 @@ public interface ICourseAdminFieldsDataService { - CourseAdminFieldsResult? GetCourseAdminFields(int customisationId, int centreId, int categoryId); + CourseAdminFieldsResult? GetCourseAdminFields(int customisationId, int centreId); void UpdateCustomPromptForCourse(int customisationId, int promptNumber, string? options); @@ -37,7 +37,7 @@ public CourseAdminFieldsDataService(IDbConnection connection) this.connection = connection; } - public CourseAdminFieldsResult GetCourseAdminFields(int customisationId, int centreId, int categoryId) + public CourseAdminFieldsResult GetCourseAdminFields(int customisationId, int centreId) { var result = connection.Query( @"SELECT @@ -63,7 +63,7 @@ LEFT JOIN CoursePrompts AS cp3 WHERE cu.CentreID = @centreId AND ap.ArchivedDate IS NULL AND cu.CustomisationID = @customisationId", - new { customisationId, centreId, categoryId } + new { customisationId, centreId } ).Single(); return result; diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index 7a9e8ed55f..9dae44d545 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -23,6 +23,7 @@ public interface ICourseDataService CourseNameInfo? GetCourseNameAndApplication(int customisationId); CourseDetails? GetCourseDetailsForAdminCategoryId(int customisationId, int centreId, int categoryId); IEnumerable GetCentrallyManagedAndCentreCourses(int centreId, int? categoryId); + bool DoesCourseExistAtCentre(int customisationId, int centreId, int? categoryId); } public class CourseDataService : ICourseDataService @@ -30,7 +31,7 @@ public class CourseDataService : ICourseDataService private const string DelegateCountQuery = @"(SELECT COUNT(pr.CandidateID) FROM dbo.Progress AS pr - INNER JOIN dbo.Candidates AS can ON can.CandidateID = pr.CandidateID + INNER JOIN dbo.Candidates AS can ON can.CandidateID = pr.CandidateID WHERE pr.CustomisationID = cu.CustomisationID AND can.CentreID = @centreId AND RemovedDate IS NULL) AS DelegateCount"; @@ -38,7 +39,7 @@ FROM dbo.Progress AS pr private const string CompletedCountQuery = @"(SELECT COUNT(pr.CandidateID) FROM dbo.Progress AS pr - INNER JOIN dbo.Candidates AS can ON can.CandidateID = pr.CandidateID + INNER JOIN dbo.Candidates AS can ON can.CandidateID = pr.CandidateID WHERE pr.CustomisationID = cu.CustomisationID AND pr.Completed IS NOT NULL AND can.CentreID = @centreId) AS CompletedCount"; @@ -167,7 +168,7 @@ public int GetNumberOfActiveCoursesAtCentreForCategory(int centreId, int adminCa @"SELECT COUNT(*) FROM Customisations AS c JOIN Applications AS a on a.ApplicationID = c.ApplicationID - WHERE Active = 1 AND CentreID = @centreId + WHERE Active = 1 AND CentreID = @centreId AND (a.CourseCategoryID = @adminCategoryId OR @adminCategoryId = 0)", new { centreId, adminCategoryId } ); @@ -199,7 +200,7 @@ FROM dbo.Customisations AS cu INNER JOIN dbo.Applications AS ap ON ap.ApplicationID = ca.ApplicationID INNER JOIN dbo.CourseCategories AS cc ON cc.CourseCategoryID = ap.CourseCategoryID INNER JOIN dbo.CourseTopics AS ct ON ct.CourseTopicID = ap.CourseTopicId - WHERE (ap.CourseCategoryID = @categoryId OR @categoryId = 0) + WHERE (ap.CourseCategoryID = @categoryId OR @categoryId = 0) AND (cu.CentreID = @centreId OR (cu.AllCentres = 1 AND ca.Active = 1)) AND ca.CentreID = @centreId AND ap.ArchivedDate IS NULL", @@ -298,9 +299,9 @@ FROM dbo.Customisations AS cu LEFT JOIN dbo.Customisations AS refreshToCu ON refreshToCu.CustomisationID = cu.RefreshToCustomisationId LEFT JOIN dbo.Applications AS refreshToAp ON refreshToAp.ApplicationID = refreshToCu.ApplicationID WHERE - (ap.CourseCategoryID = @categoryId OR @categoryId = 0) + (ap.CourseCategoryID = @categoryId OR @categoryId = 0) AND cu.CentreID = @centreId - AND ap.ArchivedDate IS NULL + AND ap.ArchivedDate IS NULL AND cu.CustomisationID = @customisationId", new { customisationId, centreId, categoryId } ).FirstOrDefault(); @@ -309,9 +310,9 @@ AND ap.ArchivedDate IS NULL public CourseNameInfo? GetCourseNameAndApplication(int customisationId) { var names = connection.QueryFirstOrDefault( - @"SELECT cu.CustomisationId, cu.CustomisationName, ap.ApplicationName + @"SELECT cu.CustomisationName, ap.ApplicationName FROM Customisations cu - JOIN Applications ap ON cu.ApplicationId = ap.ApplicationId + JOIN Applications ap ON cu.ApplicationId = ap.ApplicationId WHERE cu.CustomisationId = @customisationId", new { customisationId } ); @@ -345,5 +346,22 @@ FROM Customisations AS c new { centreId, categoryId } ); } + + public bool DoesCourseExistAtCentre(int customisationId, int centreId, int? categoryId) + { + return connection.ExecuteScalar( + @"SELECT CASE WHEN EXISTS ( + SELECT * + FROM Customisations AS c + JOIN Applications AS a on a.ApplicationID = c.ApplicationID + WHERE CustomisationID = @customisationId + AND c.CentreID = @centreId + AND (a.CourseCategoryID = @categoryId OR @categoryId IS NULL) + ) + THEN CAST(1 AS BIT) + ELSE CAST(0 AS BIT) END", + new { customisationId, centreId, categoryId } + ); + } } } diff --git a/DigitalLearningSolutions.Data/Services/CourseAdminFieldsService.cs b/DigitalLearningSolutions.Data/Services/CourseAdminFieldsService.cs index 0b9d76eee4..03c7b8b6ac 100644 --- a/DigitalLearningSolutions.Data/Services/CourseAdminFieldsService.cs +++ b/DigitalLearningSolutions.Data/Services/CourseAdminFieldsService.cs @@ -11,13 +11,12 @@ public interface ICourseAdminFieldsService { - public CourseAdminFields GetCustomPromptsForCourse(int customisationId, int centreId, int categoryId); + public CourseAdminFields GetCustomPromptsForCourse(int customisationId, int centreId); public List GetCustomPromptsWithAnswersForCourse( DelegateCourseInfo delegateCourseInfo, int customisationId, - int centreId, - int categoryId = 0 + int centreId ); public void UpdateCustomPromptForCourse(int customisationId, int promptId, string? options); @@ -27,7 +26,6 @@ public List GetCustomPromptsWithAnswersForCourse( public bool AddCustomPromptToCourse( int customisationId, int centreId, - int categoryId, int promptId, string? options ); @@ -53,11 +51,10 @@ ILogger logger public CourseAdminFields GetCustomPromptsForCourse( int customisationId, - int centreId, - int categoryId = 0 + int centreId ) { - var result = courseAdminFieldsDataService.GetCourseAdminFields(customisationId, centreId, categoryId); + var result = courseAdminFieldsDataService.GetCourseAdminFields(customisationId, centreId); return new CourseAdminFields( customisationId, centreId, @@ -68,11 +65,10 @@ public CourseAdminFields GetCustomPromptsForCourse( public List GetCustomPromptsWithAnswersForCourse( DelegateCourseInfo delegateCourseInfo, int customisationId, - int centreId, - int categoryId = 0 + int centreId ) { - var result = GetCourseCustomPromptsResultForCourse(customisationId, centreId, categoryId); + var result = GetCourseCustomPromptsResultForCourse(customisationId, centreId); return PopulateCustomPromptWithAnswerListFromCourseAdminFieldsResult(result, delegateCourseInfo); } @@ -90,15 +86,13 @@ public void UpdateCustomPromptForCourse(int customisationId, int promptId, strin public bool AddCustomPromptToCourse( int customisationId, int centreId, - int categoryId, int promptId, string? options ) { var courseAdminFields = GetCustomPromptsForCourse( customisationId, - centreId, - categoryId + centreId ); var promptNumber = GetNextPromptNumber(courseAdminFields); @@ -157,12 +151,11 @@ public string GetPromptName(int customisationId, int promptNumber) private CourseAdminFieldsResult? GetCourseCustomPromptsResultForCourse( int customisationId, - int centreId, - int categoryId + int centreId ) { - var result = courseAdminFieldsDataService.GetCourseAdminFields(customisationId, centreId, categoryId); - if (result == null || categoryId != 0 && result.CourseCategoryId != categoryId) + var result = courseAdminFieldsDataService.GetCourseAdminFields(customisationId, centreId); + if (result == null) { return null; } diff --git a/DigitalLearningSolutions.Data/Services/CourseService.cs b/DigitalLearningSolutions.Data/Services/CourseService.cs index 4a37781e7b..6a0474bb11 100644 --- a/DigitalLearningSolutions.Data/Services/CourseService.cs +++ b/DigitalLearningSolutions.Data/Services/CourseService.cs @@ -10,12 +10,13 @@ public interface ICourseService public IEnumerable GetTopCourseStatistics(int centreId, int categoryId); public IEnumerable GetCentreSpecificCourseStatistics(int centreId, int categoryId); public IEnumerable GetAllCoursesForDelegate(int delegateId, int centreId); + public bool VerifyAdminUserCanAccessCourse(int customisationId, int centreId, int categoryId); } public class CourseService : ICourseService { - private readonly ICourseDataService courseDataService; private readonly ICourseAdminFieldsService courseAdminFieldsService; + private readonly ICourseDataService courseDataService; public CourseService(ICourseDataService courseDataService, ICourseAdminFieldsService courseAdminFieldsService) { @@ -52,5 +53,11 @@ public IEnumerable GetAllCoursesForDelegate(int delegateI } ); } + + public bool VerifyAdminUserCanAccessCourse(int customisationId, int centreId, int adminCategoryIdClaim) + { + var categoryIdFilter = adminCategoryIdClaim == 0 ? (int?)null : adminCategoryIdClaim; + return courseDataService.DoesCourseExistAtCentre(customisationId, centreId, categoryIdFilter); + } } } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/AdminFieldsControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/AdminFieldsControllerTests.cs index ecd224e07d..ea79b8cd4f 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/AdminFieldsControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/AdminFieldsControllerTests.cs @@ -23,12 +23,16 @@ public class AdminFieldsControllerTests A.Fake(); private readonly ICourseAdminFieldsService courseAdminFieldsService = A.Fake(); + private readonly ICourseService courseService = A.Fake(); private AdminFieldsController controller = null!; [SetUp] public void Setup() { - controller = new AdminFieldsController(courseAdminFieldsService, courseAdminFieldsDataService) + controller = new AdminFieldsController( + courseAdminFieldsService, + courseAdminFieldsDataService + ) .WithDefaultContext() .WithMockUser(true, 101) .WithMockTempData(); @@ -40,7 +44,7 @@ public void AdminFields_returns_AdminFields_page_when_appropriate_course_found() // Given var samplePrompt1 = CustomPromptsTestHelper.GetDefaultCustomPrompt(1, "System Access Granted", "Yes\r\nNo"); var customPrompts = new List { samplePrompt1 }; - A.CallTo(() => courseAdminFieldsService.GetCustomPromptsForCourse(A._, A._, A._)) + A.CallTo(() => courseAdminFieldsService.GetCustomPromptsForCourse(A._, A._)) .Returns(CustomPromptsTestHelper.GetDefaultCourseAdminFields(customPrompts)); // When @@ -54,7 +58,7 @@ public void AdminFields_returns_AdminFields_page_when_appropriate_course_found() public void PostEditAdminField_save_calls_correct_methods() { // Given - var model = new EditAdminFieldViewModel(1, 1, "Test", "Options"); + var model = new EditAdminFieldViewModel(1, "Test", "Options"); const string action = "save"; A.CallTo( @@ -66,7 +70,7 @@ public void PostEditAdminField_save_calls_correct_methods() ).DoesNothing(); // When - var result = controller.EditAdminField(model, action); + var result = controller.EditAdminField(1, model, action); // Then A.CallTo( @@ -83,7 +87,7 @@ public void PostEditAdminField_save_calls_correct_methods() public void PostEditAdminField_add_configures_new_answer() { // Given - var model = new EditAdminFieldViewModel(1, 1, "Test", "Options"); + var model = new EditAdminFieldViewModel(1, "Test", "Options"); const string action = "addPrompt"; A.CallTo( @@ -95,7 +99,7 @@ public void PostEditAdminField_add_configures_new_answer() ).DoesNothing(); // When - var result = controller.EditAdminField(model, action); + var result = controller.EditAdminField(1, model, action); // Then using (new AssertionScope()) @@ -109,11 +113,11 @@ public void PostEditAdminField_add_configures_new_answer() public void PostEditAdminField_delete_removes_configured_answer() { // Given - var model = new EditAdminFieldViewModel(1, 1, "Test", "Test\r\nAnswer"); + var model = new EditAdminFieldViewModel(1, "Test", "Test\r\nAnswer"); const string action = "delete0"; // When - var result = controller.EditAdminField(model, action); + var result = controller.EditAdminField(1, model, action); // Then using (new AssertionScope()) @@ -127,17 +131,17 @@ public void PostEditAdminField_delete_removes_configured_answer() public void PostAdminField_bulk_sets_up_temp_data_and_redirects() { // Given - var model = new EditAdminFieldViewModel(1, 1, "Test", "Options"); + var model = new EditAdminFieldViewModel(1, "Test", "Options"); const string action = "bulk"; // When - var result = controller.EditAdminField(model, action); + var result = controller.EditAdminField(1, model, action); // Then using (new AssertionScope()) { AssertEditTempDataIsExpected(model); - result.Should().BeRedirectToActionResult().WithActionName("EditAdminFieldBulk"); + result.Should().BeRedirectToActionResult().WithActionName("EditAdminFieldAnswersBulk"); } } @@ -145,29 +149,32 @@ public void PostAdminField_bulk_sets_up_temp_data_and_redirects() public void PostEditAdminField_returns_error_with_unexpected_action() { // Given - var model = new EditAdminFieldViewModel(1, 1, "Test", "Options"); + var model = new EditAdminFieldViewModel(1, "Test", "Options"); const string action = "deletetest"; // When - var result = controller.EditAdminField(model, action); + var result = controller.EditAdminField(1, model, action); // Then result.Should().BeStatusCodeResult().WithStatusCode(500); } [Test] - public void AdminFieldBulkPost_updates_temp_data_and_redirects_to_edit() + public void EditAdminFieldAnswersBulk_updates_temp_data_and_redirects_to_edit() { // Given - var inputViewModel = new BulkAdminFieldAnswersViewModel("Test\r\nAnswer", false, 1, 1); - var initialEditViewModel = new EditAdminFieldViewModel(1, 1, "Test", "Test"); - var expectedViewModel = new EditAdminFieldViewModel(1, 1, "Test", "Test\r\nAnswer"); + var inputViewModel = new BulkAdminFieldAnswersViewModel("Test\r\nAnswer"); + var initialEditViewModel = new EditAdminFieldViewModel(1, "Test", "Test"); + var expectedViewModel = new EditAdminFieldViewModel(1, "Test", "Test\r\nAnswer"); var initialTempData = new EditAdminFieldData(initialEditViewModel); controller.TempData.Set(initialTempData); + A.CallTo(() => courseService.VerifyAdminUserCanAccessCourse(A._, A._, A._)) + .Returns(true); + // When - var result = controller.EditAdminFieldBulkPost(inputViewModel); + var result = controller.EditAdminFieldAnswersBulk(1, 1, inputViewModel); // Then using (new AssertionScope()) @@ -191,7 +198,7 @@ public void AddAdminFieldNew_sets_new_temp_data() [Test] public void AddAdminField_post_updates_temp_data_and_redirects() { - var expectedPromptModel = new AddAdminFieldViewModel(1); + var expectedPromptModel = new AddAdminFieldViewModel(); var initialTempData = new AddAdminFieldData(expectedPromptModel); controller.TempData.Set(initialTempData); @@ -207,7 +214,7 @@ public void AddAdminField_post_updates_temp_data_and_redirects() public void AddAdminField_save_clears_temp_data_and_redirects_to_index() { // Given - var model = new AddAdminFieldViewModel(100, 1, "Test"); + var model = new AddAdminFieldViewModel(1, "Test"); const string action = "save"; var initialTempData = new AddAdminFieldData(model); controller.TempData.Set(initialTempData); @@ -216,7 +223,6 @@ public void AddAdminField_save_clears_temp_data_and_redirects_to_index() () => courseAdminFieldsService.AddCustomPromptToCourse( 100, 101, - 0, 1, "Test" ) @@ -237,7 +243,7 @@ public void AddAdminField_save_clears_temp_data_and_redirects_to_index() public void AddAdminField_save_redirects_successfully_without_answers_configured() { // Given - var model = new AddAdminFieldViewModel(100, 1, null); + var model = new AddAdminFieldViewModel(1, null); const string action = "save"; var initialTempData = new AddAdminFieldData(model); controller.TempData.Set(initialTempData); @@ -246,7 +252,6 @@ public void AddAdminField_save_redirects_successfully_without_answers_configured () => courseAdminFieldsService.AddCustomPromptToCourse( 100, 101, - 0, 1, null ) @@ -267,7 +272,7 @@ public void AddAdminField_save_redirects_successfully_without_answers_configured public void AddAdminField_calls_service_and_redirects_to_error_on_failure() { // Given - var model = new AddAdminFieldViewModel(100, 1, "Test"); + var model = new AddAdminFieldViewModel(1, "Test"); const string action = "save"; var initialTempData = new AddAdminFieldData(model); controller.TempData.Set(initialTempData); @@ -276,7 +281,6 @@ public void AddAdminField_calls_service_and_redirects_to_error_on_failure() () => courseAdminFieldsService.AddCustomPromptToCourse( 100, 101, - 0, 1, "Test" ) @@ -295,11 +299,11 @@ public void AddAdminField_calls_service_and_redirects_to_error_on_failure() [Test] public void AddAdminField_add_configures_new_answer_and_updates_temp_data() { - var initialViewModel = new AddAdminFieldViewModel(1, 1, "Test", "Answer"); + var initialViewModel = new AddAdminFieldViewModel(1, "Test", "Answer"); var initialTempData = new AddAdminFieldData(initialViewModel); controller.TempData.Set(initialTempData); - var expectedViewModel = new AddAdminFieldViewModel(1, 1, "Test\r\nAnswer"); + var expectedViewModel = new AddAdminFieldViewModel(1, "Test\r\nAnswer"); const string action = "addPrompt"; // When @@ -318,16 +322,15 @@ public void AddAdminField_add_configures_new_answer_and_updates_temp_data() [Test] public void AddAdminField_adds_answer_without_admin_field_selected() { - var initialViewModel = new AddAdminFieldViewModel(1, null, null, "Answer"); + var initialViewModel = new AddAdminFieldViewModel(null, null, "Answer"); var initialTempData = new AddAdminFieldData(initialViewModel); controller.TempData.Set(initialTempData); - var expectedViewModel = new AddAdminFieldViewModel(1, null, "Answer"); + var expectedViewModel = new AddAdminFieldViewModel(null, "Answer"); const string action = "addPrompt"; // When - var result = - controller.AddAdminField(1, initialViewModel, action); + controller.AddAdminField(1, initialViewModel, action); // Then using (new AssertionScope()) @@ -340,7 +343,7 @@ public void AddAdminField_adds_answer_without_admin_field_selected() public void AddAdminField_delete_removes_configured_answer() { // Given - var model = new AddAdminFieldViewModel(1, 1, "Test\r\nAnswer"); + var model = new AddAdminFieldViewModel(1, "Test\r\nAnswer"); const string action = "delete0"; var initialTempData = new AddAdminFieldData(model); controller.TempData.Set(initialTempData); @@ -359,7 +362,7 @@ public void AddAdminField_delete_removes_configured_answer() [Test] public void AddAdminField_removes_answer_without_admin_field_selected() { - var model = new AddAdminFieldViewModel(1, null, "Test\r\nAnswer"); + var model = new AddAdminFieldViewModel(null, "Test\r\nAnswer"); const string action = "delete0"; var initialTempData = new AddAdminFieldData(model); controller.TempData.Set(initialTempData); @@ -379,7 +382,7 @@ public void AddAdminField_removes_answer_without_admin_field_selected() public void AddAdminField_bulk_sets_up_temp_data_and_redirects() { // Given - var model = new AddAdminFieldViewModel(1, 1, "Options"); + var model = new AddAdminFieldViewModel(1, "Options"); const string action = "bulk"; var initialTempData = new AddAdminFieldData(model); controller.TempData.Set(initialTempData); @@ -399,7 +402,7 @@ public void AddAdminField_bulk_sets_up_temp_data_and_redirects() public void AddAdminField_bulk_redirects_without_admin_field_selected() { // Given - var model = new AddAdminFieldViewModel(1, null, "Options"); + var model = new AddAdminFieldViewModel(null, "Options"); const string action = "bulk"; var initialTempData = new AddAdminFieldData(model); controller.TempData.Set(initialTempData); @@ -419,7 +422,7 @@ public void AddAdminField_bulk_redirects_without_admin_field_selected() public void AddAdminField_returns_error_with_unexpected_action() { // Given - var model = new AddAdminFieldViewModel(1); + var model = new AddAdminFieldViewModel(); const string action = "deletetest"; var initialTempData = new AddAdminFieldData(model); controller.TempData.Set(initialTempData); @@ -432,18 +435,18 @@ public void AddAdminField_returns_error_with_unexpected_action() } [Test] - public void AddAdminFieldBulkPost_updates_temp_data_and_redirects_to_add() + public void AddAdminFieldAnswersBulk_updates_temp_data_and_redirects_to_add() { // Given - var inputViewModel = new BulkAdminFieldAnswersViewModel("Test\r\nAnswer", false, 1); - var initialAddViewModel = new AddAdminFieldViewModel(1, 1, "Test"); - var expectedViewModel = new AddAdminFieldViewModel(1, 1, "Test\r\nAnswer"); + var inputViewModel = new AddBulkAdminFieldAnswersViewModel("Test\r\nAnswer", 1); + var initialAddViewModel = new AddAdminFieldViewModel(1, "Test"); + var expectedViewModel = new AddAdminFieldViewModel(1, "Test\r\nAnswer"); var initialTempData = new AddAdminFieldData(initialAddViewModel); controller.TempData.Set(initialTempData); // When - var result = controller.AddAdminFieldAnswersBulk(inputViewModel); + var result = controller.AddAdminFieldAnswersBulk(1, inputViewModel); // Then using (new AssertionScope()) @@ -467,7 +470,7 @@ public void RemoveAdminField_removes_admin_field_with_no_user_answers() public void RemoveAdminField_returns_remove_view_if_admin_field_has_user_answers() { // Given - var removeViewModel = new RemoveAdminFieldViewModel(100, "System Access Granted", 1); + var removeViewModel = new RemoveAdminFieldViewModel("System Access Granted", 1); // When var result = controller.RemoveAdminField(100, 1, removeViewModel); @@ -480,7 +483,7 @@ public void RemoveAdminField_returns_remove_view_if_admin_field_has_user_answers public void RemoveAdminField_does_not_remove_admin_field_without_confirmation() { // Given - var removeViewModel = new RemoveAdminFieldViewModel(100, "System Access Granted", 1); + var removeViewModel = new RemoveAdminFieldViewModel("System Access Granted", 1); removeViewModel.Confirm = false; var expectedErrorMessage = "You must confirm before deleting this field"; @@ -497,7 +500,7 @@ public void RemoveAdminField_does_not_remove_admin_field_without_confirmation() public void RemoveAdminField_removes_admin_field_with_confirmation_and_redirects() { // Given - var removeViewModel = new RemoveAdminFieldViewModel(100, "System Access Granted", 1); + var removeViewModel = new RemoveAdminFieldViewModel("System Access Granted", 1); removeViewModel.Confirm = true; // When diff --git a/DigitalLearningSolutions.Web.Tests/ServiceFilter/VerifyAdminUserCanAccessCourseTests.cs b/DigitalLearningSolutions.Web.Tests/ServiceFilter/VerifyAdminUserCanAccessCourseTests.cs new file mode 100644 index 0000000000..cb567d250d --- /dev/null +++ b/DigitalLearningSolutions.Web.Tests/ServiceFilter/VerifyAdminUserCanAccessCourseTests.cs @@ -0,0 +1,70 @@ +namespace DigitalLearningSolutions.Web.Tests.ServiceFilter +{ + using System.Collections.Generic; + using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Web.Controllers; + using DigitalLearningSolutions.Web.ServiceFilter; + using DigitalLearningSolutions.Web.Tests.ControllerHelpers; + using FakeItEasy; + using FluentAssertions.AspNetCore.Mvc; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Abstractions; + using Microsoft.AspNetCore.Mvc.Filters; + using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.Configuration; + using NUnit.Framework; + + public class VerifyAdminUserCanAccessCourseTests + { + private readonly ICourseService courseService = A.Fake(); + private ActionExecutingContext context = null!; + + [SetUp] + public void Setup() + { + var homeController = new HomeController(A.Fake()).WithDefaultContext().WithMockTempData() + .WithMockUser(true, 101); + context = new ActionExecutingContext( + new ActionContext( + new DefaultHttpContext(), + new RouteData(new RouteValueDictionary()), + new ActionDescriptor() + ), + new List(), + new Dictionary(), + homeController + ); + } + + [Test] + public void Returns_NotFound_if_service_returns_false() + { + // Given + context.RouteData.Values["customisationId"] = 2; + A.CallTo(() => courseService.VerifyAdminUserCanAccessCourse(A._, A._, A._)) + .Returns(false); + + // When + new VerifyAdminUserCanAccessCourse(courseService).OnActionExecuting(context); + + // Then + context.Result.Should().BeNotFoundResult(); + } + + [Test] + public void Does_not_return_NotFound_if_service_returns_true() + { + // Given + context.RouteData.Values["customisationId"] = 24286; + A.CallTo(() => courseService.VerifyAdminUserCanAccessCourse(A._, A._, A._)) + .Returns(true); + + // When + new VerifyAdminUserCanAccessCourse(courseService).OnActionExecuting(context); + + // Then + context.Result.Should().BeNull(); + } + } +} diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/AdminFieldsController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/AdminFieldsController.cs index c5de15c3b5..405edf58db 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/AdminFieldsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/AdminFieldsController.cs @@ -39,15 +39,15 @@ ICourseAdminFieldsDataService courseAdminFieldsDataService } [HttpGet] - [Route("{customisationId}/AdminFields")] + [Route("{customisationId:int}/AdminFields")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] public IActionResult Index(int customisationId) { var centreId = User.GetCentreId(); - var categoryId = User.GetAdminCategoryId()!; + var courseAdminFields = courseAdminFieldsService.GetCustomPromptsForCourse( customisationId, - centreId, - categoryId.Value + centreId ); var model = new AdminFieldsViewModel(courseAdminFields.AdminFields, customisationId); @@ -55,7 +55,7 @@ public IActionResult Index(int customisationId) } [HttpGet] - [Route("{customisationId}/AdminFields/{promptNumber:int}/Edit/Start")] + [Route("{customisationId:int}/AdminFields/{promptNumber:int}/Edit/Start")] public IActionResult EditAdminFieldStart(int customisationId, int promptNumber) { TempData.Clear(); @@ -64,28 +64,33 @@ public IActionResult EditAdminFieldStart(int customisationId, int promptNumber) } [HttpGet] - [Route("{customisationId}/AdminFields/{promptNumber:int}/Edit")] + [Route("{customisationId:int}/AdminFields/{promptNumber:int}/Edit")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] public IActionResult EditAdminField(int customisationId, int promptNumber) { var centreId = User.GetCentreId(); - var categoryId = User.GetAdminCategoryId()!; + var courseAdminField = courseAdminFieldsService.GetCustomPromptsForCourse( customisationId, - centreId, - categoryId.Value + centreId ).AdminFields .Single(cp => cp.CustomPromptNumber == promptNumber); var data = TempData.Get(); - var model = data?.EditModel ?? new EditAdminFieldViewModel(courseAdminField, customisationId); + var model = data?.EditModel ?? new EditAdminFieldViewModel(courseAdminField); return View(model); } [HttpPost] - [Route("{customisationId}/AdminFields/{promptNumber:int}/Edit")] - public IActionResult EditAdminField(EditAdminFieldViewModel model, string action) + [Route("{customisationId:int}/AdminFields/{promptNumber:int}/Edit")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] + public IActionResult EditAdminField( + int customisationId, + EditAdminFieldViewModel model, + string action + ) { if (action.StartsWith(DeleteAction) && TryGetAnswerIndexFromDeleteAction(action, out var index)) { @@ -94,34 +99,35 @@ public IActionResult EditAdminField(EditAdminFieldViewModel model, string action return action switch { - SaveAction => EditAdminFieldPostSave(model), + SaveAction => EditAdminFieldPostSave(customisationId, model), AddPromptAction => AdminFieldAnswersPostAddPrompt(model), - BulkAction => EditAdminFieldBulk(model), + BulkAction => EditAdminFieldBulk(customisationId, model), _ => new StatusCodeResult(500) }; } [HttpGet] [Route("{customisationId:int}/AdminFields/{promptNumber:int}/Edit/Bulk")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] [ServiceFilter(typeof(RedirectEmptySessionData))] - public IActionResult EditAdminFieldBulk(int customisationId, int promptNumber) + public IActionResult EditAdminFieldAnswersBulk(int customisationId, int promptNumber) { var data = TempData.Peek()!; var model = new BulkAdminFieldAnswersViewModel( - data.EditModel.OptionsString, - false, - customisationId, - promptNumber + data.EditModel.OptionsString ); return View("BulkAdminFieldAnswers", model); } [HttpPost] - [Route("AdminFieldsEdit/Bulk")] + [Route("{customisationId:int}/AdminFields/{promptNumber:int}/Edit/Bulk")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] [ServiceFilter(typeof(RedirectEmptySessionData))] - public IActionResult EditAdminFieldBulkPost( + public IActionResult EditAdminFieldAnswersBulk( + int customisationId, + int promptNumber, BulkAdminFieldAnswersViewModel model ) { @@ -132,13 +138,13 @@ BulkAdminFieldAnswersViewModel model } var editData = TempData.Peek()!; - editData.EditModel!.OptionsString = + editData.EditModel.OptionsString = NewlineSeparatedStringListHelper.RemoveEmptyOptions(model.OptionsString); TempData.Set(editData); return RedirectToAction( "EditAdminField", - new { customisationId = model.CustomisationId, promptNumber = model.PromptNumber } + new { customisationId, promptNumber } ); } @@ -148,15 +154,16 @@ public IActionResult AddAdminFieldNew(int customisationId) { TempData.Clear(); - var model = new AddAdminFieldViewModel(customisationId); + var model = new AddAdminFieldViewModel(); SetAddAdminFieldTempData(model); - return RedirectToAction("AddAdminField", new { customisationId = model.CustomisationId }); + return RedirectToAction("AddAdminField", new { customisationId }); } [HttpGet] [Route("{customisationId:int}/AdminFields/Add")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] [ServiceFilter(typeof(RedirectEmptySessionData))] public IActionResult AddAdminField(int customisationId) { @@ -170,15 +177,11 @@ public IActionResult AddAdminField(int customisationId) } [HttpPost] - [Route("{customisationId}/AdminFields/Add")] + [Route("{customisationId:int}/AdminFields/Add")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] [ServiceFilter(typeof(RedirectEmptySessionData))] public IActionResult AddAdminField(int customisationId, AddAdminFieldViewModel model, string action) { - if (customisationId != model.CustomisationId) - { - return new StatusCodeResult(500); - } - UpdateTempDataWithCoursePromptModelValues(model); if (action.StartsWith(DeleteAction) && TryGetAnswerIndexFromDeleteAction(action, out var index)) @@ -188,39 +191,40 @@ public IActionResult AddAdminField(int customisationId, AddAdminFieldViewModel m return action switch { - SaveAction => AddAdminFieldPostSave(model), + SaveAction => AddAdminFieldPostSave(customisationId, model), AddPromptAction => AdminFieldAnswersPostAddPrompt(model), - BulkAction => AddAdminFieldBulk(model), + BulkAction => AddAdminFieldBulk(customisationId, model), _ => new StatusCodeResult(500) }; } [HttpGet] - [Route("{customisationId}/AdminFields/Add/Bulk")] + [Route("{customisationId:int}/AdminFields/Add/Bulk")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] [ServiceFilter(typeof(RedirectEmptySessionData))] public IActionResult AddAdminFieldAnswersBulk(int customisationId) { var data = TempData.Peek()!; - var model = new BulkAdminFieldAnswersViewModel( - data.AddModel.OptionsString, - true, - customisationId + var model = new AddBulkAdminFieldAnswersViewModel( + data.AddModel.OptionsString ); - return View("BulkAdminFieldAnswers", model); + return View("AddBulkAdminFieldAnswers", model); } [HttpPost] - [Route("{customisationId}/AdminFields/Add/Bulk")] + [Route("{customisationId:int}/AdminFields/Add/Bulk")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] [ServiceFilter(typeof(RedirectEmptySessionData))] public IActionResult AddAdminFieldAnswersBulk( - BulkAdminFieldAnswersViewModel model + int customisationId, + AddBulkAdminFieldAnswersViewModel model ) { ValidateBulkOptionsString(model.OptionsString); if (!ModelState.IsValid) { - return View("BulkAdminFieldAnswers", model); + return View("AddBulkAdminFieldAnswers", model); } var addData = TempData.Peek()!; @@ -230,12 +234,13 @@ BulkAdminFieldAnswersViewModel model return RedirectToAction( "AddAdminField", - new { customisationId = model.CustomisationId } + new { customisationId } ); } [HttpGet] [Route("{customisationId:int}/AdminFields/{promptNumber:int}/Remove")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] public IActionResult RemoveAdminField(int customisationId, int promptNumber) { var answerCount = @@ -249,13 +254,14 @@ public IActionResult RemoveAdminField(int customisationId, int promptNumber) var promptName = courseAdminFieldsService.GetPromptName(customisationId, promptNumber); - var model = new RemoveAdminFieldViewModel(customisationId, promptName, answerCount); + var model = new RemoveAdminFieldViewModel(promptName, answerCount); return View(model); } [HttpPost] [Route("{customisationId:int}/AdminFields/{promptNumber:int}/Remove")] + [ServiceFilter(typeof(VerifyAdminUserCanAccessCourse))] public IActionResult RemoveAdminField(int customisationId, int promptNumber, RemoveAdminFieldViewModel model) { if (!model.Confirm) @@ -270,26 +276,26 @@ public IActionResult RemoveAdminField(int customisationId, int promptNumber, Rem return RemoveAdminFieldAndRedirect(customisationId, promptNumber); } - private IActionResult EditAdminFieldPostSave(EditAdminFieldViewModel model) + private IActionResult EditAdminFieldPostSave(int customisationId, EditAdminFieldViewModel model) { ModelState.ClearAllErrors(); courseAdminFieldsService.UpdateCustomPromptForCourse( - model.CustomisationId, + customisationId, model.PromptNumber, model.OptionsString ); - return RedirectToAction("Index", new { customisationId = model.CustomisationId }); + return RedirectToAction("Index", new { customisationId }); } - private IActionResult EditAdminFieldBulk(EditAdminFieldViewModel model) + private IActionResult EditAdminFieldBulk(int customisationId, EditAdminFieldViewModel model) { SetEditAdminFieldTempData(model); return RedirectToAction( - "EditAdminFieldBulk", - new { customisationId = model.CustomisationId, promptNumber = model.PromptNumber } + "EditAdminFieldAnswersBulk", + new { customisationId, promptNumber = model.PromptNumber } ); } @@ -309,7 +315,7 @@ private void SetEditAdminFieldTempData(EditAdminFieldViewModel model) TempData.Set(data); } - private IActionResult AddAdminFieldPostSave(AddAdminFieldViewModel model) + private IActionResult AddAdminFieldPostSave(int customisationId, AddAdminFieldViewModel model) { ModelState.ClearErrorsForAllFieldsExcept(nameof(AddAdminFieldViewModel.AdminFieldId)); @@ -323,27 +329,26 @@ private IActionResult AddAdminFieldPostSave(AddAdminFieldViewModel model) var categoryId = User.GetAdminCategoryId()!; if (courseAdminFieldsService.AddCustomPromptToCourse( - model.CustomisationId, + customisationId, centreId, - categoryId.Value, model.AdminFieldId!.Value, model.OptionsString )) { TempData.Clear(); - return RedirectToAction("Index", new { customisationId = model.CustomisationId }); + return RedirectToAction("Index", new { customisationId }); } return new StatusCodeResult(500); } - private IActionResult AddAdminFieldBulk(AddAdminFieldViewModel model) + private IActionResult AddAdminFieldBulk(int customisationId, AddAdminFieldViewModel model) { SetAddAdminFieldTempData(model); return RedirectToAction( "AddAdminFieldAnswersBulk", - new { customisationId = model.CustomisationId } + new { customisationId } ); } diff --git a/DigitalLearningSolutions.Web/Extensions/HtmlHelperExtensions.cs b/DigitalLearningSolutions.Web/Extensions/HtmlHelperExtensions.cs index 9792155412..2cfa3ae855 100644 --- a/DigitalLearningSolutions.Web/Extensions/HtmlHelperExtensions.cs +++ b/DigitalLearningSolutions.Web/Extensions/HtmlHelperExtensions.cs @@ -1,5 +1,9 @@ namespace DigitalLearningSolutions.Web.Extensions { + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; using Microsoft.AspNetCore.Mvc.Rendering; public static class HtmlHelperExtensions @@ -18,5 +22,16 @@ public static string IsSelected( ? selectedCssClass : string.Empty; } + + public static Dictionary GetRouteValues( + this IHtmlHelper htmlHelper + ) + { + var routeValues = htmlHelper.ViewContext.HttpContext.Request.RouteValues; + return routeValues.ToDictionary( + kvp => kvp.Key, + kvp => Convert.ToString(kvp.Value, CultureInfo.InvariantCulture) + ); + } } } diff --git a/DigitalLearningSolutions.Web/ServiceFilter/VerifyAdminUserCanAccessCourse.cs b/DigitalLearningSolutions.Web/ServiceFilter/VerifyAdminUserCanAccessCourse.cs new file mode 100644 index 0000000000..5ee27d7bff --- /dev/null +++ b/DigitalLearningSolutions.Web/ServiceFilter/VerifyAdminUserCanAccessCourse.cs @@ -0,0 +1,38 @@ +namespace DigitalLearningSolutions.Web.ServiceFilter +{ + using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Web.Helpers; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Filters; + + public class VerifyAdminUserCanAccessCourse : IActionFilter + { + private readonly ICourseService courseService; + + public VerifyAdminUserCanAccessCourse( + ICourseService courseService + ) + { + this.courseService = courseService; + } + + public void OnActionExecuted(ActionExecutedContext context) { } + + public void OnActionExecuting(ActionExecutingContext context) + { + if (!(context.Controller is Controller controller)) + { + return; + } + + var centreId = controller.User.GetCentreId(); + var categoryId = controller.User.GetAdminCategoryId()!; + var customisationId = int.Parse(context.RouteData.Values["customisationId"].ToString()!); + + if (!courseService.VerifyAdminUserCanAccessCourse(customisationId, centreId, categoryId.Value)) + { + context.Result = new NotFoundResult(); + } + } + } +} diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index 9e48b6ac08..e9e089ed02 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -246,6 +246,7 @@ private static void RegisterWebServiceFilters(IServiceCollection services) services.AddScoped>(); services.AddScoped>(); services.AddScoped>(); + services.AddScoped(); } public void Configure(IApplicationBuilder app, IMigrationRunner migrationRunner, IFeatureManager featureManager) diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AddAdminFieldViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AddAdminFieldViewModel.cs index b3945ce773..7a9bbfc43e 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AddAdminFieldViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AddAdminFieldViewModel.cs @@ -9,20 +9,12 @@ public AddAdminFieldViewModel() IncludeAnswersTableCaption = true; } - public AddAdminFieldViewModel(int customisationId) - { - CustomisationId = customisationId; - IncludeAnswersTableCaption = true; - } - public AddAdminFieldViewModel( - int customisationId, int? adminFieldId, string? options, string? answer = null ) { - CustomisationId = customisationId; AdminFieldId = adminFieldId; OptionsString = options; IncludeAnswersTableCaption = true; diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AddBulkAdminFieldAnswersViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AddBulkAdminFieldAnswersViewModel.cs new file mode 100644 index 0000000000..50e1ddaffd --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AddBulkAdminFieldAnswersViewModel.cs @@ -0,0 +1,25 @@ +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup +{ + public class AddBulkAdminFieldAnswersViewModel : BulkAdminFieldAnswersViewModel + { + public AddBulkAdminFieldAnswersViewModel() { } + + public AddBulkAdminFieldAnswersViewModel( + string? optionsString + ) + { + OptionsString = optionsString; + } + + public AddBulkAdminFieldAnswersViewModel( + string? optionsString, + int promptNumber + ) + { + OptionsString = optionsString; + PromptNumber = promptNumber; + } + + private int PromptNumber { get; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AdminFieldAnswersViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AdminFieldAnswersViewModel.cs index 7daa6cf7be..32b9ec68f8 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AdminFieldAnswersViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AdminFieldAnswersViewModel.cs @@ -9,20 +9,16 @@ public class AdminFieldAnswersViewModel public AdminFieldAnswersViewModel() { } public AdminFieldAnswersViewModel( - int customisationId, string optionsString, string? answer = null, bool includeAnswersTableCaption = false ) { - CustomisationId = customisationId; OptionsString = optionsString; Answer = answer; IncludeAnswersTableCaption = includeAnswersTableCaption; } - public int CustomisationId { get; set; } - public string? OptionsString { get; set; } public List Options => NewlineSeparatedStringListHelper.SplitNewlineSeparatedList(OptionsString); diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/BulkAdminFieldAnswersViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/BulkAdminFieldAnswersViewModel.cs index 80567ea52d..3f5120ecc2 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/BulkAdminFieldAnswersViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/BulkAdminFieldAnswersViewModel.cs @@ -5,35 +5,12 @@ public class BulkAdminFieldAnswersViewModel public BulkAdminFieldAnswersViewModel() { } public BulkAdminFieldAnswersViewModel( - string? optionsString, - bool isAddPromptJourney, - int customisationId, - int promptNumber + string? optionsString ) { OptionsString = optionsString; - IsAddPromptJourney = isAddPromptJourney; - CustomisationId = customisationId; - PromptNumber = promptNumber; - } - - public BulkAdminFieldAnswersViewModel( - string? optionsString, - bool isAddPromptJourney, - int customisationId - ) - { - OptionsString = optionsString; - IsAddPromptJourney = isAddPromptJourney; - CustomisationId = customisationId; } public string? OptionsString { get; set; } - - public bool IsAddPromptJourney { get; set; } - - public int CustomisationId { get; set; } - - public int PromptNumber { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/EditAdminFieldViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/EditAdminFieldViewModel.cs index c93a5c1a94..f9ffee776d 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/EditAdminFieldViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/EditAdminFieldViewModel.cs @@ -7,9 +7,8 @@ public class EditAdminFieldViewModel : AdminFieldAnswersViewModel { public EditAdminFieldViewModel() { } - public EditAdminFieldViewModel(CustomPrompt customPrompt, int customisationId) + public EditAdminFieldViewModel(CustomPrompt customPrompt) { - CustomisationId = customisationId; PromptNumber = customPrompt.CustomPromptNumber; Prompt = customPrompt.CustomPromptText; OptionsString = NewlineSeparatedStringListHelper.JoinNewlineSeparatedList(customPrompt.Options); @@ -17,13 +16,11 @@ public EditAdminFieldViewModel(CustomPrompt customPrompt, int customisationId) } public EditAdminFieldViewModel( - int customisationId, int customPromptNumber, string text, string? options ) { - CustomisationId = customisationId; PromptNumber = customPromptNumber; Prompt = text; OptionsString = options; diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/RemoveAdminFieldViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/RemoveAdminFieldViewModel.cs index 46560d2cac..77d1dbeb57 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/RemoveAdminFieldViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/RemoveAdminFieldViewModel.cs @@ -4,15 +4,12 @@ public class RemoveAdminFieldViewModel { public RemoveAdminFieldViewModel() { } - public RemoveAdminFieldViewModel(int customisationId, string promptName, int answerCount) + public RemoveAdminFieldViewModel(string promptName, int answerCount) { - CustomisationId = customisationId; PromptName = promptName; AnswerCount = answerCount; } - public int CustomisationId { get; set; } - public string? PromptName { get; set; } public bool Confirm { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddAdminField.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddAdminField.cshtml index 905e9cc5ae..24ba73ed8d 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddAdminField.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddAdminField.cshtml @@ -1,5 +1,6 @@ @inject IConfiguration configuration @using DigitalLearningSolutions.Web.Controllers.TrackingSystem.CourseSetup +@using DigitalLearningSolutions.Web.Extensions @using DigitalLearningSolutions.Web.ViewComponents @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup @using Microsoft.AspNetCore.Mvc.TagHelpers @@ -10,11 +11,11 @@ @{ var errorHasOccurred = !ViewData.ModelState.IsValid; - ViewData["Title"] = "Add Course Admin Field"; + ViewData["Title"] = "Add course admin field"; ViewData["Application"] = "Tracking System"; ViewData["HeaderPath"] = $"{configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; ViewData["HeaderPathName"] = "Tracking System"; - var cancelLinkData = new Dictionary { { "customisationId", Model.CustomisationId.ToString() } }; + var cancelLinkData = Html.GetRouteValues(); } @section NavMenuItems { diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddBulkAdminFieldAnswers.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddBulkAdminFieldAnswers.cshtml new file mode 100644 index 0000000000..16b6231072 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddBulkAdminFieldAnswers.cshtml @@ -0,0 +1,45 @@ +@inject IConfiguration configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup +@using Microsoft.Extensions.Configuration +@model AddBulkAdminFieldAnswersViewModel + +@{ + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Configure answers in bulk" : "Configure answers in bulk"; + ViewData["Application"] = "Tracking System"; + ViewData["HeaderPath"] = $"{configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; + ViewData["HeaderPathName"] = "Tracking System"; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} + +
+
+ @if (errorHasOccurred) { + + } + +

Configure answers in bulk

+ +
+ + + + + + + + +
+
diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/BulkAdminFieldAnswers.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/BulkAdminFieldAnswers.cshtml index 92912749c4..f537942896 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/BulkAdminFieldAnswers.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/BulkAdminFieldAnswers.cshtml @@ -1,4 +1,5 @@ @inject IConfiguration configuration +@using DigitalLearningSolutions.Web.Extensions @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup @using Microsoft.Extensions.Configuration @model BulkAdminFieldAnswersViewModel @@ -9,13 +10,7 @@ ViewData["Application"] = "Tracking System"; ViewData["HeaderPath"] = $"{configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; ViewData["HeaderPathName"] = "Tracking System"; - var editCancelLinkData = new Dictionary { - { "customisationId", Model.CustomisationId.ToString() }, - { "promptNumber", Model.PromptNumber.ToString() } - }; - var addCancelLinkData = new Dictionary { - { "customisationId", Model.CustomisationId.ToString() } - }; + var cancelLinkData = Html.GetRouteValues(); } @section NavMenuItems { @@ -30,10 +25,7 @@

Configure answers in bulk

-
- - - + - + - @if (Model.IsAddPromptJourney) { - - } else { - - } + diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/EditAdminField.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/EditAdminField.cshtml index ff25d44b59..ec59f4c40f 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/EditAdminField.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/EditAdminField.cshtml @@ -1,5 +1,6 @@ @inject IConfiguration configuration @using DigitalLearningSolutions.Web.Controllers.TrackingSystem.CourseSetup +@using DigitalLearningSolutions.Web.Extensions @using DigitalLearningSolutions.Web.ViewComponents @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup @using Microsoft.AspNetCore.Mvc.TagHelpers @@ -10,11 +11,11 @@ @{ var errorHasOccurred = !ViewData.ModelState.IsValid; - ViewData["Title"] = "Edit Course Admin Field"; + ViewData["Title"] = "Edit course admin field"; ViewData["Application"] = "Tracking System"; ViewData["HeaderPath"] = $"{configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; ViewData["HeaderPathName"] = "Tracking System"; - var cancelLinkData = new Dictionary { { "customisationId", Model.CustomisationId.ToString() } }; + var cancelLinkData = Html.GetRouteValues(); } @section NavMenuItems { diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/Index.cshtml index cfbac37c20..8bd219d610 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/Index.cshtml @@ -1,17 +1,20 @@ @inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewComponents @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup +@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.Extensions.Configuration @model AdminFieldsViewModel @{ - ViewData["Title"] = "Manage Course Admin Fields"; + ViewData["Title"] = "Manage course admin fields"; ViewData["Application"] = "Tracking System"; ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; ViewData["HeaderPathName"] = "Tracking System"; var canAddNewField = Model.CustomFields.Count < 3; - var backLinkData = new Dictionary { { "customisationId", Model.CustomisationId.ToString() } }; + var backLinkData = Html.GetRouteValues(); } @section NavMenuItems { diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/RemoveAdminField.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/RemoveAdminField.cshtml index 8209489c04..4b60bfa532 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/RemoveAdminField.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/RemoveAdminField.cshtml @@ -1,5 +1,8 @@ @inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewComponents @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup +@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.Extensions.Configuration @model RemoveAdminFieldViewModel @@ -11,7 +14,7 @@ ViewData["HeaderPathName"] = "Tracking System"; var confirmError = ViewData.ModelState[nameof(RemoveAdminFieldViewModel.Confirm)]?.Errors?.Count > 0; var confirmFormErrorClass = confirmError ? "nhsuk-form-group--error" : ""; - var cancelLinkRouteData = new Dictionary { { "customisationId", Model.CustomisationId.ToString() } }; + var cancelLinkRouteData = Html.GetRouteValues(); } @section NavMenuItems {