From e090eb3bac114921e144a96c86b82d0132428ff9 Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Sat, 17 Dec 2022 18:05:43 +0100 Subject: [PATCH] Add tests for EmailQueue --- Gruntfile.js | 4 +- README.md | 4 +- composer.json | 2 +- composer.lock | Bin 77391 -> 77391 bytes package-lock.json | Bin 226176 -> 226176 bytes package.json | 2 +- src/main/index.html | 1 + .../deathnotifier/mailer/EmailQueue.php | 30 ++-- .../mailer/ProcessEmailQueueAction.php | 2 +- .../tracking/UpdateTrackingsAction.php | 2 +- .../deathnotifier/user/ChangeEmailAction.php | 2 +- .../user/ChangePasswordAction.php | 2 +- .../deathnotifier/user/RegisterAction.php | 2 +- .../user/ResendVerifyEmailAction.php | 2 +- .../user/ResetPasswordAction.php | 2 +- .../user/SendPasswordResetAction.php | 2 +- .../deathnotifier/DatabaseTestCase.php | 50 ++++--- .../deathnotifier/mailer/EmailQueueTest.php | 132 ++++++++++++++++++ .../deathnotifier/mailer/TestEmail.php | 34 +++++ .../deathnotifier/wikipedia/RedirectsTest.php | 2 +- .../deathnotifier/wikipedia/WikipediaTest.php | 5 +- 21 files changed, 230 insertions(+), 52 deletions(-) create mode 100644 src/test/php/com/fwdekker/deathnotifier/mailer/EmailQueueTest.php create mode 100644 src/test/php/com/fwdekker/deathnotifier/mailer/TestEmail.php diff --git a/Gruntfile.js b/Gruntfile.js index edd64de..8433fc9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,8 @@ const path = require("path"); module.exports = grunt => { + const testTarget = grunt.option("test-target") || "."; + grunt.initConfig({ pkg: grunt.file.readJSON("package.json"), clean: { @@ -69,7 +71,7 @@ module.exports = grunt => { exec: "composer.phar install --no-dev" }, phpunit: { - exec: "cd dist/ && chmod +x .vendor/bin/phpunit && .vendor/bin/phpunit --testdox ." + exec: `cd dist/ && chmod +x .vendor/bin/phpunit && .vendor/bin/phpunit --testdox ${testTarget}` }, stan: { exec: "vendor/bin/phpstan analyse -l 8 src/main src/test" diff --git a/README.md b/README.md index 7874bdb..823af40 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,10 @@ $> npm install ```shell script # Run static analysis $> npm run analyze -# Run tests +# Run all tests $> npm run test +# Run all tests in package +$> npm run test -- --test-target=com/fwdekker/deathnotifier/wikipedia/ # Run static analysis and tests $> npm run check ``` diff --git a/composer.json b/composer.json index a5ce7f2..5d8ef61 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "fwdekker/death-notifier", "description": "Get notified when a famous person dies.", - "version": "0.19.13", "_comment_version": "Also update version in `package.json`!", + "version": "0.19.14", "_comment_version": "Also update version in `package.json`!", "type": "project", "license": "MIT", "homepage": "https://git.fwdekker.com/tools/death-notifier", diff --git a/composer.lock b/composer.lock index 3332fa70e5e1fb67c38799b1825248a47748e976..dc5ca96e15015baaf430d3f6204e46c89b9a911a 100644 GIT binary patch delta 229 zcmX?qhvobomJPy;3MuA^M#-tk7ReTgmWgJDiK!_@hNj8M=4O_uNy*6;2Aho;?V~48 zY)G5DFNuBf{TN-16a!{>p@~_Nd9r1Sp}C2Pg;|n?Nt#JYvYBC$L7LHI z#<1GS8)L=Ph|@6n;WM$x7s7anSDv(aMw}7z delta 236 zcmX?qhvobomJPy;3KnLTX2z)|hGu3)2FA%Ism6(>X2ywzMoGyADM@JtMw^Wp?V~4e z>`0ruFNuBf{TN*ha|25tNU}6EF|kN7Ni#}HGB8g`Oap?{lw=DF%OqoNew password + diff --git a/src/main/php/com/fwdekker/deathnotifier/mailer/EmailQueue.php b/src/main/php/com/fwdekker/deathnotifier/mailer/EmailQueue.php index 85d56a9..1e1da77 100644 --- a/src/main/php/com/fwdekker/deathnotifier/mailer/EmailQueue.php +++ b/src/main/php/com/fwdekker/deathnotifier/mailer/EmailQueue.php @@ -56,6 +56,19 @@ class EmailQueue } + /** + * Returns all queued emails. + * + * @return array all queued + * emails + */ + public function get_queue(): array + { + $stmt = $this->database->conn->prepare("SELECT type_key, recipient, subject, body FROM email_tasks;"); + $stmt->execute(); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + /** * Adds {@see Email Emails} to the queue. * @@ -66,7 +79,7 @@ class EmailQueue * @param array $emails the emails to queue * @return void */ - public function queue_emails(array $emails): void + public function enqueue(array $emails): void { $stmt = $this->database->conn->prepare("INSERT OR REPLACE INTO email_tasks (type_key, recipient, subject, body) VALUES (:type_key, :recipient, :subject, :body);"); @@ -85,26 +98,13 @@ class EmailQueue } } - /** - * Returns all queued emails. - * - * @return array all queued - * emails - */ - public function get_queue(): array - { - $stmt = $this->database->conn->prepare("SELECT type_key, recipient, subject, body FROM email_tasks;"); - $stmt->execute(); - return $stmt->fetchAll(PDO::FETCH_ASSOC); - } - /** * Removes mails from the queue. * * @param array $emails the emails to remove from the queue * @return void */ - public function unqueue_emails(array $emails): void + public function unqueue(array $emails): void { $stmt = $this->database->conn->prepare("DELETE FROM email_tasks WHERE type_key=:type_key AND recipient=:recipient;"); diff --git a/src/main/php/com/fwdekker/deathnotifier/mailer/ProcessEmailQueueAction.php b/src/main/php/com/fwdekker/deathnotifier/mailer/ProcessEmailQueueAction.php index 47e8074..53681a0 100644 --- a/src/main/php/com/fwdekker/deathnotifier/mailer/ProcessEmailQueueAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/mailer/ProcessEmailQueueAction.php @@ -65,7 +65,7 @@ class ProcessEmailQueueAction extends Action $mailer->clearAddresses(); } - $this->email_queue->unqueue_emails($emails); + $this->email_queue->unqueue($emails); return null; } diff --git a/src/main/php/com/fwdekker/deathnotifier/tracking/UpdateTrackingsAction.php b/src/main/php/com/fwdekker/deathnotifier/tracking/UpdateTrackingsAction.php index ee2393f..558b640 100644 --- a/src/main/php/com/fwdekker/deathnotifier/tracking/UpdateTrackingsAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/tracking/UpdateTrackingsAction.php @@ -133,7 +133,7 @@ class UpdateTrackingsAction extends Action $emails[] = new NotifyStatusChangedEmail($user_email, $person_name, $person_status->value); } - $this->mailer->queue_emails($emails); + $this->mailer->enqueue($emails); return null; } } diff --git a/src/main/php/com/fwdekker/deathnotifier/user/ChangeEmailAction.php b/src/main/php/com/fwdekker/deathnotifier/user/ChangeEmailAction.php index 16001ff..4a679a0 100644 --- a/src/main/php/com/fwdekker/deathnotifier/user/ChangeEmailAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/user/ChangeEmailAction.php @@ -79,7 +79,7 @@ class ChangeEmailAction extends Action throw new InvalidValueException("That email address is already in use by someone else.", "email"); $token = $this->user_list->set_email($_SESSION["uuid"], $inputs["email"]); - $this->email_queue->queue_emails([ + $this->email_queue->enqueue([ new ChangeEmailFromEmail($user_data["email"], $inputs["email"]), new ChangeEmailToEmail($inputs["email"], $user_data["email"], $token) ]); diff --git a/src/main/php/com/fwdekker/deathnotifier/user/ChangePasswordAction.php b/src/main/php/com/fwdekker/deathnotifier/user/ChangePasswordAction.php index a4d1e24..a1e861f 100644 --- a/src/main/php/com/fwdekker/deathnotifier/user/ChangePasswordAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/user/ChangePasswordAction.php @@ -77,7 +77,7 @@ class ChangePasswordAction extends Action throw new InvalidValueException("Incorrect old password.", "password_old"); $this->user_list->set_password($_SESSION["uuid"], $inputs["password_new"]); - $this->email_queue->queue_emails([new ChangePasswordEmail($user_data["email"])]); + $this->email_queue->enqueue([new ChangePasswordEmail($user_data["email"])]); }); return null; diff --git a/src/main/php/com/fwdekker/deathnotifier/user/RegisterAction.php b/src/main/php/com/fwdekker/deathnotifier/user/RegisterAction.php index 79593a6..7e9a5ce 100644 --- a/src/main/php/com/fwdekker/deathnotifier/user/RegisterAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/user/RegisterAction.php @@ -71,7 +71,7 @@ class RegisterAction extends Action throw new InvalidValueException("That email address already in use.", "email"); $token = $this->user_list->add_user($inputs["email"], $inputs["password"]); - $this->email_queue->queue_emails([new RegisterEmail($inputs["email"], $token)]); + $this->email_queue->enqueue([new RegisterEmail($inputs["email"], $token)]); }); return null; diff --git a/src/main/php/com/fwdekker/deathnotifier/user/ResendVerifyEmailAction.php b/src/main/php/com/fwdekker/deathnotifier/user/ResendVerifyEmailAction.php index f58e79e..a3455b3 100644 --- a/src/main/php/com/fwdekker/deathnotifier/user/ResendVerifyEmailAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/user/ResendVerifyEmailAction.php @@ -82,7 +82,7 @@ class ResendVerifyEmailAction extends Action } $token = $this->user_list->set_email($_SESSION["uuid"], $user_data["email"]); - $this->email_queue->queue_emails([new ResendVerifyEmailEmail($user_data["email"], $token)]); + $this->email_queue->enqueue([new ResendVerifyEmailEmail($user_data["email"], $token)]); }); return null; diff --git a/src/main/php/com/fwdekker/deathnotifier/user/ResetPasswordAction.php b/src/main/php/com/fwdekker/deathnotifier/user/ResetPasswordAction.php index 1e97b86..e87ed35 100644 --- a/src/main/php/com/fwdekker/deathnotifier/user/ResetPasswordAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/user/ResetPasswordAction.php @@ -81,7 +81,7 @@ class ResetPasswordAction extends Action throw new IllegalStateError("User data is `null` despite previous validation."); $this->user_list->set_password($user_data["uuid"], $inputs["password"]); - $this->email_queue->queue_emails([new ResetPasswordEmail($inputs["email"])]); + $this->email_queue->enqueue([new ResetPasswordEmail($inputs["email"])]); }); return null; diff --git a/src/main/php/com/fwdekker/deathnotifier/user/SendPasswordResetAction.php b/src/main/php/com/fwdekker/deathnotifier/user/SendPasswordResetAction.php index 98b0a79..37bf7b2 100644 --- a/src/main/php/com/fwdekker/deathnotifier/user/SendPasswordResetAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/user/SendPasswordResetAction.php @@ -80,7 +80,7 @@ class SendPasswordResetAction extends Action } $token = $this->user_list->register_password_reset($inputs["email"]); - $this->email_queue->queue_emails([new SendPasswordResetEmail($inputs["email"], $token)]); + $this->email_queue->enqueue([new SendPasswordResetEmail($inputs["email"], $token)]); }); return null; diff --git a/src/test/php/com/fwdekker/deathnotifier/DatabaseTestCase.php b/src/test/php/com/fwdekker/deathnotifier/DatabaseTestCase.php index d16b49d..6e81118 100644 --- a/src/test/php/com/fwdekker/deathnotifier/DatabaseTestCase.php +++ b/src/test/php/com/fwdekker/deathnotifier/DatabaseTestCase.php @@ -10,10 +10,11 @@ use PHPUnit\Framework\TestCase; /** - * For tests that use a fully-installed database. + * Provides a fresh database for each test. * - * By default, only the main schema of the database is installed. Override the getters to additionally install the - * corresponding parts of the schema. + * By default, the fields {@see DatabaseTestCase::$email_queue}, {@see DatabaseTestCase::$user_list}, and + * {@see DatabaseTestCase::$tracking_list} are mocks, and hence their `install` methods are not called. To change this, + * override {@see DatabaseTestCase::setUpWithDatabase()}. */ abstract class DatabaseTestCase extends TestCase { @@ -26,31 +27,18 @@ abstract class DatabaseTestCase extends TestCase * @var Database the database */ public Database $database; - - /** - * @return EmailQueue the `EmailQueue` to install the database schema of + * @var EmailQueue the `EmailQueue` to install the database schema of */ - function get_email_queue(): EmailQueue - { - return $this->createStub(EmailQueue::class); - } - + public EmailQueue $email_queue; /** - * @return TrackingList the `TrackingList` to install the database schema of + * @var UserList the `UserList` to install the database schema of */ - function get_tracking_list(): TrackingList - { - return $this->createStub(TrackingList::class); - } - + public UserList $user_list; /** - * @return UserList the `UserList` to install the database schema of + * @var TrackingList the `TrackingList` to install the database schema of */ - function get_user_list(): UserList - { - return $this->createStub(UserList::class); - } + public TrackingList $tracking_list; /** @@ -87,6 +75,22 @@ abstract class DatabaseTestCase extends TestCase if ($db_tmp_file === false) throw new Exception("Failed to create temporary database file."); $this->database = new Database($db_tmp_file); - $this->database->auto_install($this->get_email_queue(), $this->get_user_list(), $this->get_tracking_list()); + $this->setUpWithDatabase($this->database); + $this->database->auto_install($this->email_queue, $this->user_list, $this->tracking_list); + } + + /** + * Creates the {@see EmailQueue}, {@see UserList}, and {@see TrackingList} using {@see $database}. + * + * By default, all three structures are stubs. Override this method to change that. + * + * @param Database $database the `Database` with which to instantiate the structures + * @return void + */ + public function setUpWithDatabase(Database $database): void + { + $this->email_queue = $this->createStub(EmailQueue::class); + $this->user_list = $this->createStub(UserList::class); + $this->tracking_list = $this->createStub(TrackingList::class); } } diff --git a/src/test/php/com/fwdekker/deathnotifier/mailer/EmailQueueTest.php b/src/test/php/com/fwdekker/deathnotifier/mailer/EmailQueueTest.php new file mode 100644 index 0000000..2bcf702 --- /dev/null +++ b/src/test/php/com/fwdekker/deathnotifier/mailer/EmailQueueTest.php @@ -0,0 +1,132 @@ +email_queue = new EmailQueue($database); + } + + + public function test_get_queue_initially_empty(): void + { + self::assertEmpty($this->email_queue->get_queue()); + } + + public function test_get_queue_returns_added_emails(): void + { + $this->email_queue->enqueue([ + new TestEmail("key1", "recipient1", "subject1", "body1"), + new TestEmail("key2", "recipient2", "subject2", "body2"), + ]); + + self::assertSame( + [ + ["type_key" => "key1", "recipient" => "recipient1", "subject" => "subject1", "body" => "body1"], + ["type_key" => "key2", "recipient" => "recipient2", "subject" => "subject2", "body" => "body2"], + ], + $this->email_queue->get_queue() + ); + } + + + public function test_enqueue_adds_emails(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key1"), new TestEmail(type_key: "key2")]); + + self::assertSame(["key1", "key2"], array_column($this->email_queue->get_queue(), "type_key")); + } + + public function test_enqueue_appends_emails(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key1"), new TestEmail(type_key: "key2")]); + + $this->email_queue->enqueue([new TestEmail(type_key: "key3"), new TestEmail(type_key: "key4")]); + + self::assertSame(["key1", "key2", "key3", "key4"], array_column($this->email_queue->get_queue(), "type_key")); + } + + public function test_enqueue_appends_email_if_only_type_key_is_different(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key1", recipient: "recipient")]); + + $this->email_queue->enqueue([new TestEmail(type_key: "key2", recipient: "recipient")]); + + $queue = $this->email_queue->get_queue(); + self::assertSame(["key1", "key2"], array_column($queue, "type_key")); + self::assertSame(["recipient", "recipient"], array_column($queue, "recipient")); + } + + public function test_enqueue_appends_email_if_only_recipient_is_different(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key", recipient: "recipient1")]); + + $this->email_queue->enqueue([new TestEmail(type_key: "key", recipient: "recipient2")]); + + $queue = $this->email_queue->get_queue(); + self::assertSame(["key", "key"], array_column($queue, "type_key")); + self::assertSame(["recipient1", "recipient2"], array_column($queue, "recipient")); + } + + public function test_enqueue_replaces_email_if_both_type_key_and_recipient_are_same(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key", recipient: "recipient", subject: "old_subject")]); + + $this->email_queue->enqueue([new TestEmail(type_key: "key", recipient: "recipient", subject: "new_subject")]); + + $queue = $this->email_queue->get_queue(); + self::assertSame(["key"], array_column($queue, "type_key")); + self::assertSame(["recipient"], array_column($queue, "recipient")); + self::assertSame(["new_subject"], array_column($queue, "subject")); + } + + + public function test_unqueue_does_not_remove_if_both_type_key_and_recipient_are_different(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key1", recipient: "recipient1")]); + + $this->email_queue->unqueue([["type_key" => "key2", "recipient" => "recipient2"]]); + + $queue = $this->email_queue->get_queue(); + self::assertSame(["key1"], array_column($queue, "type_key")); + self::assertSame(["recipient1"], array_column($queue, "recipient")); + } + + public function test_unqueue_does_not_remove_if_type_key_is_different(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key1", recipient: "recipient")]); + + $this->email_queue->unqueue([["type_key" => "key2", "recipient" => "recipient"]]); + + self::assertSame(["key1"], array_column($this->email_queue->get_queue(), "type_key")); + } + + public function test_unqueue_does_not_remove_if_recipient_is_different(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key", recipient: "recipient1")]); + + $this->email_queue->unqueue([["type_key" => "key", "recipient" => "recipient2"]]); + + self::assertSame(["recipient1"], array_column($this->email_queue->get_queue(), "recipient")); + } + + public function test_unqueue_removes_email_with_same_key_and_recipient(): void + { + $this->email_queue->enqueue([new TestEmail(type_key: "key", recipient: "recipient")]); + + $this->email_queue->unqueue([["type_key" => "key", "recipient" => "recipient"]]); + + self::assertEmpty($this->email_queue->get_queue()); + } +} diff --git a/src/test/php/com/fwdekker/deathnotifier/mailer/TestEmail.php b/src/test/php/com/fwdekker/deathnotifier/mailer/TestEmail.php new file mode 100644 index 0000000..c691c63 --- /dev/null +++ b/src/test/php/com/fwdekker/deathnotifier/mailer/TestEmail.php @@ -0,0 +1,34 @@ +subject = $subject; + $this->body = $body; + } + + + public function get_subject(): string + { + return $this->subject; + } + + public function get_body(): string + { + return $this->body; + } +} diff --git a/src/test/php/com/fwdekker/deathnotifier/wikipedia/RedirectsTest.php b/src/test/php/com/fwdekker/deathnotifier/wikipedia/RedirectsTest.php index bc6782f..15fc873 100644 --- a/src/test/php/com/fwdekker/deathnotifier/wikipedia/RedirectsTest.php +++ b/src/test/php/com/fwdekker/deathnotifier/wikipedia/RedirectsTest.php @@ -163,6 +163,6 @@ class RedirectsTest extends TestCase foreach ($redirects as $from => $to) $extracted[$from] = $to; - self::assertTrue($extracted === $inserted); + self::assertSame($extracted, $inserted); } } diff --git a/src/test/php/com/fwdekker/deathnotifier/wikipedia/WikipediaTest.php b/src/test/php/com/fwdekker/deathnotifier/wikipedia/WikipediaTest.php index 4fd1823..9a53d1f 100644 --- a/src/test/php/com/fwdekker/deathnotifier/wikipedia/WikipediaTest.php +++ b/src/test/php/com/fwdekker/deathnotifier/wikipedia/WikipediaTest.php @@ -6,13 +6,16 @@ use PHPUnit\Framework\TestCase; /** - * Unit tests for {@see Wikipedia}. + * Integration tests for {@see Wikipedia}. * * These tests are slow because they directly communicate with Wikipedia. No mocking going on here. This also means that * in several years, some of these tests will fail. */ class WikipediaTest extends TestCase { + /** + * @var Wikipedia the `Wikipedia` instance to test on + */ private Wikipedia $wikipedia;