Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions DigitalLearningSolutions.Data/DataServices/CourseDataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ int EnrolOnActivitySelfAssessment(int selfAssessmentId, int candidateId, int sup
public IEnumerable<CourseStatistics> GetDelegateCourseStatisticsAtCentre(string searchString, int centreId, int? categoryId, bool allCentreCourses, bool? hideInLearnerPortal, string isActive, string categoryName, string courseTopic, string hasAdminFields);

public IEnumerable<DelegateAssessmentStatistics> GetDelegateAssessmentStatisticsAtCentre(string searchString, int centreId, string categoryName, string isActive);
bool IsSelfEnrollmentAllowed(int customisationId);
Customisation? GetCourse(int customisationId);
}

public class CourseDataService : ICourseDataService
Expand Down Expand Up @@ -1969,5 +1971,38 @@ AND sa.[Name] LIKE '%' + @searchString + '%'
new { searchString, centreId, categoryName, isActive }, commandTimeout: 3000);
return delegateAssessmentStatistics;
}

public bool IsSelfEnrollmentAllowed(int customisationId)
{
int selfRegister = connection.QueryFirstOrDefault<int>(
@"SELECT COUNT(CustomisationID) FROM Customisations
WHERE CustomisationID = @customisationID AND SelfRegister = 1 AND Active = 1",
new { customisationId });

return selfRegister > 0;
}

public Customisation? GetCourse(int customisationId)
{
return connection.Query<Customisation>(
@"SELECT CustomisationID
,Active
,CentreID
,ApplicationID
,CustomisationName
,IsAssessed
,Password
,SelfRegister
,TutCompletionThreshold
,DiagCompletionThreshold
,DiagObjSelect
,HideInLearnerPortal
,NotificationEmails
FROM Customisations
WHERE CustomisationID = @customisationID ",
new { customisationId }).FirstOrDefault();


}
}
}
4 changes: 4 additions & 0 deletions DigitalLearningSolutions.Data/Models/Courses/Customisation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
public class Customisation
{
public Customisation() { }
public Customisation(
int centreId,
int applicationId,
Expand Down Expand Up @@ -40,5 +41,8 @@ public Customisation(
public bool DiagObjSelect { get; set; }
public bool HideInLearnerPortal { get; set; }
public string? NotificationEmails { get; set; }
public int CustomisationId { get; set; }
public bool Active { get; set; }

}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
namespace DigitalLearningSolutions.Web.Tests.Controllers.LearningMenu
{
using DigitalLearningSolutions.Data.Models.Progress;
using DigitalLearningSolutions.Web.Tests.TestHelpers;
using DigitalLearningSolutions.Web.ViewModels.LearningMenu;
using FakeItEasy;
using FluentAssertions;
using FluentAssertions.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using NUnit.Framework;
using System.Collections.Generic;

public partial class LearningMenuControllerTests
{
Expand All @@ -15,6 +17,11 @@ public void Index_should_render_view()
{
// Given
var expectedCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
var course = CourseContentHelper.CreateDefaultCourse();
A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
);
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
.Returns(expectedCourseContent);
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId)).Returns(10);
Expand All @@ -38,7 +45,13 @@ public void Index_should_redirect_to_section_page_if_one_section_in_course()
var section = CourseContentHelper.CreateDefaultCourseSection(id: sectionId);
var expectedCourseContent = CourseContentHelper.CreateDefaultCourseContent(customisationId);
expectedCourseContent.Sections.Add(section);
var course = CourseContentHelper.CreateDefaultCourse();
course.CustomisationId = customisationId;

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

var expectedCourseContent = CourseContentHelper.CreateDefaultCourseContent(customisationId);
expectedCourseContent.Sections.AddRange(new[] { section1, section2, section3 });
var course = CourseContentHelper.CreateDefaultCourse();
course.CustomisationId = customisationId;

A.CallTo(() => courseService.GetCourse(customisationId)).Returns(course);
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, customisationId)).Returns(
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
);

