Write tests for UserManager
Also fixes a bug with checking token validity dates, reorders some methods for clarity, and ensures that the login menu is not shown in the password reset screen.
This commit is contained in:
parent
6fb88c5454
commit
1c62c73055
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "death-notifier",
|
"name": "death-notifier",
|
||||||
"version": "0.14.10", "_comment_version": "Also update version in `composer.json`!",
|
"version": "0.14.11", "_comment_version": "Also update version in `composer.json`!",
|
||||||
"description": "Get notified when a famous person dies.",
|
"description": "Get notified when a famous person dies.",
|
||||||
"author": "Florine W. Dekker",
|
"author": "Florine W. Dekker",
|
||||||
"browser": "dist/bundle.js",
|
"browser": "dist/bundle.js",
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
<a href="./">Click here to return to the main page</a>
|
<a href="./">Click here to return to the main page</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div id="welcome-part" class="grid">
|
<div id="welcome-part" class="grid hidden">
|
||||||
<article>
|
<article>
|
||||||
<header>
|
<header>
|
||||||
<hgroup>
|
<hgroup>
|
||||||
|
|
|
@ -117,8 +117,7 @@ class UserManager
|
||||||
*/
|
*/
|
||||||
private function minutes_until_interval_elapsed(string $timestamp, int $interval): int
|
private function minutes_until_interval_elapsed(string $timestamp, int $interval): int
|
||||||
{
|
{
|
||||||
$minutes_since_timestamp = (new DateTime("@$timestamp"))->diff(new DateTime(), absolute: true)->i;
|
return $interval - ((time() - intval($timestamp)) / 60);
|
||||||
return $interval - $minutes_since_timestamp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,20 +147,6 @@ class UserManager
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the user with the given UUID.
|
|
||||||
*
|
|
||||||
* @param string $uuid the UUID of the user to delete
|
|
||||||
* @return Response a satisfied `Response` with payload `null`
|
|
||||||
*/
|
|
||||||
public function delete_user(string $uuid): Response
|
|
||||||
{
|
|
||||||
$stmt = $this->conn->prepare("DELETE FROM users WHERE uuid=:uuid;");
|
|
||||||
$stmt->bindValue(":uuid", $uuid);
|
|
||||||
$stmt->execute();
|
|
||||||
return Response::satisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a login attempt with the given email address and password.
|
* Validates a login attempt with the given email address and password.
|
||||||
*
|
*
|
||||||
|
@ -175,8 +160,9 @@ class UserManager
|
||||||
$stmt = $this->conn->prepare("SELECT uuid, password FROM users WHERE email=:email;");
|
$stmt = $this->conn->prepare("SELECT uuid, password FROM users WHERE email=:email;");
|
||||||
$stmt->bindValue(":email", $email);
|
$stmt->bindValue(":email", $email);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$user = $stmt->fetch();
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// TODO: Expose whether account exists, it's exposed in forgot password anyway
|
||||||
return $user === false || !password_verify($password, $user["password"])
|
return $user === false || !password_verify($password, $user["password"])
|
||||||
? [Response::unsatisfied("Incorrect combination of email and password.", "password"), null]
|
? [Response::unsatisfied("Incorrect combination of email and password.", "password"), null]
|
||||||
: [Response::satisfied(), $user["uuid"]];
|
: [Response::satisfied(), $user["uuid"]];
|
||||||
|
@ -218,6 +204,20 @@ class UserManager
|
||||||
: Response::satisfied($user);
|
: Response::satisfied($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the user with the given UUID.
|
||||||
|
*
|
||||||
|
* @param string $uuid the UUID of the user to delete
|
||||||
|
* @return Response a satisfied `Response` with payload `null`
|
||||||
|
*/
|
||||||
|
public function delete_user(string $uuid): Response
|
||||||
|
{
|
||||||
|
$stmt = $this->conn->prepare("DELETE FROM users WHERE uuid=:uuid;");
|
||||||
|
$stmt->bindValue(":uuid", $uuid);
|
||||||
|
$stmt->execute();
|
||||||
|
return Response::satisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the indicated user's email address.
|
* Updates the indicated user's email address.
|
||||||
|
@ -229,6 +229,7 @@ class UserManager
|
||||||
*/
|
*/
|
||||||
public function set_email(string $uuid, string $email): Response
|
public function set_email(string $uuid, string $email): Response
|
||||||
{
|
{
|
||||||
|
// TODO: Also send message to old email address
|
||||||
return Database::transaction($this->conn, function () use ($uuid, $email) {
|
return Database::transaction($this->conn, function () use ($uuid, $email) {
|
||||||
$stmt = $this->conn->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE uuid=:uuid AND email=:email);");
|
$stmt = $this->conn->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE uuid=:uuid AND email=:email);");
|
||||||
$stmt->bindValue(":uuid", $uuid);
|
$stmt->bindValue(":uuid", $uuid);
|
||||||
|
@ -255,46 +256,6 @@ class UserManager
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resends the email verification email.
|
|
||||||
*
|
|
||||||
* @param string $uuid the UUID of the user to resend the email for
|
|
||||||
* @return Response a satisfied `Response` with payload `null` if the email was sent, or an unsatisfied `Response`
|
|
||||||
* otherwise
|
|
||||||
*/
|
|
||||||
public function resend_verify_email(string $uuid): Response
|
|
||||||
{
|
|
||||||
return Database::transaction($this->conn, function () use ($uuid) {
|
|
||||||
$stmt = $this->conn->prepare("SELECT email, email_verification_token, email_verification_token_timestamp
|
|
||||||
FROM users
|
|
||||||
WHERE uuid=:uuid;");
|
|
||||||
$stmt->bindValue(":uuid", $uuid);
|
|
||||||
$stmt->execute();
|
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if ($user["email_verification_token"] === null)
|
|
||||||
return Response::unsatisfied("Your email address is already verified.");
|
|
||||||
|
|
||||||
$minutes_left = $this->minutes_until_interval_elapsed(
|
|
||||||
$user["email_verification_token_timestamp"],
|
|
||||||
self::MINUTES_BETWEEN_VERIFICATION_EMAILS
|
|
||||||
);
|
|
||||||
if ($minutes_left > 0) {
|
|
||||||
return Response::unsatisfied(
|
|
||||||
"A verification email was sent recently. " .
|
|
||||||
"Please wait $minutes_left more minute(s) before requesting a new email."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $this->conn->prepare("UPDATE users
|
|
||||||
SET email_verification_token_timestamp=unixepoch()
|
|
||||||
WHERE uuid=:uuid;");
|
|
||||||
$stmt->bindValue(":uuid", $uuid);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
return $this->mailer->queue_email(new VerifyEmailEmail($user["email"], $user["email_verification_token"]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies an email address with a token.
|
* Verifies an email address with a token.
|
||||||
*
|
*
|
||||||
|
@ -312,19 +273,21 @@ class UserManager
|
||||||
$stmt->bindValue(":email", $email);
|
$stmt->bindValue(":email", $email);
|
||||||
$stmt->bindValue(":token", $token);
|
$stmt->bindValue(":token", $token);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (sizeof($results) === 0)
|
if ($user === false)
|
||||||
return Response::unsatisfied(
|
return Response::unsatisfied(
|
||||||
"Failed to verify email address. " .
|
"Failed to verify email address. " .
|
||||||
"Maybe you already verified your email address?"
|
"Maybe you already verified your email address?"
|
||||||
);
|
);
|
||||||
|
|
||||||
$minutes_remaining = $this->minutes_until_interval_elapsed(
|
$minutes_remaining = $this->minutes_until_interval_elapsed(
|
||||||
$results[0]["email_verification_token_timestamp"],
|
$user["email_verification_token_timestamp"],
|
||||||
self::MINUTES_VALID_VERIFICATION
|
self::MINUTES_VALID_VERIFICATION
|
||||||
);
|
);
|
||||||
if ($minutes_remaining < 0)
|
if ($minutes_remaining < 0)
|
||||||
return Response::unsatisfied("This email verification link has expired.");
|
return Response::unsatisfied(
|
||||||
|
"This email verification link has expired. Log in and request a new verification email."
|
||||||
|
);
|
||||||
|
|
||||||
$stmt = $this->conn->prepare("UPDATE users SET email_verification_token=null WHERE email=:email;");
|
$stmt = $this->conn->prepare("UPDATE users SET email_verification_token=null WHERE email=:email;");
|
||||||
$stmt->bindValue(":email", $email);
|
$stmt->bindValue(":email", $email);
|
||||||
|
@ -334,6 +297,49 @@ class UserManager
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resends the email verification email.
|
||||||
|
*
|
||||||
|
* @param string $uuid the UUID of the user to resend the email for
|
||||||
|
* @return Response a satisfied `Response` with payload `null` if the email was sent, or an unsatisfied `Response`
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
public function resend_verify_email(string $uuid): Response
|
||||||
|
{
|
||||||
|
return Database::transaction($this->conn, function () use ($uuid) {
|
||||||
|
$stmt = $this->conn->prepare("SELECT email, email_verification_token, email_verification_token_timestamp
|
||||||
|
FROM users
|
||||||
|
WHERE uuid=:uuid;");
|
||||||
|
$stmt->bindValue(":uuid", $uuid);
|
||||||
|
$stmt->execute();
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($user === false || $user["email_verification_token"] === null)
|
||||||
|
return Response::unsatisfied("Your email address is already verified.");
|
||||||
|
|
||||||
|
$minutes_left = $this->minutes_until_interval_elapsed(
|
||||||
|
$user["email_verification_token_timestamp"],
|
||||||
|
self::MINUTES_BETWEEN_VERIFICATION_EMAILS
|
||||||
|
);
|
||||||
|
if ($minutes_left > 0) {
|
||||||
|
return Response::unsatisfied(
|
||||||
|
"A verification email was sent recently. " .
|
||||||
|
"Please wait $minutes_left more minute(s) before requesting a new email."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->conn->prepare("UPDATE users
|
||||||
|
SET email_verification_token=lower(hex(randomblob(16))),
|
||||||
|
email_verification_token_timestamp=unixepoch()
|
||||||
|
WHERE uuid=:uuid
|
||||||
|
RETURNING email_verification_token;");
|
||||||
|
$stmt->bindValue(":uuid", $uuid);
|
||||||
|
$stmt->execute();
|
||||||
|
$verification_token = $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["email_verification_token"];
|
||||||
|
|
||||||
|
return $this->mailer->queue_email(new VerifyEmailEmail($user["email"], $verification_token));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles whether the user receives death notifications.
|
* Toggles whether the user receives death notifications.
|
||||||
*
|
*
|
||||||
|
@ -349,8 +355,8 @@ class UserManager
|
||||||
WHERE uuid=:uuid;");
|
WHERE uuid=:uuid;");
|
||||||
$stmt->bindValue(":uuid", $uuid);
|
$stmt->bindValue(":uuid", $uuid);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$user = $stmt->fetchAll(PDO::FETCH_ASSOC)[0];
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if ($user["email_verification_token"] !== null)
|
if ($user === false || $user["email_verification_token"] !== null)
|
||||||
return Response::unsatisfied("Please verify your email address before enabling notifications.");
|
return Response::unsatisfied("Please verify your email address before enabling notifications.");
|
||||||
|
|
||||||
$stmt = $this->conn->prepare("UPDATE users SET email_notifications_enabled=:enabled WHERE uuid=:uuid;");
|
$stmt = $this->conn->prepare("UPDATE users SET email_notifications_enabled=:enabled WHERE uuid=:uuid;");
|
||||||
|
@ -379,7 +385,7 @@ class UserManager
|
||||||
$stmt->bindValue(":uuid", $uuid);
|
$stmt->bindValue(":uuid", $uuid);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (!password_verify($password_old, $user["password"]))
|
if ($user === false || !password_verify($password_old, $user["password"]))
|
||||||
return Response::unsatisfied("Incorrect old password.", "password_old");
|
return Response::unsatisfied("Incorrect old password.", "password_old");
|
||||||
|
|
||||||
$stmt = $this->conn->prepare("UPDATE users
|
$stmt = $this->conn->prepare("UPDATE users
|
||||||
|
@ -407,12 +413,12 @@ class UserManager
|
||||||
$stmt = $this->conn->prepare("SELECT password_reset_token_timestamp FROM users WHERE email=:email;");
|
$stmt = $this->conn->prepare("SELECT password_reset_token_timestamp FROM users WHERE email=:email;");
|
||||||
$stmt->bindValue(":email", $email);
|
$stmt->bindValue(":email", $email);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (sizeof($results) === 0)
|
if ($user === false)
|
||||||
return Response::unsatisfied("No account with that email address has been registered.");
|
return Response::unsatisfied("No account with that email address has been registered.");
|
||||||
|
|
||||||
$minutes_left = $this->minutes_until_interval_elapsed(
|
$minutes_left = $this->minutes_until_interval_elapsed(
|
||||||
$results[0]["password_reset_token_timestamp"],
|
$user["password_reset_token_timestamp"],
|
||||||
self::MINUTES_BETWEEN_PASSWORD_RESETS
|
self::MINUTES_BETWEEN_PASSWORD_RESETS
|
||||||
);
|
);
|
||||||
if ($minutes_left > 0) {
|
if ($minutes_left > 0) {
|
||||||
|
@ -452,14 +458,14 @@ class UserManager
|
||||||
$stmt->bindValue(":email", $email);
|
$stmt->bindValue(":email", $email);
|
||||||
$stmt->bindValue(":token", $token);
|
$stmt->bindValue(":token", $token);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (sizeof($results) === 0)
|
if ($user === false)
|
||||||
return Response::unsatisfied(
|
return Response::unsatisfied(
|
||||||
"This password reset link is invalid. Maybe you already reset your password?"
|
"This password reset link is invalid. Maybe you already reset your password?"
|
||||||
);
|
);
|
||||||
|
|
||||||
$minutes_remaining = $this->minutes_until_interval_elapsed(
|
$minutes_remaining = $this->minutes_until_interval_elapsed(
|
||||||
$results[0]["password_reset_token_timestamp"],
|
$user["password_reset_token_timestamp"],
|
||||||
self::MINUTES_VALID_PASSWORD_RESET
|
self::MINUTES_VALID_PASSWORD_RESET
|
||||||
);
|
);
|
||||||
if ($minutes_remaining < 0)
|
if ($minutes_remaining < 0)
|
||||||
|
|
|
@ -35,7 +35,7 @@ abstract class DatabaseTestCase extends TestCase
|
||||||
/**
|
/**
|
||||||
* @return Mailer the `Mailer` to install the database schema of
|
* @return Mailer the `Mailer` to install the database schema of
|
||||||
*/
|
*/
|
||||||
function getMailer(): Mailer
|
function get_mailer(): Mailer
|
||||||
{
|
{
|
||||||
return $this->createStub(Mailer::class);
|
return $this->createStub(Mailer::class);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ abstract class DatabaseTestCase extends TestCase
|
||||||
/**
|
/**
|
||||||
* @return TrackingManager the `TrackingManager` to install the database schema of
|
* @return TrackingManager the `TrackingManager` to install the database schema of
|
||||||
*/
|
*/
|
||||||
function getTrackingManager(): TrackingManager
|
function get_tracking_manager(): TrackingManager
|
||||||
{
|
{
|
||||||
return $this->createStub(TrackingManager::class);
|
return $this->createStub(TrackingManager::class);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ abstract class DatabaseTestCase extends TestCase
|
||||||
/**
|
/**
|
||||||
* @return UserManager the `UserManager` to install the database schema of
|
* @return UserManager the `UserManager` to install the database schema of
|
||||||
*/
|
*/
|
||||||
function getUserManager(): UserManager
|
function get_user_manager(): UserManager
|
||||||
{
|
{
|
||||||
return $this->createStub(UserManager::class);
|
return $this->createStub(UserManager::class);
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,6 @@ abstract class DatabaseTestCase extends TestCase
|
||||||
$this->logger = new Logger("DatabaseTestCase");
|
$this->logger = new Logger("DatabaseTestCase");
|
||||||
$this->database = new Database($this->logger, $db_tmp_file);
|
$this->database = new Database($this->logger, $db_tmp_file);
|
||||||
|
|
||||||
$this->database->auto_install($this->getMailer(), $this->getUserManager(), $this->getTrackingManager());
|
$this->database->auto_install($this->get_mailer(), $this->get_user_manager(), $this->get_tracking_manager());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,13 @@
|
||||||
|
|
||||||
namespace com\fwdekker\deathnotifier;
|
namespace com\fwdekker\deathnotifier;
|
||||||
|
|
||||||
|
use com\fwdekker\deathnotifier\mailer\ChangedEmailEmail;
|
||||||
|
use com\fwdekker\deathnotifier\mailer\ChangedPasswordEmail;
|
||||||
use com\fwdekker\deathnotifier\mailer\Mailer;
|
use com\fwdekker\deathnotifier\mailer\Mailer;
|
||||||
use com\fwdekker\deathnotifier\mailer\RegisterEmail;
|
use com\fwdekker\deathnotifier\mailer\RegisterEmail;
|
||||||
|
use com\fwdekker\deathnotifier\mailer\ResetPasswordEmail;
|
||||||
|
use com\fwdekker\deathnotifier\mailer\VerifyEmailEmail;
|
||||||
|
use PDO;
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,39 +21,119 @@ class UserManagerTest extends DatabaseTestCase
|
||||||
private Mailer&MockObject $mailer;
|
private Mailer&MockObject $mailer;
|
||||||
|
|
||||||
|
|
||||||
public function getUserManager(): UserManager
|
public function get_user_manager(): UserManager
|
||||||
{
|
{
|
||||||
return new UserManager($this->logger, $this->database->conn, $this->mailer);
|
return new UserManager($this->logger, $this->database->conn, $this->mailer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->mailer = $this->createMock(Mailer::class);
|
$this->mailer = $this->createMock(Mailer::class);
|
||||||
|
$this->mailer->method("queue_email")->willReturn(Response::satisfied());
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$this->user_manager = $this->getUserManager();
|
$this->user_manager = $this->get_user_manager();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the UUID of the given email address.
|
||||||
|
*
|
||||||
|
* @param string $email the email address to return the email verification token of
|
||||||
|
* @return string|null the email verification token of the given email address
|
||||||
|
*/
|
||||||
|
private function get_uuid(string $email): ?string
|
||||||
|
{
|
||||||
|
$stmt = $this->database->conn->prepare("SELECT uuid FROM users WHERE email=:email;");
|
||||||
|
$stmt->bindValue(":email", $email);
|
||||||
|
$stmt->execute();
|
||||||
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
return empty($results) ? null : $results[0]["uuid"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the email verification token of the given email address.
|
||||||
|
*
|
||||||
|
* @param string $email the email address to return the email verification token of
|
||||||
|
* @return string|null the email verification token of the given email address, or `null` if there is no user with
|
||||||
|
* the given email address
|
||||||
|
*/
|
||||||
|
private function get_email_token(string $email): ?string
|
||||||
|
{
|
||||||
|
$stmt = $this->database->conn->prepare("SELECT email_verification_token FROM users WHERE email=:email;");
|
||||||
|
$stmt->bindValue(":email", $email);
|
||||||
|
$stmt->execute();
|
||||||
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
return empty($results) ? null : $results[0]["email_verification_token"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the timestamp of the email verification token of the given email address, if a user with that email
|
||||||
|
* address exists.
|
||||||
|
*
|
||||||
|
* @param string $email the email address to set the timestamp of
|
||||||
|
* @param int $timestamp the timestamp to set
|
||||||
|
*/
|
||||||
|
private function set_email_token_timestamp(string $email, int $timestamp): void
|
||||||
|
{
|
||||||
|
$stmt = $this->database->conn->prepare("UPDATE users
|
||||||
|
SET email_verification_token_timestamp=:timestamp
|
||||||
|
WHERE email=:email;");
|
||||||
|
$stmt->bindValue(":email", $email);
|
||||||
|
$stmt->bindValue(":timestamp", $timestamp);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the password verification token of the given email address.
|
||||||
|
*
|
||||||
|
* @param string $email the email address to return the password verification token of
|
||||||
|
* @return string|null the password verification token of the given email address, or `null` if there is no user
|
||||||
|
* with the given email address
|
||||||
|
*/
|
||||||
|
private function get_password_token(string $email): ?string
|
||||||
|
{
|
||||||
|
$stmt = $this->database->conn->prepare("SELECT password_reset_token FROM users WHERE email=:email;");
|
||||||
|
$stmt->bindValue(":email", $email);
|
||||||
|
$stmt->execute();
|
||||||
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
return empty($results) ? null : $results[0]["password_reset_token"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the timestamp of the password verification token of the given email address, if a user with that email
|
||||||
|
* address exists.
|
||||||
|
*
|
||||||
|
* @param string $email the email address to set the timestamp of
|
||||||
|
* @param int $timestamp the timestamp to set
|
||||||
|
*/
|
||||||
|
private function set_password_token_timestamp(string $email, int $timestamp): void
|
||||||
|
{
|
||||||
|
$stmt = $this->database->conn->prepare("UPDATE users
|
||||||
|
SET password_reset_token_timestamp=:timestamp
|
||||||
|
WHERE email=:email;");
|
||||||
|
$stmt->bindValue(":email", $email);
|
||||||
|
$stmt->bindValue(":timestamp", $timestamp);
|
||||||
|
$stmt->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function test_register_user_returns_an_unsatisfied_response_if_the_email_is_used(): void
|
public function test_register_user_returns_an_unsatisfied_response_if_the_email_is_used(): void
|
||||||
{
|
{
|
||||||
$this->mailer->method("queue_email")->willReturn(Response::satisfied());
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
$this->user_manager->register_user("test@test.test", "password");
|
|
||||||
|
|
||||||
$response = $this->user_manager->register_user("test@test.test", "password");
|
$response = $this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
self::assertFalse($response->satisfied);
|
self::assertFalse($response->satisfied);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_register_user_returns_a_satisfied_response_if_the_email_is_unused(): void
|
public function test_register_user_registers_the_user_and_returns_a_satisfied_response_if_the_email_is_unused(): void
|
||||||
{
|
{
|
||||||
$this->mailer->method("queue_email")->willReturn(Response::satisfied());
|
$response = $this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
$response = $this->user_manager->register_user("test@test.test", "password");
|
|
||||||
|
|
||||||
self::assertTrue($response->satisfied);
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertNotNull($this->get_uuid("john@example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_register_user_sends_an_email_with_the_verification_token(): void
|
public function test_register_user_sends_an_email_with_the_verification_token(): void
|
||||||
|
@ -56,11 +141,527 @@ class UserManagerTest extends DatabaseTestCase
|
||||||
$this->mailer
|
$this->mailer
|
||||||
->expects($this->once())
|
->expects($this->once())
|
||||||
->method("queue_email")
|
->method("queue_email")
|
||||||
->with($this->callback(fn($email) => $email instanceof RegisterEmail && $email->recipient === "test@test.test"))
|
->with($this->callback(
|
||||||
->willReturn(Response::satisfied());
|
fn($email) => $email instanceof RegisterEmail && $email->recipient === "john@example.com")
|
||||||
|
);
|
||||||
|
|
||||||
$response = $this->user_manager->register_user("test@test.test", "password");
|
$response = $this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
self::assertTrue($response->satisfied);
|
self::assertTrue($response->satisfied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_check_login_returns_an_unsatisfied_response_if_the_email_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
|
$response = $this->user_manager->check_login("lydia@other.example.com", "password");
|
||||||
|
|
||||||
|
self::assertFalse($response[0]->satisfied);
|
||||||
|
self::assertNull($response[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_check_login_returns_an_unsatisfied_response_if_the_email_is_incorrect(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
|
$response = $this->user_manager->check_login("lydia@other.example.com", "password");
|
||||||
|
|
||||||
|
self::assertFalse($response[0]->satisfied);
|
||||||
|
self::assertNull($response[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_check_login_returns_a_satisfied_response_if_the_email_and_password_are_correct(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
|
$response = $this->user_manager->check_login("john@example.com", "password");
|
||||||
|
|
||||||
|
self::assertTrue($response[0]->satisfied);
|
||||||
|
self::assertNotNull($response[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_user_exists_returns_false_if_the_user_does_not_exist(): void
|
||||||
|
{
|
||||||
|
self::assertFalse($this->user_manager->user_exists("invalid-uuid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_exists_returns_true_if_the_user_exists(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
self::assertTrue($this->user_manager->user_exists($uuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_get_user_returns_a_satisfied_response_with_the_user_data_if_the_uuid_exists(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->get_user($uuid);
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertArrayHasKey("email", $response->payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_get_user_returns_an_unsatisfied_response_if_the_uuid_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$response = $this->user_manager->get_user("invalid-uuid");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_delete_user_deletes_the_user_and_returns_a_satisfied_response_if_the_uuid_is_used(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->delete_user($uuid);
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertFalse($this->user_manager->user_exists($uuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_delete_user_returns_a_satisfied_response_even_if_the_user_was_already_deleted(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
$this->user_manager->delete_user($uuid);
|
||||||
|
|
||||||
|
$response = $this->user_manager->delete_user($uuid);
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_delete_user_returns_a_satisfied_response_even_if_the_uuid_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$response = $this->user_manager->delete_user("invalid-uuid");
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_set_email_returns_an_unsatisfied_response_if_the_email_is_not_different(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->set_email($uuid, "john@example.com");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_set_email_returns_an_unsatisfied_response_if_the_email_is_already_used(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->user_manager->register_user("lydia@other.example.com", "another-password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->set_email($uuid, "lydia@other.example.com");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_set_email_returns_a_satisfied_response_and_sends_an_email_if_the_email_is_unused(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$this->mailer
|
||||||
|
->expects($this->once())
|
||||||
|
->method("queue_email")
|
||||||
|
->with($this->callback(
|
||||||
|
fn($email) => $email instanceof ChangedEmailEmail && $email->recipient === "lydia@other.example.com")
|
||||||
|
);
|
||||||
|
$response = $this->user_manager->set_email($uuid, "lydia@other.example.com");
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertNull($this->get_email_token("john@example.com"));
|
||||||
|
self::assertNotNull($this->get_email_token("lydia@other.example.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_set_email_retains_the_uuid_of_the_user(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$old_uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$this->user_manager->set_email($old_uuid, "lydia@other.example.com");
|
||||||
|
$new_uuid = $this->get_uuid("lydia@other.example.com");
|
||||||
|
|
||||||
|
self::assertEquals($old_uuid, $new_uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_set_email_disallows_logging_in_with_the_old_email_address(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$this->user_manager->set_email($uuid, "lydia@other.example.com");
|
||||||
|
|
||||||
|
self::assertFalse($this->user_manager->check_login("john@example.com", "password")[0]->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_verify_email_returns_an_unsatisfied_response_if_the_email_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$response = $this->user_manager->verify_email("does-not-exist@example.com", "token");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_verify_email_returns_an_unsatisfied_response_if_the_wrong_token_is_given(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
|
$response = $this->user_manager->verify_email("john@example.com", "incorrect-token");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_verify_email_returns_an_unsatisfied_response_if_an_email_is_already_verified(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$token = $this->get_email_token("john@example.com");
|
||||||
|
|
||||||
|
$this->user_manager->verify_email("john@example.com", $token);
|
||||||
|
$response = $this->user_manager->verify_email("john@example.com", $token);
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_verify_email_returns_an_unsatisfied_response_if_the_token_is_expired(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_email_token_timestamp("john@example.com", "1640995200");
|
||||||
|
$token = $this->get_email_token("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->verify_email("john@example.com", $token);
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_verify_email_verifies_the_email_and_returns_a_satisfied_response_if_the_verification_token_is_valid(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
$token = $this->get_email_token("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->verify_email("john@example.com", $token);
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertNull($this->get_email_token("john@example.com"));
|
||||||
|
self::assertTrue($this->user_manager->get_user($uuid)->payload["email_verified"] === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_resend_verify_email_returns_an_unsatisfied_response_if_the_uuid_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$response = $this->user_manager->resend_verify_email("invalid-uuid");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_resend_verify_email_does_not_send_an_email_and_returns_an_unsatisfied_response_if_the_email_is_already_verified(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
$token = $this->get_email_token("john@example.com");
|
||||||
|
$this->user_manager->verify_email("john@example.com", $token);
|
||||||
|
|
||||||
|
$this->mailer->expects($this->never())->method("queue_email");
|
||||||
|
$response = $this->user_manager->resend_verify_email($uuid);
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_resend_verify_email_does_not_send_an_email_and_returns_an_unsatisfied_response_if_the_user_recently_registered(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$this->mailer->expects($this->never())->method("queue_email");
|
||||||
|
$response = $this->user_manager->resend_verify_email($uuid);
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_resend_verify_email_does_not_send_an_email_and_returns_an_unsatisfied_response_if_an_email_was_recently_sent(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_email_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_VERIFICATION_EMAILS + 1)
|
||||||
|
);
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
$this->user_manager->resend_verify_email($uuid);
|
||||||
|
|
||||||
|
$this->mailer->expects($this->never())->method("queue_email");
|
||||||
|
$response = $this->user_manager->resend_verify_email($uuid);
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_resend_verify_email_sends_an_email_and_returns_a_satisfied_response_if_no_email_was_recently_sent(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_email_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_VERIFICATION_EMAILS + 1)
|
||||||
|
);
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$this->mailer
|
||||||
|
->expects($this->once())
|
||||||
|
->method("queue_email")
|
||||||
|
->with($this->callback(
|
||||||
|
fn($email) => $email instanceof VerifyEmailEmail && $email->recipient === "john@example.com")
|
||||||
|
);
|
||||||
|
$response = $this->user_manager->resend_verify_email($uuid);
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_resend_verify_email_disallows_verifying_the_email_with_the_old_token(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$old_token = $this->get_email_token("john@example.com");
|
||||||
|
$this->set_email_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_VERIFICATION_EMAILS + 1)
|
||||||
|
);
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
$this->user_manager->resend_verify_email($uuid);
|
||||||
|
|
||||||
|
$response = $this->user_manager->verify_email("john@example.com", $old_token);
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_toggle_notifications_returns_an_unsatisfied_response_if_the_uuid_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$response = $this->user_manager->toggle_notifications("invalid-uuid");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_toggle_notifications_does_not_toggle_notifications_and_returns_an_unsatisfied_response_if_the_email_is_not_verified(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->toggle_notifications($uuid);
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
self::assertTrue($this->user_manager->get_user($uuid)->payload["email_notifications_enabled"] === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_toggle_notifications_enables_notifications_and_returns_a_satisfied_response_if_they_are_disabled(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
$token = $this->get_email_token("john@example.com");
|
||||||
|
$this->user_manager->verify_email("john@example.com", $token);
|
||||||
|
|
||||||
|
$this->user_manager->toggle_notifications($uuid);
|
||||||
|
$response = $this->user_manager->toggle_notifications($uuid);
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertTrue($this->user_manager->get_user($uuid)->payload["email_notifications_enabled"] === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_toggle_notifications_disables_notifications_and_returns_a_satisfied_response_if_they_are_enabled(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
$token = $this->get_email_token("john@example.com");
|
||||||
|
$this->user_manager->verify_email("john@example.com", $token);
|
||||||
|
|
||||||
|
$response = $this->user_manager->toggle_notifications($uuid);
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertFalse($this->user_manager->get_user($uuid)->payload["email_notifications_enabled"] === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_set_password_returns_an_unsatisfied_response_if_the_uuid_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$response = $this->user_manager->set_password("invalid_uuid", "old-password", "new-password");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_set_password_returns_an_unsatisfied_response_if_the_old_password_is_incorrect(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->set_password($uuid, "wrong-password", "new-password");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
self::assertFalse($this->user_manager->check_login("john@example.com", "new-password")[0]->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_set_password_sends_an_email_and_returns_a_satisfied_response_if_the_old_password_is_correct(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
|
||||||
|
$this->mailer
|
||||||
|
->expects($this->once())
|
||||||
|
->method("queue_email")
|
||||||
|
->with($this->callback(
|
||||||
|
fn($email) => $email instanceof ChangedPasswordEmail && $email->recipient === "john@example.com")
|
||||||
|
);
|
||||||
|
$response = $this->user_manager->set_password($uuid, "password", "new-password");
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertTrue($this->user_manager->check_login("john@example.com", "new-password")[0]->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_set_password_disallows_resetting_the_password_with_an_old_token(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_password_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_PASSWORD_RESETS + 1)
|
||||||
|
);
|
||||||
|
$this->user_manager->send_password_reset("john@example.com");
|
||||||
|
$uuid = $this->get_uuid("john@example.com");
|
||||||
|
$token = $this->get_password_token("john@example.com");
|
||||||
|
|
||||||
|
$this->user_manager->set_password($uuid, "password", "new-password");
|
||||||
|
$response = $this->user_manager->reset_password("john@example.com", $token, "newer-password");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
self::assertFalse($this->user_manager->check_login("john@example.com", "newer-password")[0]->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_send_password_reset_returns_an_unsatisfied_response_if_the_email_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$response = $this->user_manager->send_password_reset("invalid@example.com");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_send_password_reset_does_not_send_an_email_and_returns_an_unsatisfied_response_if_the_user_recently_registered(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
|
$this->mailer->expects($this->never())->method("queue_email");
|
||||||
|
$response = $this->user_manager->send_password_reset("john@example.com");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_send_password_reset_does_not_send_an_email_and_returns_an_unsatisfied_response_if_an_email_was_recently_sent(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_password_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_PASSWORD_RESETS + 1)
|
||||||
|
);
|
||||||
|
$this->user_manager->send_password_reset("john@example.com");
|
||||||
|
|
||||||
|
$this->mailer->expects($this->never())->method("queue_email");
|
||||||
|
$response = $this->user_manager->send_password_reset("john@example.com");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_send_password_reset_sends_an_email_and_returns_a_satisfied_response_if_no_email_was_recently_sent(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_password_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_PASSWORD_RESETS + 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->mailer
|
||||||
|
->expects($this->once())
|
||||||
|
->method("queue_email")
|
||||||
|
->with($this->callback(
|
||||||
|
fn($email) => $email instanceof ResetPasswordEmail && $email->recipient === "john@example.com")
|
||||||
|
);
|
||||||
|
$response = $this->user_manager->send_password_reset("john@example.com");
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_send_password_reset_disallows_resetting_the_password_with_the_old_token(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_password_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_PASSWORD_RESETS + 1)
|
||||||
|
);
|
||||||
|
$this->user_manager->send_password_reset("john@example.com");
|
||||||
|
$old_token = $this->get_password_token("john@example.com");
|
||||||
|
$this->set_password_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_PASSWORD_RESETS + 1)
|
||||||
|
);
|
||||||
|
$this->user_manager->send_password_reset("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->reset_password("john@example.com", $old_token, "password-new");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function test_reset_password_returns_an_unsatisfied_response_if_the_email_does_not_exist(): void
|
||||||
|
{
|
||||||
|
$response = $this->user_manager->reset_password("does-not-exist@example.com", "token", "new-password");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_reset_password_does_not_change_the_password_and_returns_an_unsatisfied_response_if_the_wrong_token_is_given(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
|
||||||
|
$response = $this->user_manager->reset_password("john@example.com", "incorrect-token", "new-password");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
self::assertFalse($this->user_manager->check_login("john@example.com", "new-password")[0]->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_reset_password_does_not_change_the_password_and_returns_an_unsatisfied_response_if_the_token_is_expired(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_password_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_PASSWORD_RESETS + 1)
|
||||||
|
);
|
||||||
|
$this->user_manager->send_password_reset("john@example.com");
|
||||||
|
$this->set_password_token_timestamp("john@example.com", "1640995200");
|
||||||
|
$token = $this->get_password_token("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->reset_password("john@example.com", $token, "new-password");
|
||||||
|
|
||||||
|
self::assertFalse($response->satisfied);
|
||||||
|
self::assertFalse($this->user_manager->check_login("john@example.com", "new-password")[0]->satisfied);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_reset_password_changes_the_password_if_the_verification_token_is_valid(): void
|
||||||
|
{
|
||||||
|
$this->user_manager->register_user("john@example.com", "password");
|
||||||
|
$this->set_password_token_timestamp(
|
||||||
|
"john@example.com",
|
||||||
|
time() - 60 * (UserManager::MINUTES_BETWEEN_PASSWORD_RESETS + 1)
|
||||||
|
);
|
||||||
|
$this->user_manager->send_password_reset("john@example.com");
|
||||||
|
$token = $this->get_password_token("john@example.com");
|
||||||
|
|
||||||
|
$response = $this->user_manager->reset_password("john@example.com", $token, "new-password");
|
||||||
|
|
||||||
|
self::assertTrue($response->satisfied);
|
||||||
|
self::assertNull($this->get_password_token("john@example.com"));
|
||||||
|
self::assertTrue($this->user_manager->check_login("john@example.com", "new-password")[0]->satisfied);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class IsEmailRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["email" => "example@example.com"], "email");
|
$is_valid = $rule->check(["email" => "example@example.com"], "email");
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_response_message_if_email_is_invalid(): void
|
public function test_returns_response_message_if_email_is_invalid(): void
|
||||||
|
@ -39,7 +39,7 @@ class IsEmailRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["email" => "example.com"], "email");
|
$is_valid = $rule->check(["email" => "example.com"], "email");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
$this->assertEquals("Enter a valid email address.", $is_valid->payload["message"]);
|
self::assertEquals("Enter a valid email address.", $is_valid->payload["message"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class IsNotBlankRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => "not-blank"], "input");
|
$is_valid = $rule->check(["input" => "not-blank"], "input");
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_response_message_if_input_is_the_empty_string(): void
|
public function test_returns_response_message_if_input_is_the_empty_string(): void
|
||||||
|
@ -39,8 +39,8 @@ class IsNotBlankRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => ""], "input");
|
$is_valid = $rule->check(["input" => ""], "input");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
$this->assertEquals("Use at least one character.", $is_valid->payload["message"]);
|
self::assertEquals("Use at least one character.", $is_valid->payload["message"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_response_message_if_input_contains_whitespace_only(): void
|
public function test_returns_response_message_if_input_contains_whitespace_only(): void
|
||||||
|
@ -49,7 +49,7 @@ class IsNotBlankRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => " "], "input");
|
$is_valid = $rule->check(["input" => " "], "input");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
$this->assertEquals("Use at least one character.", $is_valid->payload["message"]);
|
self::assertEquals("Use at least one character.", $is_valid->payload["message"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class LengthRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => "a"], "input");
|
$is_valid = $rule->check(["input" => "a"], "input");
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_null_if_input_is_exactly_maximum_length(): void
|
public function test_returns_null_if_input_is_exactly_maximum_length(): void
|
||||||
|
@ -39,7 +39,7 @@ class LengthRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => "123"], "input");
|
$is_valid = $rule->check(["input" => "123"], "input");
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_null_if_input_is_strictly_inside_range(): void
|
public function test_returns_null_if_input_is_strictly_inside_range(): void
|
||||||
|
@ -48,7 +48,7 @@ class LengthRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => "12"], "input");
|
$is_valid = $rule->check(["input" => "12"], "input");
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_not_null_if_input_is_strictly_below_minimum(): void
|
public function test_returns_not_null_if_input_is_strictly_below_minimum(): void
|
||||||
|
@ -57,7 +57,7 @@ class LengthRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => ""], "input");
|
$is_valid = $rule->check(["input" => ""], "input");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_not_null_if_input_is_strictly_above_maximum(): void
|
public function test_returns_not_null_if_input_is_strictly_above_maximum(): void
|
||||||
|
@ -66,6 +66,6 @@ class LengthRuleTest extends RuleTest
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => "1234"], "input");
|
$is_valid = $rule->check(["input" => "1234"], "input");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ abstract class RuleTest extends TestCase
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => $this->get_valid_input()], "input");
|
$is_valid = $rule->check(["input" => $this->get_valid_input()], "input");
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_not_null_if_input_is_invalid(): void
|
public function test_returns_not_null_if_input_is_invalid(): void
|
||||||
|
@ -46,7 +46,7 @@ abstract class RuleTest extends TestCase
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => $this->get_invalid_input()], "input");
|
$is_valid = $rule->check(["input" => $this->get_invalid_input()], "input");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_not_null_if_input_is_not_set(): void
|
public function test_returns_not_null_if_input_is_not_set(): void
|
||||||
|
@ -55,7 +55,7 @@ abstract class RuleTest extends TestCase
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => $this->get_valid_input()], "does_not_exist");
|
$is_valid = $rule->check(["input" => $this->get_valid_input()], "does_not_exist");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_unsatisfied_payload_if_input_is_invalid(): void
|
public function test_returns_unsatisfied_payload_if_input_is_invalid(): void
|
||||||
|
@ -64,8 +64,8 @@ abstract class RuleTest extends TestCase
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => $this->get_invalid_input()], "input");
|
$is_valid = $rule->check(["input" => $this->get_invalid_input()], "input");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
$this->assertFalse($is_valid->satisfied);
|
self::assertFalse($is_valid->satisfied);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_payload_with_overridden_message_if_input_is_invalid(): void
|
public function test_returns_payload_with_overridden_message_if_input_is_invalid(): void
|
||||||
|
@ -75,8 +75,8 @@ abstract class RuleTest extends TestCase
|
||||||
|
|
||||||
$is_valid = $rule->check(["input" => $this->get_invalid_input()], "input");
|
$is_valid = $rule->check(["input" => $this->get_invalid_input()], "input");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
$this->assertEquals($override, $is_valid->payload["message"]);
|
self::assertEquals($override, $is_valid->payload["message"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_returns_payload_with_key_of_invalid_input_if_input_is_invalid(): void
|
public function test_returns_payload_with_key_of_invalid_input_if_input_is_invalid(): void
|
||||||
|
@ -92,7 +92,7 @@ abstract class RuleTest extends TestCase
|
||||||
"invalid"
|
"invalid"
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
$this->assertEquals("invalid", $is_valid->payload["target"]);
|
self::assertEquals("invalid", $is_valid->payload["target"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_inputs_returns_null_if_all_inputs_are_valid(): void
|
function test_validate_inputs_returns_null_if_all_inputs_are_valid(): void
|
||||||
|
@ -30,7 +30,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_inputs_considers_empty_rule_set_to_always_be_valid(): void
|
function test_validate_inputs_considers_empty_rule_set_to_always_be_valid(): void
|
||||||
|
@ -40,7 +40,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_inputs_returns_not_null_if_at_least_one_rule_is_violated(): void
|
function test_validate_inputs_returns_not_null_if_at_least_one_rule_is_violated(): void
|
||||||
|
@ -53,7 +53,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_inputs_returns_first_violation_if_multiple_inputs_are_invalid(): void
|
function test_validate_inputs_returns_first_violation_if_multiple_inputs_are_invalid(): void
|
||||||
|
@ -66,8 +66,8 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
$this->assertEquals("among", $is_valid->payload["target"]);
|
self::assertEquals("among", $is_valid->payload["target"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_inputs_returns_first_violation_if_one_input_is_invalid_in_multiple_ways(): void
|
function test_validate_inputs_returns_first_violation_if_one_input_is_invalid_in_multiple_ways(): void
|
||||||
|
@ -80,9 +80,9 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
$is_valid = Validator::validate_inputs($inputs, $rule_sets);
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
$this->assertEquals("among", $is_valid->payload["target"]);
|
self::assertEquals("among", $is_valid->payload["target"]);
|
||||||
$this->assertEquals("Field 'among' required.", $is_valid->payload["message"]);
|
self::assertEquals("Field 'among' required.", $is_valid->payload["message"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_logged_in($session);
|
$is_valid = Validator::validate_logged_in($session);
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_logged_in_returns_not_null_if_uuid_is_not_set(): void
|
function test_validate_logged_in_returns_not_null_if_uuid_is_not_set(): void
|
||||||
|
@ -101,7 +101,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_logged_in($session);
|
$is_valid = Validator::validate_logged_in($session);
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_logged_out($session);
|
$is_valid = Validator::validate_logged_out($session);
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_logged_out_returns_null_if_uuid_is_not_set(): void
|
function test_validate_logged_out_returns_null_if_uuid_is_not_set(): void
|
||||||
|
@ -120,7 +120,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_logged_out($session);
|
$is_valid = Validator::validate_logged_out($session);
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_token($array, $token);
|
$is_valid = Validator::validate_token($array, $token);
|
||||||
|
|
||||||
$this->assertNull($is_valid);
|
self::assertNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_token_returns_not_null_if_token_is_not_set(): void
|
function test_validate_token_returns_not_null_if_token_is_not_set(): void
|
||||||
|
@ -141,7 +141,7 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_token($array, $token);
|
$is_valid = Validator::validate_token($array, $token);
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_validate_token_returns_not_null_if_token_does_not_equal_input(): void
|
function test_validate_token_returns_not_null_if_token_does_not_equal_input(): void
|
||||||
|
@ -151,6 +151,6 @@ class ValidatorTest extends TestCase
|
||||||
|
|
||||||
$is_valid = Validator::validate_token($array, "woof");
|
$is_valid = Validator::validate_token($array, "woof");
|
||||||
|
|
||||||
$this->assertNotNull($is_valid);
|
self::assertNotNull($is_valid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue