public async Task Update_competition_does_not_redirect_unchanged_route()
        {
            var competition = _databaseFixture.TestData.Competitions.First();

            var auditable = new Competition
            {
                CompetitionId    = competition.CompetitionId,
                CompetitionName  = competition.CompetitionName,
                CompetitionRoute = competition.CompetitionRoute
            };

            var routeGenerator = new Mock <IRouteGenerator>();

            routeGenerator.Setup(x => x.GenerateUniqueRoute(auditable.CompetitionRoute, "/competitions", auditable.CompetitionName, NoiseWords.CompetitionRoute, It.IsAny <Func <string, Task <int> > >())).Returns(Task.FromResult(competition.CompetitionRoute));
            var copier = new Mock <IStoolballEntityCopier>();

            copier.Setup(x => x.CreateAuditableCopy(competition)).Returns(auditable);
            var redirects = new Mock <IRedirectsRepository>();

            var repo = new SqlServerCompetitionRepository(
                _databaseFixture.ConnectionFactory,
                Mock.Of <IAuditRepository>(),
                Mock.Of <ILogger>(),
                Mock.Of <ISeasonRepository>(),
                routeGenerator.Object,
                redirects.Object,
                Mock.Of <IHtmlSanitizer>(),
                copier.Object,
                Mock.Of <IUrlFormatter>(),
                Mock.Of <ISocialMediaAccountFormatter>());

            var updated = await repo.UpdateCompetition(competition, Guid.NewGuid(), "Person 1").ConfigureAwait(false);

            redirects.Verify(x => x.InsertRedirect(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <IDbTransaction>()), Times.Never);
        }
        public async Task Update_competition_does_not_add_competition_version_if_name_is_unchanged()
        {
            var competition = _databaseFixture.TestData.Competitions.First(x => !x.UntilYear.HasValue);

            var auditable = new Competition
            {
                CompetitionId   = competition.CompetitionId,
                CompetitionName = competition.CompetitionName,
            };

            int?existingVersions = null;

            (Guid? competitionVersionId, string competitionName, string comparableName, DateTimeOffset? fromDate)currentVersion = (null, null, null, null);
            using (var connection = _databaseFixture.ConnectionFactory.CreateDatabaseConnection())
            {
                existingVersions = await connection.ExecuteScalarAsync <int>($"SELECT COUNT(*) FROM {Tables.CompetitionVersion} WHERE CompetitionId = @CompetitionId", auditable).ConfigureAwait(false);

                currentVersion = await connection.QuerySingleAsync <(Guid competitionVersionId, string competitionName, string comparableName, DateTimeOffset?fromDate)>(
                    $"SELECT CompetitionVersionId, CompetitionName, ComparableName, FromDate FROM {Tables.CompetitionVersion} WHERE CompetitionId = @CompetitionId AND UntilDate IS NULL", auditable).ConfigureAwait(false);
            }

            var routeGenerator = new Mock <IRouteGenerator>();

            routeGenerator.Setup(x => x.GenerateUniqueRoute(competition.CompetitionRoute, "/competitions", auditable.CompetitionName, NoiseWords.CompetitionRoute, It.IsAny <Func <string, Task <int> > >())).Returns(Task.FromResult(competition.CompetitionRoute));

            var copier = new Mock <IStoolballEntityCopier>();

            copier.Setup(x => x.CreateAuditableCopy(competition)).Returns(auditable);

            var repo = new SqlServerCompetitionRepository(
                _databaseFixture.ConnectionFactory,
                Mock.Of <IAuditRepository>(),
                Mock.Of <ILogger>(),
                Mock.Of <ISeasonRepository>(),
                routeGenerator.Object,
                Mock.Of <IRedirectsRepository>(),
                Mock.Of <IHtmlSanitizer>(),
                copier.Object,
                Mock.Of <IUrlFormatter>(),
                Mock.Of <ISocialMediaAccountFormatter>());

            var updated = await repo.UpdateCompetition(competition, Guid.NewGuid(), "Person 1").ConfigureAwait(false);

            using (var connection = _databaseFixture.ConnectionFactory.CreateDatabaseConnection())
            {
                var totalVersions = await connection.ExecuteScalarAsync <int>($"SELECT COUNT(*) FROM {Tables.CompetitionVersion} WHERE CompetitionId = @CompetitionId", auditable).ConfigureAwait(false);

                Assert.Equal(existingVersions, totalVersions);

                var versionResult = await connection.QuerySingleAsync <(Guid competitionVersionId, string competitionName, string comparableName, DateTimeOffset?fromDate)>(
                    $"SELECT CompetitionVersionId, CompetitionName, ComparableName, FromDate FROM {Tables.CompetitionVersion} WHERE CompetitionId = @CompetitionId AND UntilDate IS NULL", auditable).ConfigureAwait(false);

                Assert.Equal(currentVersion.competitionVersionId, versionResult.competitionVersionId);
                Assert.Equal(auditable.CompetitionName, versionResult.competitionName);
                Assert.Equal(auditable.ComparableName(), versionResult.comparableName);
                Assert.Equal(currentVersion.fromDate.Value, versionResult.fromDate.Value);
            }
        }
        public async Task Update_competition_throws_ArgumentNullException_if_memberName_is_empty_string()
        {
            var repo = new SqlServerCompetitionRepository(
                _databaseFixture.ConnectionFactory,
                Mock.Of <IAuditRepository>(),
                Mock.Of <ILogger>(),
                Mock.Of <ISeasonRepository>(),
                Mock.Of <IRouteGenerator>(),
                Mock.Of <IRedirectsRepository>(),
                Mock.Of <IHtmlSanitizer>(),
                Mock.Of <IStoolballEntityCopier>(),
                Mock.Of <IUrlFormatter>(),
                Mock.Of <ISocialMediaAccountFormatter>());

            await Assert.ThrowsAsync <ArgumentNullException>(async() => await repo.UpdateCompetition(new Competition(), Guid.NewGuid(), string.Empty).ConfigureAwait(false)).ConfigureAwait(false);
        }
        public async Task Update_competition_audits_and_logs()
        {
            var location  = _databaseFixture.TestData.Competitions.First();
            var auditable = new Competition
            {
                CompetitionId    = location.CompetitionId,
                CompetitionRoute = location.CompetitionRoute,
                CompetitionName  = location.CompetitionName,
            };
            var redacted = new Competition
            {
                CompetitionId    = location.CompetitionId,
                CompetitionRoute = location.CompetitionRoute,
                CompetitionName  = location.CompetitionName
            };

            var routeGenerator = new Mock <IRouteGenerator>();

            routeGenerator.Setup(x => x.GenerateUniqueRoute(location.CompetitionRoute, "/competitions", auditable.CompetitionName, NoiseWords.CompetitionRoute, It.IsAny <Func <string, Task <int> > >())).Returns(Task.FromResult(location.CompetitionRoute));
            var copier = new Mock <IStoolballEntityCopier>();

            copier.Setup(x => x.CreateAuditableCopy(location)).Returns(auditable);
            copier.Setup(x => x.CreateRedactedCopy(auditable)).Returns(redacted);
            var auditRepository = new Mock <IAuditRepository>();
            var logger          = new Mock <ILogger>();

            var repo = new SqlServerCompetitionRepository(
                _databaseFixture.ConnectionFactory,
                auditRepository.Object,
                logger.Object,
                Mock.Of <ISeasonRepository>(),
                routeGenerator.Object,
                Mock.Of <IRedirectsRepository>(),
                Mock.Of <IHtmlSanitizer>(),
                copier.Object,
                Mock.Of <IUrlFormatter>(),
                Mock.Of <ISocialMediaAccountFormatter>());
            var memberKey  = Guid.NewGuid();
            var memberName = "Person 1";

            var updated = await repo.UpdateCompetition(location, memberKey, memberName).ConfigureAwait(false);

            copier.Verify(x => x.CreateRedactedCopy(auditable), Times.Once);
            auditRepository.Verify(x => x.CreateAudit(It.IsAny <AuditRecord>(), It.IsAny <IDbTransaction>()), Times.Once);
            logger.Verify(x => x.Info(typeof(SqlServerCompetitionRepository), LoggingTemplates.Updated, redacted, memberName, memberKey, typeof(SqlServerCompetitionRepository), nameof(SqlServerCompetitionRepository.UpdateCompetition)));
        }
        public async Task Update_competition_updates_season_routes_and_inserts_redirects()
        {
            var competition = _databaseFixture.TestData.Competitions.First(x => x.Seasons.Count > 1);
            var auditable   = new Competition
            {
                CompetitionId    = competition.CompetitionId,
                CompetitionName  = competition.CompetitionName,
                CompetitionRoute = competition.CompetitionRoute
            };

            var updatedRoute   = competition.CompetitionRoute + "-updated";
            var routeGenerator = new Mock <IRouteGenerator>();

            routeGenerator.Setup(x => x.GenerateUniqueRoute(competition.CompetitionRoute, "/competitions", auditable.CompetitionName, NoiseWords.CompetitionRoute, It.IsAny <Func <string, Task <int> > >())).Returns(Task.FromResult(updatedRoute));
            var copier = new Mock <IStoolballEntityCopier>();

            copier.Setup(x => x.CreateAuditableCopy(competition)).Returns(auditable);
            var redirects = new Mock <IRedirectsRepository>();

            var repo = new SqlServerCompetitionRepository(
                _databaseFixture.ConnectionFactory,
                Mock.Of <IAuditRepository>(),
                Mock.Of <ILogger>(),
                Mock.Of <ISeasonRepository>(),
                routeGenerator.Object,
                redirects.Object,
                Mock.Of <IHtmlSanitizer>(),
                copier.Object,
                Mock.Of <IUrlFormatter>(),
                Mock.Of <ISocialMediaAccountFormatter>());

            var updated = await repo.UpdateCompetition(competition, Guid.NewGuid(), "Person 1").ConfigureAwait(false);

            using (var connection = _databaseFixture.ConnectionFactory.CreateDatabaseConnection())
            {
                var seasonRoutes = await connection.QueryAsync <string>($"SELECT SeasonRoute FROM {Tables.Season} WHERE CompetitionId = @CompetitionId", competition).ConfigureAwait(false);

                foreach (var route in seasonRoutes)
                {
                    Assert.Matches("^" + updatedRoute.Replace("/", @"\/") + @"\/[0-9]{4}(-[0-9]{2,4})?$", route);

                    redirects.Verify(x => x.InsertRedirect(competition.CompetitionRoute + route.Substring(route.LastIndexOf("/", StringComparison.OrdinalIgnoreCase)), route, null, It.IsAny <IDbTransaction>()), Times.Once);
                }
            }
        }
        public async Task Update_competition_returns_a_copy()
        {
            var competition = new Competition
            {
                CompetitionName  = "Example competition",
                CompetitionRoute = "/competitions/example-competition",
                MemberGroupKey   = Guid.NewGuid(),
                MemberGroupName  = "Test group"
            };

            var copyCompetition = new Competition
            {
                CompetitionName = competition.CompetitionName,
                MemberGroupKey  = competition.MemberGroupKey,
                MemberGroupName = competition.MemberGroupName
            };

            var routeGenerator = new Mock <IRouteGenerator>();

            routeGenerator.Setup(x => x.GenerateUniqueRoute(competition.CompetitionRoute, "/competitions", copyCompetition.CompetitionName, NoiseWords.CompetitionRoute, It.IsAny <Func <string, Task <int> > >())).Returns(Task.FromResult("/competitions/" + Guid.NewGuid()));

            var copier = new Mock <IStoolballEntityCopier>();

            copier.Setup(x => x.CreateAuditableCopy(competition)).Returns(copyCompetition);

            var repo = new SqlServerCompetitionRepository(
                _databaseFixture.ConnectionFactory,
                Mock.Of <IAuditRepository>(),
                Mock.Of <ILogger>(),
                Mock.Of <ISeasonRepository>(),
                routeGenerator.Object,
                Mock.Of <IRedirectsRepository>(),
                Mock.Of <IHtmlSanitizer>(),
                copier.Object,
                Mock.Of <IUrlFormatter>(),
                Mock.Of <ISocialMediaAccountFormatter>());

            var updated = await repo.UpdateCompetition(competition, Guid.NewGuid(), "Member name").ConfigureAwait(false);

            copier.Verify(x => x.CreateAuditableCopy(competition), Times.Once);
            Assert.Equal(copyCompetition, updated);
        }
        public async Task Update_competition_succeeds()
        {
            var competition = _databaseFixture.TestData.Competitions.First();

            var originalIntro           = string.IsNullOrEmpty(competition.Introduction) ? "Unsanitised introduction" : competition.Introduction;
            var originalPrivateContact  = string.IsNullOrEmpty(competition.PrivateContactDetails) ? "Unsanitised private details" : competition.PrivateContactDetails;
            var originalPublicContact   = string.IsNullOrEmpty(competition.PublicContactDetails) ? "Unsanitised public details" : competition.PublicContactDetails;
            var sanitisedIntro          = "<p>This is the sanitised intro</p>";
            var sanitisedPrivateContact = "<p>Sanitised private details</p>";
            var sanitisedPublicContact  = "<p>Sanitised public details</p>";
            var originalFacebook        = "facebook.com/" + Guid.NewGuid().ToString();
            var originalInstagram       = Guid.NewGuid().ToString();
            var originalYouTube         = "youtube.com/example" + Guid.NewGuid().ToString();
            var originalTwitter         = Guid.NewGuid().ToString();
            var originalWebsite         = "example.org/" + Guid.NewGuid().ToString();

            var auditable = new Competition
            {
                CompetitionId = competition.CompetitionId,

                Introduction          = originalIntro,
                PlayerType            = competition.PlayerType == PlayerType.JuniorGirls ? PlayerType.Ladies : PlayerType.JuniorGirls,
                CompetitionName       = Guid.NewGuid().ToString(),
                FromYear              = competition.FromYear.HasValue ? competition.FromYear + 1 : 1999,
                UntilYear             = competition.UntilYear.HasValue ? competition.UntilYear + 1 : 2021,
                Facebook              = originalFacebook,
                Instagram             = originalInstagram,
                YouTube               = originalYouTube,
                Twitter               = originalTwitter,
                Website               = originalWebsite,
                PrivateContactDetails = originalPrivateContact,
                PublicContactDetails  = originalPublicContact,
            };

            var updatedRoute = competition.CompetitionRoute + Guid.NewGuid();

            var routeGenerator = new Mock <IRouteGenerator>();

            routeGenerator.Setup(x => x.GenerateUniqueRoute(competition.CompetitionRoute, "/competitions", auditable.CompetitionName, NoiseWords.CompetitionRoute, It.IsAny <Func <string, Task <int> > >())).Returns(Task.FromResult(updatedRoute));

            var copier = new Mock <IStoolballEntityCopier>();

            copier.Setup(x => x.CreateAuditableCopy(competition)).Returns(auditable);

            var sanitizer = new Mock <IHtmlSanitizer>();

            sanitizer.Setup(x => x.Sanitize(auditable.PrivateContactDetails)).Returns(sanitisedPrivateContact);
            sanitizer.Setup(x => x.Sanitize(auditable.PublicContactDetails)).Returns(sanitisedPublicContact);
            sanitizer.Setup(x => x.Sanitize(auditable.Introduction)).Returns(sanitisedIntro);

            var urlFormatter = new Mock <IUrlFormatter>();

            urlFormatter.Setup(x => x.PrefixHttpsProtocol(auditable.Facebook)).Returns(new Uri("https://" + auditable.Facebook));
            urlFormatter.Setup(x => x.PrefixHttpsProtocol(auditable.YouTube)).Returns(new Uri("https://" + auditable.YouTube));
            urlFormatter.Setup(x => x.PrefixHttpsProtocol(auditable.Website)).Returns(new Uri("https://" + auditable.Website));

            var socialMediaFormatter = new Mock <ISocialMediaAccountFormatter>();

            socialMediaFormatter.Setup(x => x.PrefixAtSign(auditable.Instagram)).Returns("@" + auditable.Instagram);
            socialMediaFormatter.Setup(x => x.PrefixAtSign(auditable.Twitter)).Returns("@" + auditable.Twitter);

            var repo = new SqlServerCompetitionRepository(
                _databaseFixture.ConnectionFactory,
                Mock.Of <IAuditRepository>(),
                Mock.Of <ILogger>(),
                Mock.Of <ISeasonRepository>(),
                routeGenerator.Object,
                Mock.Of <IRedirectsRepository>(),
                sanitizer.Object,
                copier.Object,
                urlFormatter.Object,
                socialMediaFormatter.Object);

            var updated = await repo.UpdateCompetition(competition, Guid.NewGuid(), "Person 1").ConfigureAwait(false);

            using (var connection = _databaseFixture.ConnectionFactory.CreateDatabaseConnection())
            {
                var competitionResult = await connection.QuerySingleOrDefaultAsync <Competition>(
                    $@"SELECT PlayerType, Introduction, Facebook, Instagram, YouTube, Twitter, Website, 
                                PrivateContactDetails, PublicContactDetails, CompetitionRoute
                                FROM {Tables.Competition} 
                                WHERE CompetitionId = @CompetitionId",
                    new { updated.CompetitionId }).ConfigureAwait(false);

                Assert.NotNull(competitionResult);

                Assert.Equal(auditable.PlayerType, competitionResult.PlayerType);
                sanitizer.Verify(x => x.Sanitize(originalIntro));
                Assert.Equal(sanitisedIntro, competitionResult.Introduction);
                urlFormatter.Verify(x => x.PrefixHttpsProtocol(originalFacebook), Times.Once);
                Assert.Equal(auditable.Facebook, competitionResult.Facebook);
                Assert.StartsWith("https://", auditable.Facebook);
                socialMediaFormatter.Verify(x => x.PrefixAtSign(originalInstagram), Times.Once);
                Assert.Equal(auditable.Instagram, competitionResult.Instagram);
                Assert.StartsWith("@", auditable.Instagram);
                urlFormatter.Verify(x => x.PrefixHttpsProtocol(originalYouTube), Times.Once);
                Assert.Equal(auditable.YouTube, competitionResult.YouTube);
                Assert.StartsWith("https://", auditable.YouTube);
                socialMediaFormatter.Verify(x => x.PrefixAtSign(originalTwitter), Times.Once);
                Assert.Equal(auditable.Twitter, competitionResult.Twitter);
                Assert.StartsWith("@", auditable.Twitter);
                urlFormatter.Verify(x => x.PrefixHttpsProtocol(originalWebsite), Times.Once);
                Assert.Equal(auditable.Website, competitionResult.Website);
                Assert.StartsWith("https://", auditable.Website);
                sanitizer.Verify(x => x.Sanitize(originalPrivateContact));
                Assert.Equal(sanitisedPrivateContact, competitionResult.PrivateContactDetails);
                sanitizer.Verify(x => x.Sanitize(originalPublicContact));
                Assert.Equal(sanitisedPublicContact, competitionResult.PublicContactDetails);
                Assert.Equal(updatedRoute, competitionResult.CompetitionRoute);

                var versionResult = await connection.QuerySingleOrDefaultAsync <(string competitionName, string comparableName)>(
                    $"SELECT CompetitionName, ComparableName FROM {Tables.CompetitionVersion} WHERE CompetitionId = @CompetitionId", new { updated.CompetitionId }).ConfigureAwait(false);

                Assert.Equal(auditable.CompetitionName, versionResult.competitionName);
                Assert.Equal(auditable.ComparableName(), versionResult.comparableName);
            }
        }