Skip to content

Commit 93ee220

Browse files
committed
TD-4445-customisation validation added for self enrolment
1 parent e15940e commit 93ee220

File tree

8 files changed

+216
-4
lines changed

8 files changed

+216
-4
lines changed

DigitalLearningSolutions.Data/DataServices/CourseDataService.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ int EnrolOnActivitySelfAssessment(int selfAssessmentId, int candidateId, int sup
137137
public IEnumerable<CourseStatistics> GetDelegateCourseStatisticsAtCentre(string searchString, int centreId, int? categoryId, bool allCentreCourses, bool? hideInLearnerPortal, string isActive, string categoryName, string courseTopic, string hasAdminFields);
138138

139139
public IEnumerable<DelegateAssessmentStatistics> GetDelegateAssessmentStatisticsAtCentre(string searchString, int centreId, string categoryName, string isActive);
140+
bool IsSelfEnrollmentAllowed(int customisationId);
141+
Customisation? GetCourse(int customisationId);
140142
}
141143

142144
public class CourseDataService : ICourseDataService
@@ -1969,5 +1971,38 @@ AND sa.[Name] LIKE '%' + @searchString + '%'
19691971
new { searchString, centreId, categoryName, isActive }, commandTimeout: 3000);
19701972
return delegateAssessmentStatistics;
19711973
}
1974+
1975+
public bool IsSelfEnrollmentAllowed(int customisationId)
1976+
{
1977+
int selfRegister = connection.QueryFirstOrDefault<int>(
1978+
@"SELECT COUNT(CustomisationID) FROM Customisations
1979+
WHERE CustomisationID = @customisationID AND SelfRegister = 1 AND Active = 1",
1980+
new { customisationId });
1981+
1982+
return selfRegister > 0;
1983+
}
1984+
1985+
public Customisation? GetCourse(int customisationId)
1986+
{
1987+
return connection.Query<Customisation>(
1988+
@"SELECT CustomisationID
1989+
,Active
1990+
,CentreID
1991+
,ApplicationID
1992+
,CustomisationName
1993+
,IsAssessed
1994+
,Password
1995+
,SelfRegister
1996+
,TutCompletionThreshold
1997+
,DiagCompletionThreshold
1998+
,DiagObjSelect
1999+
,HideInLearnerPortal
2000+
,NotificationEmails
2001+
FROM Customisations
2002+
WHERE CustomisationID = @customisationID ",
2003+
new { customisationId }).FirstOrDefault();
2004+
2005+
2006+
}
19722007
}
19732008
}

DigitalLearningSolutions.Data/Models/Courses/Customisation.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{
33
public class Customisation
44
{
5+
public Customisation() { }
56
public Customisation(
67
int centreId,
78
int applicationId,
@@ -40,5 +41,8 @@ public Customisation(
4041
public bool DiagObjSelect { get; set; }
4142
public bool HideInLearnerPortal { get; set; }
4243
public string? NotificationEmails { get; set; }
44+
public int CustomisationId { get; set; }
45+
public bool Active { get; set; }
46+
4347
}
4448
}

DigitalLearningSolutions.Web.Tests/Controllers/LearningMenu/IndexTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
namespace DigitalLearningSolutions.Web.Tests.Controllers.LearningMenu
22
{
3+
using DigitalLearningSolutions.Data.Models.Progress;
34
using DigitalLearningSolutions.Web.Tests.TestHelpers;
45
using DigitalLearningSolutions.Web.ViewModels.LearningMenu;
56
using FakeItEasy;
67
using FluentAssertions;
78
using FluentAssertions.AspNetCore.Mvc;
89
using Microsoft.AspNetCore.Http;
910
using NUnit.Framework;
11+
using System.Collections.Generic;
1012

1113
public partial class LearningMenuControllerTests
1214
{
@@ -15,6 +17,11 @@ public void Index_should_render_view()
1517
{
1618
// Given
1719
var expectedCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
20+
var course = CourseContentHelper.CreateDefaultCourse();
21+
A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
22+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
23+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
24+
);
1825
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
1926
.Returns(expectedCourseContent);
2027
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId)).Returns(10);
@@ -38,7 +45,13 @@ public void Index_should_redirect_to_section_page_if_one_section_in_course()
3845
var section = CourseContentHelper.CreateDefaultCourseSection(id: sectionId);
3946
var expectedCourseContent = CourseContentHelper.CreateDefaultCourseContent(customisationId);
4047
expectedCourseContent.Sections.Add(section);
48+
var course = CourseContentHelper.CreateDefaultCourse();
49+
course.CustomisationId = customisationId;
4150

51+
A.CallTo(() => courseService.GetCourse(customisationId)).Returns(course);
52+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, customisationId)).Returns(
53+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
54+
);
4255
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, customisationId))
4356
.Returns(expectedCourseContent);
4457
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, customisationId, CentreId)).Returns(10);
@@ -67,6 +80,13 @@ public void Index_should_not_redirect_to_section_page_if_more_than_one_section_i
6780