A.CallTo(() => courseContentService.GetCourseContent(CandidateId, customisationId))
.Returns(expectedCourseContent);
Expand All @@ -86,6 +106,12 @@ public void Index_should_not_redirect_to_section_page_if_more_than_one_section_i
public void Index_should_return_404_if_unknown_course()
{
// Given
var course = CourseContentHelper.CreateDefaultCourse();

A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
);
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId)).Returns(null);
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId))
.Returns(3);
Expand All @@ -106,6 +132,12 @@ public void Index_should_return_404_if_unable_to_enrol()
{
// Given
var defaultCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
var course = CourseContentHelper.CreateDefaultCourse();

A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
);
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
.Returns(defaultCourseContent);
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId))
Expand All @@ -127,6 +159,13 @@ public void Index_always_calls_get_course_content()
{
// Given
const int customisationId = 1;
var course = CourseContentHelper.CreateDefaultCourse();

A.CallTo(() => courseService.GetCourse(customisationId)).Returns(course);
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, customisationId)).Returns(
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
);


// When
controller.Index(1);
Expand All @@ -141,6 +180,12 @@ public void Index_valid_customisation_id_should_update_login_and_duration()
// Given
const int progressId = 13;
var defaultCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
var course = CourseContentHelper.CreateDefaultCourse();

A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
);
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
.Returns(defaultCourseContent);
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId)).Returns(progressId);
Expand Down Expand Up @@ -197,6 +242,12 @@ public void Index_valid_customisationId_should_StartOrUpdate_course_sessions()
{
// Given
var defaultCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);
var course = CourseContentHelper.CreateDefaultCourse();

