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 Delete_competition_succeeds()
        {
            var sanitizer = new Mock <Ganss.XSS.IHtmlSanitizer>();

            sanitizer.Setup(x => x.AllowedTags).Returns(new HashSet <string>());
            sanitizer.Setup(x => x.AllowedAttributes).Returns(new HashSet <string>());
            sanitizer.Setup(x => x.AllowedCssProperties).Returns(new HashSet <string>());
            sanitizer.Setup(x => x.AllowedAtRules).Returns(new HashSet <CssRuleType>());

            var memberKey  = Guid.NewGuid();
            var memberName = "Dee Leeter";

            var copier = new Mock <IStoolballEntityCopier>();

            copier.Setup(x => x.CreateAuditableCopy(_databaseFixture.TestData.CompetitionWithFullDetails)).Returns(new Competition
            {
                CompetitionId = _databaseFixture.TestData.CompetitionWithFullDetails.CompetitionId,
                Seasons       = _databaseFixture.TestData.CompetitionWithFullDetails.Seasons.Select(x => new Season {
                    SeasonId = x.SeasonId
                }).ToList()
            });
            foreach (var season in _databaseFixture.TestData.CompetitionWithFullDetails.Seasons)
            {
                copier.Setup(x => x.CreateAuditableCopy(season)).Returns(new Season {
                    SeasonId = season.SeasonId
                });
            }

            var seasonRepository = new SqlServerSeasonRepository(
                _databaseFixture.ConnectionFactory,
                Mock.Of <IAuditRepository>(),
                Mock.Of <ILogger>(),
                sanitizer.Object,
                Mock.Of <IRedirectsRepository>(),
                copier.Object
                );

            var competitionRepository = new SqlServerCompetitionRepository(
                _databaseFixture.ConnectionFactory,
                Mock.Of <IAuditRepository>(),
                Mock.Of <ILogger>(),
                seasonRepository,
                Mock.Of <IRouteGenerator>(),
                Mock.Of <IRedirectsRepository>(),
                Mock.Of <IHtmlSanitizer>(),
                copier.Object,
                Mock.Of <IUrlFormatter>(),
                Mock.Of <ISocialMediaAccountFormatter>());

            await competitionRepository.DeleteCompetition(_databaseFixture.TestData.CompetitionWithFullDetails, memberKey, memberName).ConfigureAwait(false);

            using (var connection = _databaseFixture.ConnectionFactory.CreateDatabaseConnection())
            {
                var result = await connection.QuerySingleOrDefaultAsync <Guid?>($"SELECT CompetitionId FROM {Tables.Competition} WHERE CompetitionId = @CompetitionId", new { _databaseFixture.TestData.CompetitionWithFullDetails.CompetitionId }).ConfigureAwait(false);

                Assert.Null(result);
            }
        }
        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 Create_competition_audits_and_logs()
        {
            var location = new Competition
            {
                CompetitionName = "New competition " + Guid.NewGuid(),
                MemberGroupKey  = Guid.NewGuid(),
                MemberGroupName = "Test group"
            };

            var auditable = new Competition
            {
                CompetitionName = "New competition " + Guid.NewGuid(),
                MemberGroupKey  = location.MemberGroupKey,
                MemberGroupName = location.MemberGroupName
            };

            var redacted = new Competition
            {
                CompetitionName = "New competition " + Guid.NewGuid(),
                MemberGroupKey  = location.MemberGroupKey,
                MemberGroupName = location.MemberGroupName
            };

            var route          = "/competitions/" + Guid.NewGuid();
            var routeGenerator = new Mock <IRouteGenerator>();

            routeGenerator.Setup(x => x.GenerateUniqueRoute("/competitions", auditable.CompetitionName, NoiseWords.CompetitionRoute, It.IsAny <Func <string, Task <int> > >())).Returns(Task.FromResult(route));
            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 created = await repo.CreateCompetition(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.Created, redacted, memberName, memberKey, typeof(SqlServerCompetitionRepository), nameof(SqlServerCompetitionRepository.CreateCompetition)));
        }
        public async Task Create_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.CreateCompetition(new Competition(), Guid.NewGuid(), string.Empty).ConfigureAwait(false)).ConfigureAwait(false);
        }
        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 Create_minimal_competition_succeeds()
        {
            var competition = new Competition
            {
                CompetitionName = "Example competition",
                PlayerType      = PlayerType.JuniorMixed,
                MemberGroupKey  = Guid.NewGuid(),
                MemberGroupName = "Test group"
            };

            var route          = "/competitions/" + Guid.NewGuid();
            var routeGenerator = new Mock <IRouteGenerator>();

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

            var copier = new Mock <IStoolballEntityCopier>();

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

            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 created = await repo.CreateCompetition(competition, Guid.NewGuid(), "Member name").ConfigureAwait(false);

            routeGenerator.Verify(x => x.GenerateUniqueRoute("/competitions", competition.CompetitionName, NoiseWords.CompetitionRoute, It.IsAny <Func <string, Task <int> > >()), Times.Once);

            using (var connection = _databaseFixture.ConnectionFactory.CreateDatabaseConnection())
            {
                var competitionResult = await connection.QuerySingleOrDefaultAsync <Competition>(
                    @$ "SELECT MemberGroupKey, MemberGroupName, CompetitionRoute, PlayerType
                        FROM {Tables.Competition} 
                        WHERE CompetitionId = @CompetitionId",
                    new
                {
                    created.CompetitionId
                }).ConfigureAwait(false);

                Assert.NotNull(competitionResult);
                Assert.Equal(competition.MemberGroupKey, competitionResult.MemberGroupKey);
                Assert.Equal(competition.MemberGroupName, competitionResult.MemberGroupName);
                Assert.Equal(competition.CompetitionRoute, competitionResult.CompetitionRoute);
                Assert.Equal(competition.PlayerType, competitionResult.PlayerType);

                var versionResult = await connection.QuerySingleAsync <(string competitionName, string comparableName, DateTimeOffset?fromDate)>(
                    $"SELECT CompetitionName, ComparableName, FromDate FROM {Tables.CompetitionVersion} WHERE CompetitionId = @CompetitionId",
                    new { created.CompetitionId }
                    ).ConfigureAwait(false);

                Assert.Equal(competition.CompetitionName, versionResult.competitionName);
                Assert.Equal(competition.ComparableName(), versionResult.comparableName);
                Assert.Equal(DateTime.UtcNow.Date, versionResult.fromDate.Value.Date);
            }
        }
        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);
            }
        }
        public async Task Create_complete_competition_succeeds()
        {
            var originalIntro           = "<p>This is the intro</p>";
            var sanitisedIntro          = "<p>This is the sanitised intro</p>";
            var originalPrivateContact  = "<p>Private contact details</p>";
            var sanitisedPrivateContact = "<p>Sanitised private details</p>";
            var originalPublicContact   = "<p>Public contact details</p>";
            var sanitisedPublicContact  = "<p>Sanitised public details</p>";

            var competition = new Competition
            {
                Introduction          = originalIntro,
                PlayerType            = PlayerType.JuniorGirls,
                CompetitionName       = "Example competition",
                FromYear              = 1999,
                UntilYear             = 2021,
                Facebook              = "facebook.com/example",
                Instagram             = "exampleinsta",
                YouTube               = "youtube.com/example",
                Twitter               = "exampletweeter",
                Website               = "example.org/",
                PrivateContactDetails = originalPrivateContact,
                PublicContactDetails  = originalPublicContact,
                MemberGroupKey        = Guid.NewGuid(),
                MemberGroupName       = "Test group"
            };

            var auditable = new Competition
            {
                Introduction          = competition.Introduction,
                PlayerType            = competition.PlayerType,
                CompetitionName       = competition.CompetitionName,
                FromYear              = competition.FromYear,
                UntilYear             = competition.UntilYear,
                Facebook              = competition.Facebook,
                Instagram             = competition.Instagram,
                YouTube               = competition.YouTube,
                Twitter               = competition.Twitter,
                Website               = competition.Website,
                PrivateContactDetails = competition.PrivateContactDetails,
                PublicContactDetails  = competition.PublicContactDetails,
                MemberGroupKey        = competition.MemberGroupKey,
                MemberGroupName       = competition.MemberGroupName
            };

            var route          = "/competitions/" + Guid.NewGuid();
            var routeGenerator = new Mock <IRouteGenerator>();

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

            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 created = await repo.CreateCompetition(competition, Guid.NewGuid(), "Member name").ConfigureAwait(false);

            using (var connection = _databaseFixture.ConnectionFactory.CreateDatabaseConnection())
            {
                var competitionResult = await connection.QuerySingleOrDefaultAsync <Competition>(
                    $@"SELECT PlayerType, Introduction, Facebook, Instagram, YouTube, Twitter, Website, 
                                PrivateContactDetails, PublicContactDetails, MemberGroupKey, MemberGroupName, CompetitionRoute,
                                YEAR(FromDate) AS FromYear, YEAR(UntilDate) AS UntilYear
                                FROM {Tables.Competition} co INNER JOIN {Tables.CompetitionVersion} cv ON co.CompetitionId = cv.CompetitionId
                                WHERE co.CompetitionId = @CompetitionId",
                    new { created.CompetitionId }).ConfigureAwait(false);

                Assert.NotNull(competitionResult);

                sanitizer.Verify(x => x.Sanitize(originalIntro));
                Assert.Equal(sanitisedIntro, competitionResult.Introduction);
                Assert.Equal(competition.FromYear, competitionResult.FromYear);
                Assert.Equal(competition.UntilYear, competitionResult.UntilYear);
                Assert.Equal(competition.PlayerType, competitionResult.PlayerType);
                urlFormatter.Verify(x => x.PrefixHttpsProtocol(competition.Facebook), Times.Once);
                Assert.Equal("https://" + competition.Facebook, competitionResult.Facebook);
                socialMediaFormatter.Verify(x => x.PrefixAtSign(competition.Instagram), Times.Once);
                Assert.Equal("@" + competition.Instagram, competitionResult.Instagram);
                urlFormatter.Verify(x => x.PrefixHttpsProtocol(competition.YouTube), Times.Once);
                Assert.Equal("https://" + competition.YouTube, competitionResult.YouTube);
                socialMediaFormatter.Verify(x => x.PrefixAtSign(competition.Twitter), Times.Once);
                Assert.Equal("@" + competition.Twitter, competitionResult.Twitter);
                urlFormatter.Verify(x => x.PrefixHttpsProtocol(competition.Website), Times.Once);
                Assert.Equal("https://" + competition.Website, competitionResult.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(competition.MemberGroupKey, competitionResult.MemberGroupKey);
                Assert.Equal(competition.MemberGroupName, competitionResult.MemberGroupName);
                Assert.Equal(route, competitionResult.CompetitionRoute);
            }
        }