6881
var expectedCourseContent = CourseContentHelper.CreateDefaultCourseContent(customisationId);
6982
expectedCourseContent.Sections.AddRange(new[] { section1, section2, section3 });
83+
var course = CourseContentHelper.CreateDefaultCourse();
84+
course.CustomisationId = customisationId;
85+
86+
A.CallTo(() => courseService.GetCourse(customisationId)).Returns(course);
87+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, customisationId)).Returns(
88+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
89+
);
7090

7191
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, customisationId))
7292
.Returns(expectedCourseContent);
@@ -86,6 +106,12 @@ public void Index_should_not_redirect_to_section_page_if_more_than_one_section_i
86106
public void Index_should_return_404_if_unknown_course()
87107
{
88108
// Given
109+
var course = CourseContentHelper.CreateDefaultCourse();
110+
111+
A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
112+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
113+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
114+
);
89115
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId)).Returns(null);
90116
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId))
91117
.Returns(3);
@@ -106,6 +132,12 @@ public void Index_should_return_404_if_unable_to_enrol()
106132
{
107133
// Given
108134
var defaultCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
135+
var course = CourseContentHelper.CreateDefaultCourse();
136+
137+
A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
138+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
139+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
140+
);
109141
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
110142
.Returns(defaultCourseContent);
111143
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId))
@@ -127,6 +159,13 @@ public void Index_always_calls_get_course_content()
127159
{
128160
// Given
129161
const int customisationId = 1;
162+
var course = CourseContentHelper.CreateDefaultCourse();
163+
164+
A.CallTo(() => courseService.GetCourse(customisationId)).Returns(course);
165+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, customisationId)).Returns(
166+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
167+
);
168+
130169

131170
// When
132171
controller.Index(1);
@@ -141,6 +180,12 @@ public void Index_valid_customisation_id_should_update_login_and_duration()
141180
// Given
142181
const int progressId = 13;
143182
var defaultCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
183+
var course = CourseContentHelper.CreateDefaultCourse();
184+
185+
A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
186+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
187+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
188+
);
144189
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
145190
.Returns(defaultCourseContent);
146191
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId)).Returns(progressId);
@@ -197,6 +242,12 @@ public void Index_valid_customisationId_should_StartOrUpdate_course_sessions()
197242
{
198243
// Given
199244
var defaultCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
245+
var course = CourseContentHelper.CreateDefaultCourse();
246+
247+
A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
248+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
249+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
250+
);
200251
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
201252
.Returns(defaultCourseContent);
202253
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId)).Returns(1);
@@ -291,6 +342,13 @@ public void Index_not_detects_id_manipulation_self_register_true()
291342
{
292343
// Given
293344
var expectedCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
345+
346+
var course = CourseContentHelper.CreateDefaultCourse();
347+
348+
A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
349+
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
350+
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
351+
);
294352
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
295353
.Returns(expectedCourseContent);
296354
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId)).Returns(10);

DigitalLearningSolutions.Web.Tests/Controllers/LearningMenu/LearningMenuControllerTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public partial class LearningMenuControllerTests
3333
private ISessionService sessionService = null!;
3434
private ITutorialContentService tutorialContentService = null!;
3535
private ICourseService courseService = null!;
36+
private IProgressService progressService = null!;
37+
private IUserService userService = null!;
3638

3739
[SetUp]
3840
public void SetUp()
@@ -48,6 +50,8 @@ public void SetUp()
4850
postLearningAssessmentService = A.Fake<IPostLearningAssessmentService>();
4951
courseCompletionService = A.Fake<ICourseCompletionService>();
5052
courseService = A.Fake<ICourseService>();
53+
progressService = A.Fake<IProgressService>();
54+
userService = A.Fake<IUserService>();
5155
clockUtility = A.Fake<IClockUtility>();
5256

5357
controller = new LearningMenuController(
@@ -61,6 +65,8 @@ public void SetUp()
6165
sessionService,
6266
courseCompletionService,
6367
courseService,
68+
progressService,
69+
userService,
6470
clockUtility
6571
).WithDefaultContext()
6672
.WithMockHttpContextSession()

DigitalLearningSolutions.Web.Tests/TestHelpers/CourseContentHelper.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{
33
using System;
44
using DigitalLearningSolutions.Data.Models.CourseContent;
5+
using DigitalLearningSolutions.Data.Models.Courses;
56

67
internal class CourseContentHelper
78
{
@@ -62,5 +63,25 @@ public static CourseSection CreateDefaultCourseSection(
6263
postLearningAssessmentsPassed
6364
);
6465
}
66+
67+
public static Customisation CreateDefaultCourse()
68+
{
69+
Customisation customisation = new Customisation();
70+
customisation.CentreId = 1;
71+
customisation.ApplicationId = 1;
72+
customisation.CustomisationName = "Customisation";
73+
customisation.Password = null;
74+
customisation.SelfRegister = true;
75+
customisation.TutCompletionThreshold = 0;
76+
customisation.IsAssessed = true;
77+
customisation.DiagCompletionThreshold = 100;
78+
customisation.DiagObjSelect = true;
79+
customisation.HideInLearnerPortal = false;
80+
customisation.NotificationEmails = null;
81+
customisation.CustomisationId = 1;
82+
customisation.Active = true;
83+
84+
return customisation;
85+
}
6586
}
6687
}

