public async Task VenueNameSpecifiedWithProviderVenueRef_ReturnsValidationError() { // Arrange var provider = await TestData.CreateProvider(); var user = await TestData.CreateUser(); var venue = await TestData.CreateVenue( providerId : provider.ProviderId, createdBy : user, venueName : "My Venue", providerVenueRef : "VENUE1"); var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { DeliveryMode = "classroom based", ProviderVenueRef = venue.ProviderVenueRef, VenueName = venue.VenueName }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.Contains( validationResult.Errors, error => error.ErrorCode == "COURSERUN_VENUE_NAME_NOT_ALLOWED_WITH_REF"); }
public async Task FindVenue_RefProvidedAndMatches_ReturnsVenueId() { // Arrange var blobServiceClient = new Mock <BlobServiceClient>(); blobServiceClient.Setup(mock => mock.GetBlobContainerClient(It.IsAny <string>())).Returns(Mock.Of <BlobContainerClient>()); var fileUploadProcessor = new FileUploadProcessor( SqlQueryDispatcherFactory, blobServiceClient.Object, Clock, new RegionCache(SqlQueryDispatcherFactory), new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory)); var provider = await TestData.CreateProvider(); var user = await TestData.CreateUser(providerId : provider.ProviderId); var venue = await TestData.CreateVenue(providerId : provider.ProviderId, createdBy : user, venueName : "My Venue", providerVenueRef : "VENUE1"); var row = new CsvCourseRow() { DeliveryMode = "classroom based", ProviderVenueRef = "venue1" }; var rowInfo = new CourseDataUploadRowInfo(row, rowNumber: 2, courseId: Guid.NewGuid()); // Act var result = fileUploadProcessor.FindVenue(rowInfo, new[] { venue }); // Assert Assert.Equal(venue.VenueId, result?.VenueId); }
public async Task <Response> Handle(Query request, CancellationToken cancellationToken) { var providerContext = _providerContextProvider.GetProviderContext(); var courses = await _sqlQueryDispatcher.ExecuteQuery(new GetCoursesForProvider() { ProviderId = providerContext.ProviderInfo.ProviderId }); var allRegions = await _regionCache.GetAllRegions(); var rows = courses.OrderBy(x => x.LearnAimRef) .ThenBy(x => x.CourseId) .SelectMany(course => CsvCourseRow.FromModel(course, allRegions)) .ToList(); var fileName = FileNameHelper.SanitizeFileName( $"{providerContext.ProviderInfo.ProviderName}_courses_{_clock.UtcNow:yyyyMMddHHmm}.csv"); return(new Response() { FileName = fileName, Rows = rows }); }
public static ParsedCsvCourseRow FromCsvCourseRow(CsvCourseRow row, IEnumerable <Region> allRegions) { var parsedRow = row.Adapt(new ParsedCsvCourseRow()); parsedRow.ResolvedDeliveryMode = ResolveDeliveryMode(parsedRow.DeliveryMode); parsedRow.ResolvedStartDate = ResolveStartDate(parsedRow.StartDate); parsedRow.ResolvedFlexibleStartDate = ResolveFlexibleStartDate(parsedRow.FlexibleStartDate); parsedRow.ResolvedNationalDelivery = ResolveNationalDelivery(parsedRow.NationalDelivery, parsedRow.SubRegions); parsedRow.ResolvedCost = ResolveCost(parsedRow.Cost); parsedRow.ResolvedDuration = ResolveDuration(parsedRow.Duration); parsedRow.ResolvedDurationUnit = ResolveDurationUnit(parsedRow.DurationUnit); parsedRow.ResolvedStudyMode = ResolveStudyMode(parsedRow.StudyMode); parsedRow.ResolvedAttendancePattern = ResolveAttendancePattern(parsedRow.AttendancePattern); parsedRow.ResolvedSubRegions = ResolveSubRegions(parsedRow.SubRegions, allRegions); return(parsedRow); }
public static CourseDataUploadRowInfoCollection ToDataUploadRowCollection(this IEnumerable <CsvCourseRow> rows) { var rowInfos = new List <CourseDataUploadRowInfo>(); foreach (var group in CsvCourseRow.GroupRows(rows)) { var courseId = Guid.NewGuid(); foreach (var row in group) { rowInfos.Add(new CourseDataUploadRowInfo(row, rowNumber: rowInfos.Count + 2, courseId)); } } return(new CourseDataUploadRowInfoCollection(rowInfos)); }
public async Task DurationUnitMissing_ReturnsValidationError() { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { DurationUnit = string.Empty, Duration = "10" }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.Contains( validationResult.Errors, error => error.ErrorCode == "COURSERUN_DURATION_UNIT_REQUIRED"); }
public async Task CostEmptyWithEmptyCostDescription_ReturnsSingleCostValidationError() { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { Cost = string.Empty, CostDescription = string.Empty }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.Contains( validationResult.Errors, error => error.ErrorCode == "COURSERUN_COST_REQUIRED"); }
public async Task SubRegionsInvalidWithNonWorkBasedDeliveryMode_DoesNotReturnInvalidValidationError(string deliveryMode) { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { DeliveryMode = deliveryMode, SubRegions = "xxx" }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.DoesNotContain( validationResult.Errors, error => error.ErrorCode == "COURSERUN_SUBREGIONS_INVALID"); }
public async Task AttendancePatternNotEmptyWithNonClassroomBasedDeliveryMode_ReturnsValidationError(string deliveryMode) { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { AttendancePattern = "daytime", DeliveryMode = deliveryMode }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.Contains( validationResult.Errors, error => error.ErrorCode == "COURSERUN_ATTENDANCE_PATTERN_NOT_ALLOWED"); }
public async Task StudyModeNotEmptyWithNonClassroomBasedDeliveryMode_ReturnsValidationError(string deliveryMode) { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { DeliveryMode = deliveryMode, StudyMode = "full time" }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.Contains( validationResult.Errors, error => error.ErrorCode == "COURSERUN_STUDY_MODE_NOT_ALLOWED"); }
public async Task StartDateNotEmptyWithFlexibleTrue_ReturnsValidationError() { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { StartDate = "01/04/2021", FlexibleStartDate = "yes" }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.Contains( validationResult.Errors, error => error.ErrorCode == "COURSERUN_START_DATE_NOT_ALLOWED"); }
public async Task NationalDeliveryNotEmptyWithNonWorkBasedDeliveryMode_ReturnsValidationError(string nationalDelivery) { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { DeliveryMode = "classroom based", NationalDelivery = nationalDelivery }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.Contains( validationResult.Errors, error => error.ErrorCode == "COURSERUN_NATIONAL_DELIVERY_NOT_ALLOWED"); }
public async Task SubRegionsEmptyWithNational_DoesNotReturnValidationError() { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { DeliveryMode = "work based", NationalDelivery = "yes", SubRegions = "" }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.DoesNotContain( validationResult.Errors, error => error.ErrorCode == "COURSERUN_SUBREGIONS_INVALID"); }
public async Task ProviderVenueRefEmptyButVenueNameNotEmpty_DoesNotReturnValidationError() { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { DeliveryMode = "classroom based", ProviderVenueRef = string.Empty, VenueName = "venue" }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.DoesNotContain( validationResult.Errors, error => error.PropertyName == nameof(CsvCourseRow.ProviderVenueRef)); }
public async Task ProviderVenueRefAndVenueNameEmptyWithClassroomBasedDeliveryMode_ReturnsValidationError() { // Arrange var allRegions = await new RegionCache(SqlQueryDispatcherFactory).GetAllRegions(); var row = new CsvCourseRow() { DeliveryMode = "classroom based", ProviderVenueRef = string.Empty, VenueName = string.Empty }; var validator = new CourseUploadRowValidator(Clock, matchedVenueId: null); // Act var validationResult = validator.Validate(ParsedCsvCourseRow.FromCsvCourseRow(row, allRegions)); // Assert Assert.Contains( validationResult.Errors, error => error.ErrorCode == "COURSERUN_VENUE_REQUIRED"); }
public async Task ProcessCourseFile(Guid courseUploadId, Stream stream) { using (var dispatcher = _sqlQueryDispatcherFactory.CreateDispatcher(System.Data.IsolationLevel.ReadCommitted)) { var setProcessingResult = await dispatcher.ExecuteQuery(new SetCourseUploadProcessing() { CourseUploadId = courseUploadId, ProcessingStartedOn = _clock.UtcNow }); if (setProcessingResult != SetCourseUploadProcessingResult.Success) { await DeleteBlob(); return; } await dispatcher.Commit(); } // At this point `stream` should be a CSV that's already known to conform to `CsvCourseRow`'s schema. // We read all the rows upfront because validation needs to group rows into courses. // We also don't expect massive files here so reading everything into memory is ok. List <CsvCourseRow> rows; using (var streamReader = new StreamReader(stream)) using (var csvReader = CreateCsvReader(streamReader)) { rows = await csvReader.GetRecordsAsync <CsvCourseRow>().ToListAsync(); } var rowsCollection = CreateCourseDataUploadRowInfoCollection(); using (var dispatcher = _sqlQueryDispatcherFactory.CreateDispatcher(System.Data.IsolationLevel.ReadCommitted)) { // If CourseName is empty, use the LearnAimRefTitle from LARS var learnAimRefs = rowsCollection.Select(r => r.Data.LearnAimRef).Distinct(); var learningDeliveries = await dispatcher.ExecuteQuery(new GetLearningDeliveries() { LearnAimRefs = learnAimRefs }); foreach (var row in rowsCollection) { if (string.IsNullOrWhiteSpace(row.Data.CourseName)) { row.Data.CourseName = learningDeliveries[row.Data.LearnAimRef].LearnAimRefTitle; } } var venueUpload = await dispatcher.ExecuteQuery(new GetCourseUpload() { CourseUploadId = courseUploadId }); var providerId = venueUpload.ProviderId; await AcquireExclusiveCourseUploadLockForProvider(providerId, dispatcher); await ValidateCourseUploadRows(dispatcher, courseUploadId, providerId, rowsCollection); await dispatcher.Commit(); } await DeleteBlob(); Task DeleteBlob() { var blobName = $"{Constants.CoursesFolder}/{courseUploadId}.csv"; return(_blobContainerClient.DeleteBlobIfExistsAsync(blobName)); } CourseDataUploadRowInfoCollection CreateCourseDataUploadRowInfoCollection() { // N.B. It's important we maintain ordering here; RowNumber needs to match the input var grouped = CsvCourseRow.GroupRows(rows); var groupCourseIds = grouped.Select(g => (CourseId: Guid.NewGuid(), Rows: g)).ToArray(); var rowInfos = new List <CourseDataUploadRowInfo>(rows.Count); foreach (var row in rows) { var courseId = groupCourseIds.Single(g => g.Rows.Contains(row)).CourseId; row.LearnAimRef = NormalizeLearnAimRef(row.LearnAimRef); rowInfos.Add(new CourseDataUploadRowInfo(row, rowNumber: rowInfos.Count + 2, courseId)); } return(new CourseDataUploadRowInfoCollection(rowInfos)); } }