Add setup for database-based tests

This commit is contained in:
Florine W. Dekker 2022-11-27 13:54:13 +01:00
parent 4bca46fb97
commit 379498cfe0
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
17 changed files with 200 additions and 36 deletions

View File

@ -1,7 +1,8 @@
{
"name": "fwdekker/death-notifier",
"description": "Get notified when a famous person dies.",
"version": "0.14.7", "_comment_version": "Also update version in `package.json`!",
"version": "0.14.9",
"_comment_version": "Also update version in `package.json`!",
"type": "project",
"license": "MIT",
"homepage": "https://git.fwdekker.com/tools/death-notifier",
@ -23,14 +24,10 @@
"phpmailer/phpmailer": "^6.6"
},
"require-dev": {
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^9.5"
"phpstan/phpstan": "^1.9.2",
"phpunit/phpunit": "^9.5.26"
},
"autoload": {
"classmap": [
"src/main/php/",
"src/test/php/"
],
"psr-4": {
"php\\": "php/"
}

BIN
composer.lock generated

Binary file not shown.

BIN
package-lock.json generated

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "death-notifier",
"version": "0.14.8", "_comment_version": "Also update version in `composer.json`!",
"version": "0.14.9", "_comment_version": "Also update version in `composer.json`!",
"description": "Get notified when a famous person dies.",
"author": "Florine W. Dekker",
"browser": "dist/bundle.js",

View File

@ -26,7 +26,7 @@ $db = new Database($logger->withName("Database"), $config["database"]["filename"
$mailer = new Mailer($config, $logger->withName("Mailer"), $db->conn);
$mediawiki = new Mediawiki($logger->withName("Mediawiki"));
$user_manager = new UserManager($db->conn, $mailer);
$user_manager = new UserManager($logger->withName("UserManager"), $db->conn, $mailer);
$tracking_manager = new TrackingManager($logger->withName("TrackingManager"), $db->conn, $mailer, $mediawiki);
$db->auto_install($mailer, $user_manager, $tracking_manager);

View File

@ -138,7 +138,7 @@ class Database
* Migrates the database from a previous version to one compatible with v0.8.0.
*
* @return void
* @noinspection SqlResolve Function necessarily refers to old scheme which is not detected by tools
* @noinspection SqlResolve Function necessarily refers to old schema which is not detected by tools
*/
private function migrate_0_8_0(): void
{
@ -166,7 +166,7 @@ class Database
* Migrates the database from a previous version to one compatible with v0.10.0.
*
* @return void
* @noinspection SqlResolve Function necessarily refers to old scheme which is not detected by tools
* @noinspection SqlResolve Function necessarily refers to old schema which is not detected by tools
*/
private function migrate_0_10_0(): void
{

View File

@ -2,9 +2,10 @@
namespace php;
use Exception;
use Monolog\Logger;
use PDO;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\Exception as MailerException;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
@ -98,7 +99,7 @@ class Mailer
$mailer->Password = $this->config["mail"]["password"];
try {
$mailer->setFrom($this->config["mail"]["username"], $this->config["mail"]["from_name"]);
} catch (Exception $exception) {
} catch (MailerException $exception) {
$this->logger->error("Failed to set 'from' address while processing queue.", ["cause" => $exception]);
$mailer->smtpClose();
}
@ -124,7 +125,7 @@ class Mailer
try {
$mailer->addAddress($recipient);
$mailer->send();
} catch (Exception $exception) {
} catch (MailerException $exception) {
$this->logger->error(
"Failed to send mail.",
["cause" => $exception, "email" => $email]
@ -133,7 +134,7 @@ class Mailer
}
$mailer->clearAddresses();
} catch (\Exception $exception) {
} catch (Exception $exception) {
$this->logger->error(
"Failed to send mail.",
["cause" => $exception]
@ -199,7 +200,7 @@ abstract class Email
* @param string $arg1 the first argument to construct the email
* @param string $arg2 the second argument to construct the email
* @return Email a deserialized email
* @throws \Exception if the `type` is not recognized
* @throws Exception if the `type` is not recognized
*/
public static function deserialize(string $type, string $recipient, string $arg1, string $arg2): Email
{
@ -212,7 +213,7 @@ abstract class Email
NotifyArticleDeletedEmail::TYPE => new NotifyArticleDeletedEmail($recipient, $arg1),
NotifyArticleUndeletedEmail::TYPE => new NotifyArticleUndeletedEmail($recipient, $arg1),
NotifyStatusChangedEmail::TYPE => new NotifyStatusChangedEmail($recipient, $arg1, $arg2),
default => throw new \Exception("Unknown email type $type."),
default => throw new Exception("Unknown email type $type."),
};
}
}
@ -386,7 +387,8 @@ class ChangedEmailEmail extends Email
/**
* An email informing a user that their password has been changed.
*/
class ChangedPasswordEmail extends Email {
class ChangedPasswordEmail extends Email
{
/**
* A string identifying the type of email.
*/
@ -483,7 +485,8 @@ class ResetPasswordEmail extends Email
/**
* An email to inform a user that a tracked article has been deleted.
*/
class NotifyArticleDeletedEmail extends Email {
class NotifyArticleDeletedEmail extends Email
{
/**
* A string identifying the type of email.
*/
@ -537,7 +540,8 @@ class NotifyArticleDeletedEmail extends Email {
/**
* An email to inform a user that a tracked article has been re-created.
*/
class NotifyArticleUndeletedEmail extends Email {
class NotifyArticleUndeletedEmail extends Email
{
/**
* A string identifying the type of email.
*/

View File

@ -2,6 +2,7 @@
namespace php;
use Exception;
use Monolog\Logger;
use PDO;
@ -97,7 +98,7 @@ class TrackingManager
{
try {
$info = $this->mediawiki->query_page_info([$person_name]);
} catch (\Exception $exception) {
} catch (Exception $exception) {
$this->logger->error("Failed to query page info.", ["cause" => $exception, "name" => $person_name]);
return Response::unsatisfied("Could not reach Wikipedia. Maybe the website is down?");
}
@ -254,7 +255,7 @@ class TrackingManager
try {
$people_statuses = $this->mediawiki->query_page_info($names);
} catch (\Exception $exception) {
} catch (Exception $exception) {
$this->logger->error("Failed to retrieve page information.", ["cause" => $exception, "pages" => $names]);
return;
}

View File

@ -37,6 +37,11 @@ class UserManager
*/
public const MINUTES_VALID_PASSWORD_RESET = 60;
/**
* @var Logger The logger to use for logging.
*/
private Logger $logger; // @phpstan-ignore-line Unused, but useful for debugging
/**
* @var PDO The database connection to interact with.
*/
@ -50,11 +55,13 @@ class UserManager
/**
* Constructs a new user manager.
*
* @param Logger $logger the logger to use for logging
* @param PDO $conn the database connection to interact with
* @param Mailer $mailer the mailer to send emails with
*/
public function __construct(PDO $conn, Mailer $mailer)
public function __construct(Logger $logger, PDO $conn, Mailer $mailer)
{
$this->logger = $logger;
$this->conn = $conn;
$this->mailer = $mailer;
}

View File

@ -0,0 +1,96 @@
<?php
namespace php;
use Exception;
use Monolog\Logger;
use Monolog\Test\TestCase;
/**
* For tests that use a fully-installed database.
*
* By default, only the main schema of the database is installed. Override the getters to additionally install the
* corresponding parts of the schema.
*/
abstract class DatabaseTestCase extends TestCase
{
/**
* @var string the temporary directory to store database files in
*/
private static string $db_tmp_dir;
/**
* @var Logger the logger
*/
public Logger $logger;
/**
* @var Database the database
*/
public Database $database;
/**
* @return Mailer the `Mailer` to install the database schema of
*/
function getMailer(): Mailer
{
return $this->createStub(Mailer::class);
}
/**
* @return TrackingManager the `TrackingManager` to install the database schema of
*/
function getTrackingManager(): TrackingManager
{
return $this->createStub(TrackingManager::class);
}
/**
* @return UserManager the `UserManager` to install the database schema of
*/
function getUserManager(): UserManager
{
return $this->createStub(UserManager::class);
}
/**
* Sets up a directory for temporary files.
*
* @throws Exception if the directory could not be created
*/
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
self::$db_tmp_dir = sys_get_temp_dir() . "/DatabaseTestCase-" . rand();
if (!mkdir(self::$db_tmp_dir))
throw new Exception("Failed to create temporary directory '" . self::$db_tmp_dir . "'.");
register_shutdown_function(function () {
$files = glob(self::$db_tmp_dir . "/*");
if ($files === false) return;
array_map("unlink", $files);
});
}
/**
* Sets up and installs a database.
*
* @throws Exception if the database file could not be created
*/
public function setUp(): void
{
parent::setUp();
$db_tmp_file = tempnam(self::$db_tmp_dir, "database-");
if ($db_tmp_file === false) throw new Exception("Failed to create temporary database file.");
$this->logger = new Logger("DatabaseTestCase");
$this->database = new Database($this->logger, $db_tmp_file);
$this->database->auto_install($this->getMailer(), $this->getUserManager(), $this->getTrackingManager());
}
}

View File

@ -1,7 +1,6 @@
<?php
use php\IsEmailRule;
use php\Rule;
namespace php;
/**

View File

@ -1,7 +1,6 @@
<?php
use php\IsNotBlankRule;
use php\Rule;
namespace php;
/**

View File

@ -1,7 +1,6 @@
<?php
use php\IsSetRule;
use php\Rule;
namespace php;
/**

View File

@ -1,7 +1,6 @@
<?php
use php\LengthRule;
use php\Rule;
namespace php;
/**

View File

@ -1,7 +1,8 @@
<?php
namespace php;
use Monolog\Test\TestCase;
use php\Rule;
/**

View File

@ -0,0 +1,64 @@
<?php
namespace php;
use PHPUnit\Framework\MockObject\MockObject;
/**
* Unit tests for `UserManager`.
*/
class UserManagerTest extends DatabaseTestCase
{
private UserManager $user_manager;
private Mailer&MockObject $mailer;
public function getUserManager(): UserManager
{
return new UserManager($this->logger, $this->database->conn, $this->mailer);
}
public function setUp(): void
{
$this->mailer = $this->createMock(Mailer::class);
parent::setUp();
$this->user_manager = $this->getUserManager();
}
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("test@test.test", "password");
$response = $this->user_manager->register_user("test@test.test", "password");
self::assertFalse($response->satisfied);
}
public function test_register_user_returns_a_satisfied_response_if_the_email_is_unused(): void
{
$this->mailer->method("queue_email")->willReturn(Response::satisfied());
$response = $this->user_manager->register_user("test@test.test", "password");
self::assertTrue($response->satisfied);
}
public function test_register_user_sends_an_email_with_the_verification_token(): void
{
$this->mailer
->expects($this->once())
->method("queue_email")
->with($this->callback(fn($email) => $email instanceof RegisterEmail && $email->recipient === "test@test.test"))
->willReturn(Response::satisfied());
$response = $this->user_manager->register_user("test@test.test", "password");
self::assertTrue($response->satisfied);
}
}

View File

@ -1,10 +1,8 @@
<?php
namespace php;
use Monolog\Test\TestCase;
use php\IsEmailRule;
use php\IsNotBlankRule;
use php\IsSetRule;
use php\Validator;
/**