DigitalLearningSolutions.Web/Controllers/LearningMenuController/LearningMenuController.cs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class LearningMenuController : Controller
2828
private readonly IPostLearningAssessmentService postLearningAssessmentService;
2929
private readonly ICourseCompletionService courseCompletionService;
3030
private readonly ICourseService courseService;
31+
private readonly IProgressService progressService;
32+
private readonly IUserService userService;
3133
private readonly IClockUtility clockUtility;
3234

3335
public LearningMenuController(
@@ -41,6 +43,8 @@ public LearningMenuController(
4143
ISessionService sessionService,
4244
ICourseCompletionService courseCompletionService,
4345
ICourseService courseService,
46+
IProgressService progressService,
47+
IUserService userService,
4448
IClockUtility clockUtility
4549
)
4650
{
@@ -55,20 +59,43 @@ IClockUtility clockUtility
5559
this.courseCompletionService = courseCompletionService;
5660
this.clockUtility = clockUtility;
5761
this.courseService = courseService;
62+
this.progressService = progressService;
63+
this.userService = userService;
5864
}
5965

6066
[Route("/LearningMenu/{customisationId:int}")]
6167
public IActionResult Index(int customisationId)
6268
{
6369
var centreId = User.GetCentreIdKnownNotNull();
6470
var candidateId = User.GetCandidateIdKnownNotNull();
71+
72+
string courseValidationErrorMessage = "Redirecting to 403 as course/centre id was not available for self enrolment. " +
73+
$"Candidate id: {candidateId}, customisation id: {customisationId}, " +
74+
$"centre id: {centreId.ToString() ?? "null"}";
75+
76+
string courseErrorMessage = "Redirecting to 404 as course/centre id was not found. " +
77+
$"Candidate id: {candidateId}, customisation id: {customisationId}, " +
78+
$"centre id: {centreId.ToString() ?? "null"}";
79+
80+
var course = courseService.GetCourse(customisationId);
81+
82+
if (course == null)
83+
{
84+
logger.LogError(courseErrorMessage);
85+
return RedirectToAction("StatusCode", "LearningSolutions", new { code = 404 });
86+
}
87+
88+
if (course.CustomisationName == "ESR" || !course.Active ||
89+
!ValidateCourse(candidateId, customisationId))
90+
{
91+
logger.LogError(courseValidationErrorMessage);
92+
return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 });
93+
}
94+
6595
var courseContent = courseContentService.GetCourseContent(candidateId, customisationId);
6696
if (courseContent == null)
6797
{
68-
logger.LogError(
69-
"Redirecting to 404 as course/centre id was not found. " +
70-
$"Candidate id: {candidateId}, customisation id: {customisationId}, " +
71-
$"centre id: {centreId.ToString() ?? "null"}");
98+
logger.LogError(courseErrorMessage);
7299
return RedirectToAction("StatusCode", "LearningSolutions", new { code = 404 });
73100
}
74101
if (!String.IsNullOrEmpty(courseContent.Password) && !courseContent.PasswordSubmitted)
@@ -619,7 +646,48 @@ private bool UniqueIdManipulationDetected(int candidateId, int customisationId)
619646
{
620647
return false;
621648
}
649+
return true;
650+
}
651+
652+
private bool ValidateCourse(int candidateId, int customisationId)
653+
{
654+
var progress = progressService.GetDelegateProgressForCourse(candidateId, customisationId);
655+
656+
if (progress.Any())
657+
{
658+
if (!progress.Where(p => p.RemovedDate == null).Any())
659+
{
660+
if (!IsValidCourseForEnrloment(customisationId))
661+
{
662+
return false;
663+
}
664+
}
665+
}
666+
else
667+
{
668+
if (!IsValidCourseForEnrloment(customisationId))
669+
{
670+
return false;
671+
}
672+
}
673+
return true;
674+
}
675+
676+
private bool IsValidCourseForEnrloment(int customisationId)
677+
{
678+
if (!courseService.IsSelfEnrollmentAllowed(customisationId))
679+
{
680+
var centreId = User.GetCentreIdKnownNotNull();
681+
var userId = User.GetUserIdKnownNotNull();
682+
var userEntity = userService.GetUserById(userId);
683+
684+
var adminAccount = userEntity!.GetCentreAccountSet(centreId)?.AdminAccount;
622685

686+
if (adminAccount == null)
687+
{
688+
return false;
689+
}
690+
}
623691
return true;
624692
}
625693
}

0 commit comments

Comments
 (0)