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);
        }
        [InlineData(1)]  // Less than valid row
        //[InlineData(99]  // More than valid row - we don't have a way of checking this currently
        public async Task FileMatchesSchema_RowHasIncorrectColumnCount_ReturnsInvalidRows(int columnCount)
        {
            // Arrange
            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var stream = DataManagementFileHelper.CreateVenueUploadCsvStream(csvWriter =>
            {
                for (int i = 0; i < columnCount; i++)
                {
                    csvWriter.WriteField("value");
                }

                csvWriter.NextRecord();
            });

            // Act
            var(result, missingHeaders) = await fileUploadProcessor.FileMatchesSchema <CsvVenueRow>(stream);

            // Assert
            result.Should().Be(FileMatchesSchemaResult.InvalidRows);
        }
        public async Task FileMatchesSchema_HeaderHasMissingColumn_ReturnsInvalidHeaderResult()
        {
            // Arrange
            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var stream = DataManagementFileHelper.CreateCsvStream(csvWriter =>
            {
                // Miss out VENUE_NAME, POSTCODE
                csvWriter.WriteField("YOUR_VENUE_REFERENCE");
                csvWriter.WriteField("ADDRESS_LINE_1");
                csvWriter.WriteField("ADDRESS_LINE_2");
                csvWriter.WriteField("TOWN_OR_CITY");
                csvWriter.WriteField("COUNTY");
                csvWriter.WriteField("EMAIL");
                csvWriter.WriteField("PHONE");
                csvWriter.WriteField("WEBSITE");
                csvWriter.NextRecord();
            });

            // Act
            var(result, missingHeaders) = await fileUploadProcessor.FileMatchesSchema <CsvVenueRow>(stream);

            // Assert
            result.Should().Be(FileMatchesSchemaResult.InvalidHeader);
            missingHeaders.Should().BeEquivalentTo(new[]
            {
                "VENUE_NAME",
                "POSTCODE"
            });
        }
        public async Task PublishCourseUpload_StatusIsProcessedWithErrors_ReturnsUploadHasErrors()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            await TestData.CreateCourseUpload(
                provider.ProviderId,
                createdBy : user,
                UploadStatus.ProcessedWithErrors);

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            // Act
            var result = await fileUploadProcessor.PublishCourseUploadForProvider(provider.ProviderId, user);

            // Assert
            result.Status.Should().Be(PublishResultStatus.UploadHasErrors);
        }
        public async Task PublishVenueUpload_RevalidationAddsSupplementaryRows_PublishesSuccessfully()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var(venueUpload, venueUploadRows) = await TestData.CreateVenueUpload(
                provider.ProviderId,
                createdBy : user,
                UploadStatus.ProcessedSuccessfully,
                rowBuilder =>
            {
                rowBuilder.AddRow(record =>
                {
                    record.VenueName        = "Venue1";
                    record.ProviderVenueRef = "VENUE1";
                });
            });

            // Ensure we have a record in the Postcodes table for extracting lat/lng
            var postcodePosition = (Latitude : 1d, Longitude : 2d);

            foreach (var postcode in venueUploadRows.Select(r => r.Postcode).Distinct())
            {
                await TestData.CreatePostcodeInfo(postcode, postcodePosition.Latitude, postcodePosition.Longitude);
            }

            // Add a new venue for the provider and link a T-Level to it (so the venue cannot be removed by the publish).
            // Revalidation should kick in and add a supplementary row (without any validation errors).

            Clock.UtcNow += TimeSpan.FromDays(1);

            var oldVenue2 = await TestData.CreateVenue(provider.ProviderId, createdBy : user, venueName : "Venue2", providerVenueRef : "VENUE2");

            var tLevelDefinitions = await TestData.CreateInitialTLevelDefinitions();

            await TestData.CreateTLevel(
                provider.ProviderId,
                tLevelDefinitions.First().TLevelDefinitionId,
                new[] { oldVenue2.VenueId },
                createdBy : user);

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            // Act
            var result = await fileUploadProcessor.PublishVenueUploadForProvider(provider.ProviderId, user);

            // Assert
            result.Status.Should().Be(PublishResultStatus.Success);
            result.PublishedCount.Should().Be(2);
        }
        public async Task PublishCourseUpload_StatusIsProcessedWithErrorsAfterRevalidation_ReturnsUploadHasErrors()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var venue = await TestData.CreateVenue(provider.ProviderId, createdBy : user, venueName : "My Venue", providerVenueRef : "VENUE1");

            var learnAimRef = (await TestData.CreateLearningDelivery()).LearnAimRef;

            var(courseUpload, _) = await TestData.CreateCourseUpload(
                provider.ProviderId,
                createdBy : user,
                UploadStatus.ProcessedSuccessfully,
                rowBuilder =>
            {
                rowBuilder.AddRow(learnAimRef, record =>
                {
                    record.DeliveryMode         = "classroom based";
                    record.ResolvedDeliveryMode = CourseDeliveryMode.ClassroomBased;
                    record.ProviderVenueRef     = venue.ProviderVenueRef;
                    record.VenueId = venue.VenueId;
                });
            });

            // Delete the venue linked to the row in the upload, triggering revalidation
            // (which should fail since the venue has gone away)

            Clock.UtcNow += TimeSpan.FromDays(1);

            await WithSqlQueryDispatcher(dispatcher => dispatcher.ExecuteQuery(new DeleteVenue()
            {
                VenueId   = venue.VenueId,
                DeletedBy = user,
                DeletedOn = Clock.UtcNow
            }));

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            // Act
            var result = await fileUploadProcessor.PublishCourseUploadForProvider(provider.ProviderId, user);

            // Assert
            result.Status.Should().Be(PublishResultStatus.UploadHasErrors);
        }
        public async Task GetCourseUploadRowsRequiringRevalidation_MatchedVenueUpdatedSinceRowLastValidated_ReturnsRow()
        {
            // 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 learnAimRef = (await TestData.CreateLearningDelivery()).LearnAimRef;

            var venue = await TestData.CreateVenue(provider.ProviderId, createdBy : user, venueName : "My Venue", providerVenueRef : "REF");

            var(courseUpload, rows) = await TestData.CreateCourseUpload(
                provider.ProviderId,
                user,
                UploadStatus.ProcessedWithErrors,
                configureRows : rowBuilder =>
            {
                rowBuilder.AddRow(learnAimRef, record =>
                {
                    record.DeliveryMode         = "classroom based";
                    record.ResolvedDeliveryMode = CourseDeliveryMode.ClassroomBased;
                    record.ProviderVenueRef     = venue.ProviderVenueRef;
                    record.VenueId = venue.VenueId;
                });
            });

            Clock.UtcNow += TimeSpan.FromHours(1);
            await TestData.UpdateVenue(venue.VenueId, updatedBy : user);

            // Act
            var result = await WithSqlQueryDispatcher(
                dispatcher => fileUploadProcessor.GetCourseUploadRowsRequiringRevalidation(dispatcher, courseUpload));

            // Assert
            Assert.Collection(
                result,
                row => Assert.Equal(rows[0].CourseRunId, row.CourseRunId));
        }
        public async Task ProcessCourseFile_RowHasErrors_SetStatusToProcessedWithErrors()
        {
            // 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 learnAimRef = (await TestData.CreateLearningDelivery()).LearnAimRef;

            var(courseUpload, _) = await TestData.CreateCourseUpload(provider.ProviderId, user, UploadStatus.Created);

            var stream = DataManagementFileHelper.CreateCourseUploadCsvStream(
                // Empty record will always yield errors (but we will always have a valid LARS code at this point)
                new CsvCourseRow()
            {
                LearnAimRef = learnAimRef
            });

            // Act
            await fileUploadProcessor.ProcessCourseFile(courseUpload.CourseUploadId, stream);

            // Assert
            courseUpload = await WithSqlQueryDispatcher(
                dispatcher => dispatcher.ExecuteQuery(new GetCourseUpload()
            {
                CourseUploadId = courseUpload.CourseUploadId
            }));

            using (new AssertionScope())
            {
                courseUpload.UploadStatus.Should().Be(UploadStatus.ProcessedWithErrors);
                courseUpload.ProcessingCompletedOn.Should().Be(Clock.UtcNow);
                courseUpload.ProcessingStartedOn.Should().NotBeNull();
            }
        }
        public async Task ProcessVenueFile_AllRecordsValid_SetStatusToProcessedSuccessfully()
        {
            // 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(venueUpload, _) = await TestData.CreateVenueUpload(provider.ProviderId, user, UploadStatus.Created);

            var uploadRows = DataManagementFileHelper.CreateVenueUploadRows(rowCount: 3).ToArray();

            await WithSqlQueryDispatcher(dispatcher => AddPostcodeInfoForRows(dispatcher, uploadRows.ToDataUploadRowCollection()));

            var stream = DataManagementFileHelper.CreateVenueUploadCsvStream(uploadRows);

            // Act
            await fileUploadProcessor.ProcessVenueFile(venueUpload.VenueUploadId, stream);

            // Assert
            venueUpload = await WithSqlQueryDispatcher(
                dispatcher => dispatcher.ExecuteQuery(new GetVenueUpload()
            {
                VenueUploadId = venueUpload.VenueUploadId
            }));

            using (new AssertionScope())
            {
                venueUpload.UploadStatus.Should().Be(UploadStatus.ProcessedSuccessfully);
                venueUpload.LastValidated.Should().Be(Clock.UtcNow);
                venueUpload.ProcessingCompletedOn.Should().Be(Clock.UtcNow);
                venueUpload.ProcessingStartedOn.Should().NotBeNull();
            }
        }
        public async Task ValidateVenueUploadFile_RowsHasErrors_InsertsExpectedErrorCodesIntoDb(
            CsvVenueRow row,
            IEnumerable <string> expectedErrorCodes,
            IEnumerable <CsvVenueRow> additionalRows)
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var(venueUpload, _) = await TestData.CreateVenueUpload(provider.ProviderId, createdBy : user, UploadStatus.Processing);

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var uploadRows = new[] { row }.Concat(additionalRows ?? Enumerable.Empty <CsvVenueRow>()).ToDataUploadRowCollection();

            await WithSqlQueryDispatcher(async dispatcher =>
            {
                // Add a row into Postcodes table to ensure we don't have errors due to it missing
                // (ValidateVenueUploadFile_PostcodeIsNotInDb_InsertsExpectedErrorCodesIntoDb tests that scenario)
                await AddPostcodeInfoForRows(dispatcher, uploadRows);

                // Act
                await fileUploadProcessor.ValidateVenueUploadFile(
                    dispatcher,
                    venueUpload.VenueUploadId,
                    venueUpload.ProviderId,
                    uploadRows);

                var(rows, _) = await dispatcher.ExecuteQuery(new GetVenueUploadRows()
                {
                    VenueUploadId = venueUpload.VenueUploadId
                });

                rows.First().IsValid.Should().BeFalse();
                rows.First().Errors.Should().BeEquivalentTo(expectedErrorCodes);
            });
        }
        public async Task ProcessVenueFile_RowHasErrors_SetStatusToProcessedWithErrors()
        {
            // 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(venueUpload, _) = await TestData.CreateVenueUpload(provider.ProviderId, user, UploadStatus.Created);

            var stream = DataManagementFileHelper.CreateVenueUploadCsvStream(
                // Empty record will always yield errors
                new CsvVenueRow());

            // Act
            await fileUploadProcessor.ProcessVenueFile(venueUpload.VenueUploadId, stream);

            // Assert
            venueUpload = await WithSqlQueryDispatcher(
                dispatcher => dispatcher.ExecuteQuery(new GetVenueUpload()
            {
                VenueUploadId = venueUpload.VenueUploadId
            }));

            using (new AssertionScope())
            {
                venueUpload.UploadStatus.Should().Be(UploadStatus.ProcessedWithErrors);
                venueUpload.LastValidated.Should().Be(Clock.UtcNow);
                venueUpload.ProcessingCompletedOn.Should().Be(Clock.UtcNow);
                venueUpload.ProcessingStartedOn.Should().NotBeNull();
            }
        }
        public async Task LooksLikeCsv_ReturnsExpectedResult(byte[] content, bool expectedResult)
        {
            // Arrange
            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var stream = new MemoryStream(content);

            stream.Seek(0L, SeekOrigin.Begin);

            // Act
            var result = await fileUploadProcessor.LooksLikeCsv(stream);

            // Assert
            result.Should().Be(expectedResult);
        }
        public async Task ValidateVenueUploadFile_DoesNotNormalizeInvalidPostcode()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var(venueUpload, _) = await TestData.CreateVenueUpload(provider.ProviderId, createdBy : user, UploadStatus.Processing);

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var row = new CsvVenueRow()
            {
                Postcode = "xxxx",
            };

            var uploadRows = new[] { row }.ToDataUploadRowCollection();

            await WithSqlQueryDispatcher(async dispatcher =>
            {
                // Act
                await fileUploadProcessor.ValidateVenueUploadFile(
                    dispatcher,
                    venueUpload.VenueUploadId,
                    venueUpload.ProviderId,
                    uploadRows);

                var(rows, _) = await dispatcher.ExecuteQuery(new GetVenueUploadRows()
                {
                    VenueUploadId = venueUpload.VenueUploadId
                });

                rows.Count.Should().Be(1);
                rows.Last().Postcode.Should().Be(row.Postcode);
            });
        }
        public async Task ProcessCourseFile_LearnAimRefWithMissingLeadingZero_HasLeadingZeroAdded()
        {
            // 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(courseUpload, _) = await TestData.CreateCourseUpload(provider.ProviderId, user, UploadStatus.Created);

            var learningDelivery = await TestData.CreateLearningDelivery(learnAimRef : "01234567");

            var uploadRows = DataManagementFileHelper.CreateCourseUploadRows(learningDelivery.LearnAimRef.TrimStart('0'), rowCount: 1).ToArray();

            uploadRows[0].CourseName = string.Empty;

            var stream = DataManagementFileHelper.CreateCourseUploadCsvStream(uploadRows);

            // Act
            await fileUploadProcessor.ProcessCourseFile(courseUpload.CourseUploadId, stream);

            // Assert
            var rows = await WithSqlQueryDispatcher(async dispatcher =>
                                                    (await dispatcher.ExecuteQuery(new GetCourseUploadRows()
            {
                CourseUploadId = courseUpload.CourseUploadId,
                WithErrorsOnly = false
            })).Rows);

            rows.Single().LearnAimRef.Should().Be(learningDelivery.LearnAimRef);
        }
        public async Task ValidateLearnAimRefs_ReturnsExpectedResult()
        {
            // Arrange
            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            // Add missing lars
            var learnAimRef = (await TestData.CreateLearningDelivery()).LearnAimRef;
            List <CsvCourseRow> courseUploadRows = DataManagementFileHelper.CreateCourseUploadRows(learnAimRef, 1).ToList();

            courseUploadRows.AddRange(DataManagementFileHelper.CreateCourseUploadRows("", 1).ToList());
            courseUploadRows.AddRange(DataManagementFileHelper.CreateCourseUploadRows(learnAimRef, 1).ToList());
            courseUploadRows.AddRange(DataManagementFileHelper.CreateCourseUploadRows("    ", 1).ToList());

            // Add invalid and expired lars
            var expiredLearnAimRef        = (await TestData.CreateLearningDelivery(effectiveTo: DateTime.Now.AddDays(-1))).LearnAimRef;
            var expiredOperationalEndDate = (await TestData.CreateLearningDelivery(operationalEndDate: DateTime.Now.AddDays(-1))).LearnAimRef;

            courseUploadRows.AddRange(DataManagementFileHelper.CreateCourseUploadRows("ABCDEFGH", 1).ToList());
            courseUploadRows.AddRange(DataManagementFileHelper.CreateCourseUploadRows(expiredLearnAimRef, 1).ToList());
            courseUploadRows.AddRange(DataManagementFileHelper.CreateCourseUploadRows("GHFEDCBA", 1).ToList());
            courseUploadRows.AddRange(DataManagementFileHelper.CreateCourseUploadRows(expiredOperationalEndDate, 1).ToList());

            var stream = DataManagementFileHelper.CreateCourseUploadCsvStream(courseUploadRows.ToArray());

            // Act
            var(missing, invalid, expired) = await fileUploadProcessor.ValidateLearnAimRefs(stream);

            // Assert
            missing.Should().BeEquivalentTo(new[]
            {
                3,
                5
            });
            invalid.Should().BeEquivalentTo(new[]
        public async Task ValidateCourseUploadRows_RowsHaveNoLarsCode_AreNotGrouped()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var(courseUpload, _) = await TestData.CreateCourseUpload(provider.ProviderId, createdBy : user, null);

            var learnAimRef = (await TestData.CreateLearningDelivery()).LearnAimRef;

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var rows = DataManagementFileHelper.CreateCourseUploadRows(learnAimRef, rowCount: 2).ToArray();

            rows[0].LearnAimRef = string.Empty;
            rows[1].LearnAimRef = string.Empty;

            var uploadRows = rows.ToDataUploadRowCollection();

            await WithSqlQueryDispatcher(async dispatcher =>
            {
                // Act
                var(_, rows) = await fileUploadProcessor.ValidateCourseUploadRows(
                    dispatcher,
                    courseUpload.CourseUploadId,
                    provider.ProviderId,
                    uploadRows);

                // Assert
                rows.First().CourseId.Should().NotBe(rows.Last().CourseId);
            });
        }
        public async Task ValidateVenueUploadFile_PostcodeIsNotInEngland_InsertsExpectedOutsideOfEnglandValueIntoDb()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var(venueUpload, _) = await TestData.CreateVenueUpload(provider.ProviderId, createdBy : user, UploadStatus.Processing);

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var uploadRows = DataManagementFileHelper.CreateVenueUploadRows(rowCount: 1).ToDataUploadRowCollection();

            await WithSqlQueryDispatcher(async dispatcher =>
            {
                await AddPostcodeInfoForRows(dispatcher, uploadRows, inEngland: false);

                // Act
                await fileUploadProcessor.ValidateVenueUploadFile(
                    dispatcher,
                    venueUpload.VenueUploadId,
                    venueUpload.ProviderId,
                    uploadRows);

                var(rows, _) = await dispatcher.ExecuteQuery(new GetVenueUploadRows()
                {
                    VenueUploadId = venueUpload.VenueUploadId
                });

                rows.First().IsValid.Should().BeTrue();
                rows.First().OutsideOfEngland.Should().BeTrue();
            });
        }
        public async Task PublishVenueUpload_CanBePublished_UpsertsRowsArchivesUnmatchedVenuesAndSetsStatusToPublished()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var oldVenue1 = await TestData.CreateVenue(provider.ProviderId, createdBy : user, venueName : "Venue 1", providerVenueRef : "VENUE1");

            var oldVenue2 = await TestData.CreateVenue(provider.ProviderId, createdBy : user, venueName : "Venue 2");

            var(venueUpload, venueUploadRows) = await TestData.CreateVenueUpload(
                provider.ProviderId,
                createdBy : user,
                UploadStatus.ProcessedSuccessfully,
                rowBuilder =>
            {
                // Add two rows; one matching `oldVenue` and one that doesn't match an existing venue

                rowBuilder.AddRow(record =>
                {
                    record.VenueName        = "Venue 1";
                    record.ProviderVenueRef = "VENUE1";
                    record.VenueId          = oldVenue1.VenueId;
                });

                rowBuilder.AddRow(record => record.VenueName = "New venue 1");
            });

            // Ensure we have a record in the Postcodes table for extracting lat/lng
            var postcodePosition = (Latitude : 1d, Longitude : 2d);

            foreach (var postcode in venueUploadRows.Select(r => r.Postcode).Distinct())
            {
                await TestData.CreatePostcodeInfo(postcode, postcodePosition.Latitude, postcodePosition.Longitude);
            }

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            // Act
            var result = await fileUploadProcessor.PublishVenueUploadForProvider(provider.ProviderId, user);

            // Assert
            result.Status.Should().Be(PublishResultStatus.Success);
            result.PublishedCount.Should().Be(2);

            await WithSqlQueryDispatcher(async dispatcher =>
            {
                venueUpload = await dispatcher.ExecuteQuery(new GetVenueUpload()
                {
                    VenueUploadId = venueUpload.VenueUploadId
                });
                venueUpload.UploadStatus.Should().Be(UploadStatus.Published);
                venueUpload.PublishedOn.Should().Be(Clock.UtcNow);

                var providerVenues = await dispatcher.ExecuteQuery(new GetVenuesByProvider()
                {
                    ProviderId = provider.ProviderId
                });

                providerVenues.Should().BeEquivalentTo(new[]
                {
                    new Venue()
                    {
                        VenueId          = oldVenue1.VenueId,
                        VenueName        = venueUploadRows[0].VenueName,
                        ProviderId       = provider.ProviderId,
                        ProviderUkprn    = provider.Ukprn,
                        ProviderVenueRef = venueUploadRows[0].ProviderVenueRef,
                        AddressLine1     = venueUploadRows[0].AddressLine1,
                        AddressLine2     = venueUploadRows[0].AddressLine2,
                        Town             = venueUploadRows[0].Town,
                        County           = venueUploadRows[0].County,
                        Postcode         = venueUploadRows[0].Postcode,
                        Latitude         = postcodePosition.Latitude,
                        Longitude        = postcodePosition.Longitude,
                        Email            = venueUploadRows[0].Email,
                        Telephone        = venueUploadRows[0].Telephone,
                        Website          = venueUploadRows[0].Website
                    },
                    new Venue()
                    {
                        VenueId          = venueUploadRows[1].VenueId,
                        VenueName        = venueUploadRows[1].VenueName,
                        ProviderId       = provider.ProviderId,
                        ProviderUkprn    = provider.Ukprn,
                        ProviderVenueRef = venueUploadRows[1].ProviderVenueRef,
                        AddressLine1     = venueUploadRows[1].AddressLine1,
                        AddressLine2     = venueUploadRows[1].AddressLine2,
                        Town             = venueUploadRows[1].Town,
                        County           = venueUploadRows[1].County,
                        Postcode         = venueUploadRows[1].Postcode,
                        Latitude         = postcodePosition.Latitude,
                        Longitude        = postcodePosition.Longitude,
                        Email            = venueUploadRows[1].Email,
                        Telephone        = venueUploadRows[1].Telephone,
                        Website          = venueUploadRows[1].Website
                    },
                });
            });
        }
        public async Task ValidateVenueUploadFile_InsertsRowsIntoDb()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var(venueUpload, _) = await TestData.CreateVenueUpload(provider.ProviderId, createdBy : user, UploadStatus.Processing);

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var row = new CsvVenueRow()
            {
                ProviderVenueRef = "REF",
                VenueName        = "Place",
                AddressLine1     = "Line 1",
                AddressLine2     = "Line 2",
                Town             = "Town",
                County           = "County",
                Postcode         = "AB1 2DE",
                Email            = "*****@*****.**",
                Telephone        = "01234 567890",
                Website          = "provider.com/place"
            };

            var uploadRows = new[] { row }.ToDataUploadRowCollection();

            await WithSqlQueryDispatcher(async dispatcher =>
            {
                // Act
                await fileUploadProcessor.ValidateVenueUploadFile(
                    dispatcher,
                    venueUpload.VenueUploadId,
                    venueUpload.ProviderId,
                    uploadRows);

                var(rows, _) = await dispatcher.ExecuteQuery(new GetVenueUploadRows()
                {
                    VenueUploadId = venueUpload.VenueUploadId
                });

                rows.Count.Should().Be(1);
                rows.Last().Should().BeEquivalentTo(new VenueUploadRow()
                {
                    RowNumber        = 2,
                    LastUpdated      = Clock.UtcNow,
                    LastValidated    = Clock.UtcNow,
                    IsSupplementary  = false,
                    IsDeletable      = true,
                    AddressLine1     = row.AddressLine1,
                    AddressLine2     = row.AddressLine2,
                    County           = row.County,
                    Email            = row.Email,
                    Postcode         = row.Postcode,
                    ProviderVenueRef = row.ProviderVenueRef,
                    Telephone        = row.Telephone,
                    Town             = row.Town,
                    VenueName        = row.VenueName,
                    Website          = row.Website
                }, config => config.Excluding(r => r.IsValid).Excluding(r => r.Errors).Excluding(r => r.VenueId));
            });
        }
        public async Task ValidateVenueUploadFile_FileIsMissingVenuesWithLiveOfferings_AddsSupplementaryRowsToFile()
        {
            // Arrange
            var provider = await TestData.CreateProvider();

            var user = await TestData.CreateUser(providerId : provider.ProviderId);

            var(venueUpload, _) = await TestData.CreateVenueUpload(provider.ProviderId, createdBy : user, UploadStatus.Processing);

            var venue = await TestData.CreateVenue(provider.ProviderId, createdBy : user, venueName : "Venue 1");

            var tLevelDefinitions = await TestData.CreateInitialTLevelDefinitions();

            await TestData.CreateTLevel(
                provider.ProviderId,
                tLevelDefinitions.First().TLevelDefinitionId,
                locationVenueIds : new[] { venue.VenueId },
                createdBy : user);

            var fileUploadProcessor = new FileUploadProcessor(
                SqlQueryDispatcherFactory,
                Mock.Of <BlobServiceClient>(),
                Clock,
                new RegionCache(SqlQueryDispatcherFactory),
                new ExecuteImmediatelyBackgroundWorkScheduler(Fixture.ServiceScopeFactory));

            var uploadRows = new[]
            {
                new CsvVenueRow()
                {
                    VenueName = "Upload venue 1"
                }
            }.ToDataUploadRowCollection();

            await WithSqlQueryDispatcher(async dispatcher =>
            {
                // Act
                await fileUploadProcessor.ValidateVenueUploadFile(
                    dispatcher,
                    venueUpload.VenueUploadId,
                    venueUpload.ProviderId,
                    uploadRows);

                var(rows, _) = await dispatcher.ExecuteQuery(new GetVenueUploadRows()
                {
                    VenueUploadId = venueUpload.VenueUploadId
                });

                rows.Count.Should().Be(2);  // One from upload, one retained
                rows.Last().Should().BeEquivalentTo(new VenueUploadRow()
                {
                    RowNumber        = 3,
                    LastUpdated      = Clock.UtcNow,
                    LastValidated    = Clock.UtcNow,
                    IsSupplementary  = true,
                    VenueId          = venue.VenueId,
                    IsDeletable      = false,
                    AddressLine1     = venue.AddressLine1,
                    AddressLine2     = venue.AddressLine2,
                    County           = venue.County,
                    Email            = venue.Email,
                    Postcode         = venue.Postcode,
                    ProviderVenueRef = venue.ProviderVenueRef,
                    Telephone        = venue.Telephone,
                    Town             = venue.Town,
                    VenueName        = venue.VenueName,
                    Website          = venue.Website
                }, config => config.Excluding(r => r.IsValid).Excluding(r => r.Errors));
            });
        }