public async Task Alias_IndexPatchOldSameAsNew_Conflict()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias
                {
                    EmailAddress = "*****@*****.**",
                    Being        = new Being
                    {
                        SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1")
                    }
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody
                {
                    oldPassword = "******",
                    password    = "******"
                };
                IActionResult result = await patient.IndexPatch("*****@*****.**", body);

                result.Should().BeOfType <StatusCodeResult>()
                .Which.StatusCode.Should().Be(409, $"'{nameof(body.password)}' must differ from '{nameof(body.oldPassword)}'");
            }
        }
        public async Task Alias_IndexPatchMatchingOld_NoContentWithChangedPassword()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                Being being = new Being
                {
                    SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1")
                };
                ef.Aliases.Add(new Alias
                {
                    EmailAddress = "*****@*****.**",
                    Being        = being
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody
                {
                    oldPassword = "******",
                    password    = "******"
                };
                IActionResult result = await patient.IndexPatch("*****@*****.**", body);

                result.Should().BeOfType <NoContentResult>($"'{nameof(body.oldPassword)}' matched");
                Sha512Util.TestPassword("p@ssword1", being.SaltedHashedPassword).Should().BeTrue("the password should have been changed");
            }
        }
        public async Task Alias_IndexPatchExpiredReset_NotAuthorized()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias
                {
                    EmailAddress = "*****@*****.**",
                    Being        = new Being
                    {
                        PasswordResetToken           = "abracadabra",
                        PasswordResetTokenValidUntil = now.UtcNow,
                        SaltedHashedPassword         = Sha512Util.SaltAndHashNewPassword("password1")
                    }
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody
                {
                    resetToken = "abracadabra",
                    password   = "******"
                };
                IActionResult result = await patient.IndexPatch("*****@*****.**", body);

                result.Should().BeOfType <UnauthorizedResult>($"the reset token is expired");
            }
        }
        public async Task BeingAndResetToken_ResetPost_JsonNewResetToken()
        {
            string old_tok = "Prev";

            using (IdentityWsDbContext ef = CreateEf()) {
                Being being = new Being
                {
                    PasswordResetToken           = old_tok,
                    PasswordResetTokenValidUntil = now.UtcNow.AddMinutes(2)
                };
                ef.Aliases.Add(new Alias
                {
                    EmailAddress = "*****@*****.**",
                    Being        = being
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.ResetPost("*****@*****.**");

                result.Should().BeOfType <JsonResult>()
                .Which.Value.Should().BeOfType <Dictionary <string, string> >()
                .Which["resetToken"].Should().NotBe(old_tok, "a new token should have been generated");
                being.PasswordResetTokenValidUntil.Should().Be(now.UtcNow.AddHours(1), "the validity period should also reset");
            }
        }
        public async Task Alias_IndexPatchMismatchingOld_NotAuthorized()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias
                {
                    EmailAddress = "*****@*****.**",
                    Being        = new Being
                    {
                        SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1")
                    }
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody
                {
                    oldPassword = "******",
                    password    = "******"
                };
                IActionResult result = await patient.IndexPatch("*****@*****.**", body);

                result.Should().BeOfType <UnauthorizedResult>($"'{nameof(body.oldPassword)}' does not match");
            }
        }
        public async Task OneAlias_IndexDelete_Forbidden()
        {
            using (IdentityWsDbContext ef = CreateEf())
            {
                ef.Beings.Add(new Being
                {
                    Aliases = new HashSet <Alias>
                    {
                        new Alias
                        {
                            EmailAddress = "*****@*****.**"
                        }
                    }
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.IndexDelete("*****@*****.**");

                result.Should().BeOfType <StatusCodeResult>()
                .Which.StatusCode.Should().Be(StatusCodes.Status403Forbidden, "a being must not be without an alias");
                (await ef.Aliases.CountAsync()).Should().Be(1, "the single alias was not deleted");
            }
        }
        public async Task NoBeing_ResetPost_NotFound()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.ResetPost("*****@*****.**");

                result.Should().BeOfType <NotFoundResult>("the being does not exist");
            }
        }
        public async Task NoAlias_IndexGet_NotFound()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.Index("*****@*****.**");

                result.Should().BeOfType <NotFoundResult>("the alias does not exist in the database");
            }
        }
        public async Task NoAlias_Confirm_NotFound()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.ConfirmPost("*****@*****.**", new AliasesController.ConfirmPostRequestBody
                {
                    confirmToken = "abc"
                });

                result.Should().BeOfType <NotFoundResult>("the alias does not exist");
            }
        }
        public async Task NoAlias_IndexPatch_NotFound()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody
                {
                    password = "******"
                };
                IActionResult result = await patient.IndexPatch("*****@*****.**", body);

                result.Should().BeOfType <NotFoundResult>("the alias does not exist in the database");
            }
        }
        public async Task Alias_IndexPostWithNeitherPasswordNorEmail_BadRequest()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias {
                    EmailAddress = "*****@*****.**"
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPostRequestBody body = new AliasesController.IndexPostRequestBody();
                IActionResult result = await patient.IndexPost("*****@*****.**", body);

                result.Should().BeOfType <BadRequestResult>($"exactly one of '{nameof(body.otherEmailAddress)}' or '{nameof(body.password)} is required'");
            }
        }
        public async Task UnconfirmedAlias_Index_ConfirmationToken()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias {
                    EmailAddress = "*****@*****.**"
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.Index("*****@*****.**");

                result.Should().BeOfType <JsonResult>()
                .Which.Value.Should().BeOfType <Dictionary <string, string> >()
                .Which["confirmToken"].Should().NotBeNull("the alias has not yet been confirmed");
            }
        }
        public async Task WrongAlias_Email_NotFound()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                Alias alias;
                ef.Aliases.Add(alias = new Alias
                {
                    EmailAddress = "*****@*****.**"
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.Email("*****@*****.**", new AliasesController.EmailRequestBody());

                result.Should().BeOfType <NotFoundResult>("the email address doesn't match");
            }
        }
        public async Task TwoAliases_IndexDelete_NoContentWithCascadingDeletion()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Beings.Add(new Being
                {
                    Aliases = new HashSet <Alias>
                    {
                        new Alias
                        {
                            EmailAddress  = "*****@*****.**",
                            LoginAttempts = new HashSet <LoginAttempt>
                            {
                                new LoginAttempt
                                {
                                    ClientName = "Test"
                                }
                            },
                            Emails = new HashSet <Email>
                            {
                                new Email
                                {
                                    From    = "*****@*****.**",
                                    Subject = "Deletion"
                                }
                            }
                        },
                        new Alias
                        {
                            EmailAddress = "*****@*****.**"
                        }
                    }
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.IndexDelete("*****@*****.**");

                result.Should().BeOfType <NoContentResult>("the alias may be deleted");
                (await ef.Aliases.CountAsync()).Should().Be(1, "one of the aliases should be deleted");
                (await ef.Emails.AnyAsync()).Should().Be(false, "the deletion should cascade to emails");
                (await ef.LoginAttempts.AnyAsync()).Should().Be(false, "the deletion should cascade to login attempts");
            }
        }
        public async Task Alias_IndexPostWithExistingEmail_Conflict()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias {
                    EmailAddress = "*****@*****.**"
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.IndexPost("*****@*****.**", new AliasesController.IndexPostRequestBody
                {
                    password = "******"
                });

                result.Should().BeOfType <StatusCodeResult>().Which.StatusCode.Should().Be(StatusCodes.Status409Conflict,
                                                                                           "the alias exists in the database");
            }
        }
        public async Task Alias_IndexPostWithPassword_NoContentWithNewAliasAndBeing()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias {
                    EmailAddress = "*****@*****.**"
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.IndexPost("*****@*****.**", new AliasesController.IndexPostRequestBody
                {
                    password = "******"
                });

                result.Should().BeOfType <NoContentResult>("a non-existing email address was supplied along with a password");
                ef.Aliases.Include(a => a.Being).Should().Contain(a => a.EmailAddress == "*****@*****.**", "a new alias should have been created")
                .Which.Being.Should().NotBeNull("a new being should have been created");
            }
        }
        public async Task Alias_IndexPostWithWrongOtherEmail_NotFound()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias
                {
                    EmailAddress = "*****@*****.**"
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPostRequestBody body = new AliasesController.IndexPostRequestBody
                {
                    otherEmailAddress = "*****@*****.**"
                };
                IActionResult result = await patient.IndexPost("*****@*****.**", body);

                result.Should().BeOfType <NotFoundResult>($"the '{nameof(body.otherEmailAddress)}' does not exist");
            }
        }
        public async Task UnconfirmedAlias_Confirm_NoContentWithUpdatedDateConfirmed()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                Alias alias;
                ef.Aliases.Add(alias = new Alias {
                    EmailAddress = "*****@*****.**"
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.ConfirmPost("*****@*****.**", new AliasesController.ConfirmPostRequestBody
                {
                    confirmToken = alias.ConfirmationToken
                });

                result.Should().BeOfType <NoContentResult>("the confirmation token matches");
                alias.DateConfirmed.Should().Be(now.UtcNow, "the alias is newly confirmed");
            }
        }
        public async Task UnconfirmedAliasWithMismatchingToken_Confirm_Unauthorized()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                Alias alias;
                ef.Aliases.Add(alias = new Alias {
                    EmailAddress      = "*****@*****.**",
                    ConfirmationToken = "abc"
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.ConfirmPost("*****@*****.**", new AliasesController.ConfirmPostRequestBody
                {
                    confirmToken = "αβγ"
                });

                result.Should().BeOfType <UnauthorizedResult>("the confirmation token does not match");
            }
        }
        public async Task Alias_IndexPostWithOtherEmail_NoContentWithNewAliasAndLinkedBeing()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias
                {
                    EmailAddress = "*****@*****.**",
                    Being        = new Being()
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPostRequestBody body = new AliasesController.IndexPostRequestBody
                {
                    otherEmailAddress = "*****@*****.**"
                };
                IActionResult result = await patient.IndexPost("*****@*****.**", body);

                result.Should().BeOfType <NoContentResult>("an existing email address (only) was supplied");
                ef.Aliases.Include(a => a.Being).Should().Contain(a => a.EmailAddress == "*****@*****.**", "a new alias should have been created")
                .Which.Being.Aliases.Should().HaveCount(2, "the new alias should have been linked to the existing being");
            }
        }
        public async Task ConfirmedAlias_Confirm_NoContentWithNoUpdate()
        {
            DateTime yesterday = now.UtcNow.AddDays(-1);

            using (IdentityWsDbContext ef = CreateEf()) {
                Alias alias;
                ef.Aliases.Add(alias = new Alias
                {
                    EmailAddress  = "*****@*****.**",
                    DateConfirmed = yesterday
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                IActionResult result = await patient.ConfirmPost("*****@*****.**", new AliasesController.ConfirmPostRequestBody {
                    confirmToken = alias.ConfirmationToken
                });

                result.Should().BeOfType <NoContentResult>("the confirmation token matches");
                alias.DateConfirmed.Should().Be(yesterday, "the previous confirmation date should not be overridden");
            }
        }
        public async Task RightAlias_Email_NewDbRecordAndNudge()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                Alias alias;
                ef.Aliases.Add(alias = new Alias
                {
                    EmailAddress = "*****@*****.**"
                });
                await ef.SaveChangesAsync();

                bool nudged = false;
                Mock <IBackgroundJobRunner <EmailQueueProcessor> > mock_runner = new Mock <IBackgroundJobRunner <EmailQueueProcessor> >();
                mock_runner.Setup(m => m.Nudge()).Callback(() => nudged = true);
                AliasesController patient = new AliasesController(ef, dummyLog, now, mock_runner.Object);

                IActionResult result = await patient.Email("*****@*****.**", new AliasesController.EmailRequestBody
                {
                    from     = "*****@*****.**",
                    replyTo  = "*****@*****.**",
                    subject  = "Testing",
                    bodyText = "Hello, World!"
                });

                Email email = await ef.Emails.FirstAsync(e => e.AliasID == alias.AliasID);

                result.Should().BeOfType <NoContentResult>("email processing is asynchronous");
                email.Should().Match <Email>(e =>
                                             e.From == "*****@*****.**" &&
                                             e.ReplyTo == "*****@*****.**" &&
                                             e.Subject == "Testing" &&
                                             e.BodyText == "Hello, World!" &&
                                             !e.SendIfUnconfirmed,
                                             "all values should be stored as-is in the database except SendIfUnconfirmed, which defaults to false");
                nudged.Should().BeTrue("a new queue run should be triggered sooner rather than later");
            }
        }
        public async Task Alias_IndexPatchNoOldPasswordOrResetToken_BadRequest()
        {
            using (IdentityWsDbContext ef = CreateEf()) {
                ef.Aliases.Add(new Alias
                {
                    EmailAddress = "*****@*****.**",
                    Being        = new Being
                    {
                        SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1")
                    }
                });
                await ef.SaveChangesAsync();

                AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner);

                AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody
                {
                    password = "******"
                };
                IActionResult result = await patient.IndexPatch("*****@*****.**", body);

                result.Should().BeOfType <BadRequestResult>($"neither {nameof(body.resetToken)} nor {nameof(body.oldPassword)} was provided");
            }
        }