From 379498cfe09bfab4eb50ced0536a9ff4e99b74a6 Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Sun, 27 Nov 2022 13:54:13 +0100 Subject: [PATCH] Add setup for database-based tests --- composer.json | 11 ++-- composer.lock | Bin 77196 -> 77196 bytes package-lock.json | Bin 226174 -> 226174 bytes package.json | 2 +- src/main/api.php | 2 +- src/main/php/Database.php | 4 +- src/main/php/Mailer.php | 22 ++++--- src/main/php/TrackingManager.php | 5 +- src/main/php/UserManager.php | 9 ++- src/test/php/DatabaseTestCase.php | 96 ++++++++++++++++++++++++++++ src/test/php/IsEmailRuleTest.php | 3 +- src/test/php/IsNotBlankRuleTest.php | 3 +- src/test/php/IsSetRuleTest.php | 3 +- src/test/php/LengthRuleTest.php | 3 +- src/test/php/RuleTest.php | 3 +- src/test/php/UserManagerTest.php | 64 +++++++++++++++++++ src/test/php/ValidatorTest.php | 6 +- 17 files changed, 200 insertions(+), 36 deletions(-) create mode 100644 src/test/php/DatabaseTestCase.php create mode 100644 src/test/php/UserManagerTest.php diff --git a/composer.json b/composer.json index 0c74211..11b9dcb 100644 --- a/composer.json +++ b/composer.json @@ -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/" } diff --git a/composer.lock b/composer.lock index a856651bcb70d4c84f64c9bb8d2c9c9f07ffee58..18f92a6f8d9272672507fa9dac7f85130acadccd 100644 GIT binary patch delta 51 zcmeCV&C+w5WrHxIf`NsFMY3g*nVF@fd0MhzT2hLcp_!?%MWT7KnYoFvQL{1Qc4J1y GNF4x_xDGM^ delta 51 zcmeCV&C+w5WrHxIg1JSig_)V5VWPQ-rKORfg^7WAs(FfKszGv+g_)scTC*|Zc4J1y GNF4x(W)1=X diff --git a/package-lock.json b/package-lock.json index a1042c33a4cd2c4b643c03fec63b10e2489e2e32..3d8d0a1f0e23a8000ec99c298d637152f885186b 100644 GIT binary patch delta 32 qcmV+*0N?-q<_-Sl4UjbfIgvJ&0XdVQ4HJRjh2Q~&-~zSa1DwithName("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); diff --git a/src/main/php/Database.php b/src/main/php/Database.php index 633be4c..09264f3 100644 --- a/src/main/php/Database.php +++ b/src/main/php/Database.php @@ -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 { diff --git a/src/main/php/Mailer.php b/src/main/php/Mailer.php index 6be149d..7a08f78 100644 --- a/src/main/php/Mailer.php +++ b/src/main/php/Mailer.php @@ -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. */ diff --git a/src/main/php/TrackingManager.php b/src/main/php/TrackingManager.php index 022cdcd..0a04333 100644 --- a/src/main/php/TrackingManager.php +++ b/src/main/php/TrackingManager.php @@ -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; } diff --git a/src/main/php/UserManager.php b/src/main/php/UserManager.php index f56514f..6e827ad 100644 --- a/src/main/php/UserManager.php +++ b/src/main/php/UserManager.php @@ -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; } diff --git a/src/test/php/DatabaseTestCase.php b/src/test/php/DatabaseTestCase.php new file mode 100644 index 0000000..de42289 --- /dev/null +++ b/src/test/php/DatabaseTestCase.php @@ -0,0 +1,96 @@ +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()); + } +} diff --git a/src/test/php/IsEmailRuleTest.php b/src/test/php/IsEmailRuleTest.php index 1ca454c..55f2c78 100644 --- a/src/test/php/IsEmailRuleTest.php +++ b/src/test/php/IsEmailRuleTest.php @@ -1,7 +1,6 @@ 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); + } +} diff --git a/src/test/php/ValidatorTest.php b/src/test/php/ValidatorTest.php index 2938711..5c9d8d8 100644 --- a/src/test/php/ValidatorTest.php +++ b/src/test/php/ValidatorTest.php @@ -1,10 +1,8 @@