A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
);
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
.Returns(defaultCourseContent);
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId)).Returns(1);
Expand Down Expand Up @@ -291,6 +342,13 @@ public void Index_not_detects_id_manipulation_self_register_true()
{
// Given
var expectedCourseContent = CourseContentHelper.CreateDefaultCourseContent(CustomisationId);

var course = CourseContentHelper.CreateDefaultCourse();

A.CallTo(() => courseService.GetCourse(CustomisationId)).Returns(course);
A.CallTo(() => progressService.GetDelegateProgressForCourse(CandidateId, CustomisationId)).Returns(
new List<Progress> { new Progress { ProgressId = 1, Completed = null, RemovedDate = null } }
);
A.CallTo(() => courseContentService.GetCourseContent(CandidateId, CustomisationId))
.Returns(expectedCourseContent);
A.CallTo(() => courseContentService.GetOrCreateProgressId(CandidateId, CustomisationId, CentreId)).Returns(10);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public partial class LearningMenuControllerTests
private ISessionService sessionService = null!;
private ITutorialContentService tutorialContentService = null!;
private ICourseService courseService = null!;
private IProgressService progressService = null!;
private IUserService userService = null!;

[SetUp]
public void SetUp()
Expand All @@ -48,6 +50,8 @@ public void SetUp()
postLearningAssessmentService = A.Fake<IPostLearningAssessmentService>();
courseCompletionService = A.Fake<ICourseCompletionService>();
courseService = A.Fake<ICourseService>();
progressService = A.Fake<IProgressService>();
userService = A.Fake<IUserService>();
clockUtility = A.Fake<IClockUtility>();

controller = new LearningMenuController(
Expand All @@ -61,6 +65,8 @@ public void SetUp()
sessionService,
courseCompletionService,
courseService,
progressService,
userService,
clockUtility
).WithDefaultContext()
.WithMockHttpContextSession()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
using System;
using DigitalLearningSolutions.Data.Models.CourseContent;
using DigitalLearningSolutions.Data.Models.Courses;

internal class CourseContentHelper
{
Expand Down Expand Up @@ -62,5 +63,25 @@ public static CourseSection CreateDefaultCourseSection(
postLearningAssessmentsPassed
);
}

public static Customisation CreateDefaultCourse()
{
Customisation customisation = new Customisation();
customisation.CentreId = 1;
customisation.ApplicationId = 1;
customisation.CustomisationName = "Customisation";
customisation.Password = null;
customisation.SelfRegister = true;
customisation.TutCompletionThreshold = 0;
customisation.IsAssessed = true;
customisation.DiagCompletionThreshold = 100;
customisation.DiagObjSelect = true;
customisation.HideInLearnerPortal = false;
customisation.NotificationEmails = null;
customisation.CustomisationId = 1;
customisation.Active = true;

return customisation;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class LearningMenuController : Controller
private readonly IPostLearningAssessmentService postLearningAssessmentService;
private readonly ICourseCompletionService courseCompletionService;
private readonly ICourseService courseService;
private readonly IProgressService progressService;
private readonly IUserService userService;
private readonly IClockUtility clockUtility;

public LearningMenuController(
Expand All @@ -41,6 +43,8 @@ public LearningMenuController(
ISessionService sessionService,
ICourseCompletionService courseCompletionService,
ICourseService courseService,
IProgressService progressService,
IUserService userService,
IClockUtility clockUtility
)
{
Expand All @@ -55,20 +59,43 @@ IClockUtility clockUtility
this.courseCompletionService = courseCompletionService;
this.clockUtility = clockUtility;
this.courseService = courseService;
this.progressService = progressService;
this.userService = userService;
}

[Route("/LearningMenu/{customisationId:int}")]
public IActionResult Index(int customisationId)
{
var centreId = User.GetCentreIdKnownNotNull();
var candidateId = User.GetCandidateIdKnownNotNull();

string courseValidationErrorMessage = "Redirecting to 403 as course/centre id was not available for self enrolment. " +
$"Candidate id: {candidateId}, customisation id: {customisationId}, " +
$"centre id: {centreId.ToString() ?? "null"}";

string courseErrorMessage = "Redirecting to 404 as course/centre id was not found. " +
$"Candidate id: {candidateId}, customisation id: {customisationId}, " +
$"centre id: {centreId.ToString() ?? "null"}";

var course = courseService.GetCourse(customisationId);

if (course == null)
{
logger.LogError(courseErrorMessage);
return RedirectToAction("StatusCode", "LearningSolutions", new { code = 404 });
}

if (course.CustomisationName == "ESR" || !course.Active ||
!ValidateCourse(candidateId, customisationId))
{
logger.LogError(courseValidationErrorMessage);
return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 });
}

var courseContent = courseContentService.GetCourseContent(candidateId, customisationId);
if (courseContent == null)
{
logger.LogError(
"Redirecting to 404 as course/centre id was not found. " +
$"Candidate id: {candidateId}, customisation id: {customisationId}, " +
$"centre id: {centreId.ToString() ?? "null"}");
logger.LogError(courseErrorMessage);
return RedirectToAction("StatusCode", "LearningSolutions", new { code = 404 });
}
if (!String.IsNullOrEmpty(courseContent.Password) && !courseContent.PasswordSubmitted)
Expand Down Expand Up @@ -619,7 +646,48 @@ private bool UniqueIdManipulationDetected(int candidateId, int customisationId)
{
return false;
}
return true;
}

private bool ValidateCourse(int candidateId, int customisationId)
{
var progress = progressService.GetDelegateProgressForCourse(candidateId, customisationId);

if (progress.Any())
{
if (!progress.Where(p => p.RemovedDate == null).Any())
{
if (!IsValidCourseForEnrloment(customisationId))
{
return false;
}
}
}
else
{
if (!IsValidCourseForEnrloment(customisationId))
{
return false;
}
}
return true;
}

private bool IsValidCourseForEnrloment(int customisationId)
{
if (!courseService.IsSelfEnrollmentAllowed(customisationId))
{
var centreId = User.GetCentreIdKnownNotNull();
var userId = User.GetUserIdKnownNotNull();
var userEntity = userService.GetUserById(userId);

var adminAccount = userEntity!.GetCentreAccountSet(centreId)?.AdminAccount;

if (adminAccount == null)
{
return false;
}
}
return true;
}
}
Expand Down
Loading