Add tests for EmailQueue
This commit is contained in:
parent
096a76a782
commit
e090eb3bac
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "0.19.13", "_comment_version": "Also update version in `composer.json`!",
|
||||
"version": "0.19.14", "_comment_version": "Also update version in `composer.json`!",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -348,6 +348,7 @@
|
|||
<label for="update-password-password-new">New password</label>
|
||||
<input id="update-password-password-new" type="password" name="password_new"
|
||||
autocomplete="new-password" />
|
||||
<!-- TODO: Add password strength indicator -->
|
||||
<small id="update-password-password-new-hint" data-hint-for="update-password-password-new"
|
||||
data-hint="Use at least 8 characters."></small>
|
||||
|
||||
|
|
|
@ -56,6 +56,19 @@ class EmailQueue
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all queued emails.
|
||||
*
|
||||
* @return array<array{"type_key": string, "recipient": string, "subject": string, "body": string}> 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<Email> $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<array{"type_key": string, "recipient": string, "subject": string, "body": string}> 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<array{"type_key": string, "recipient": string}> $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;");
|
||||
|
|
|
@ -65,7 +65,7 @@ class ProcessEmailQueueAction extends Action
|
|||
|
||||
$mailer->clearAddresses();
|
||||
}
|
||||
$this->email_queue->unqueue_emails($emails);
|
||||
$this->email_queue->unqueue($emails);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
]);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\mailer;
|
||||
|
||||
use com\fwdekker\deathnotifier\Database;
|
||||
use com\fwdekker\deathnotifier\DatabaseTestCase;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see Config}.
|
||||
*/
|
||||
class EmailQueueTest extends DatabaseTestCase
|
||||
{
|
||||
public function setUpWithDatabase(Database $database): void
|
||||
{
|
||||
parent::setUpWithDatabase($database);
|
||||
|
||||
$this->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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\mailer;
|
||||
|
||||
|
||||
/**
|
||||
* A simple plain {@see Email}, only for tests.
|
||||
*/
|
||||
class TestEmail extends Email
|
||||
{
|
||||
private readonly string $subject;
|
||||
private readonly string $body;
|
||||
|
||||
|
||||
public function __construct(string $type_key = "key", string $recipient = "recipient", string $subject = "subject",
|
||||
string $body = "body")
|
||||
{
|
||||
parent::__construct($type_key, $recipient);
|
||||
|
||||
$this->subject = $subject;
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
|
||||
public function get_subject(): string
|
||||
{
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
public function get_body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
}
|
|
@ -163,6 +163,6 @@ class RedirectsTest extends TestCase
|
|||
foreach ($redirects as $from => $to)
|
||||
$extracted[$from] = $to;
|
||||
|
||||
self::assertTrue($extracted === $inserted);
|
||||
self::assertSame($extracted, $inserted);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue