Add tests for EmailQueue

This commit is contained in:
Florine W. Dekker 2022-12-17 18:05:43 +01:00
parent 096a76a782
commit e090eb3bac
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
21 changed files with 230 additions and 52 deletions

View File

@ -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"

View File

@ -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
```

View File

@ -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",

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.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",

View File

@ -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>

View File

@ -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;");

View File

@ -65,7 +65,7 @@ class ProcessEmailQueueAction extends Action
$mailer->clearAddresses();
}
$this->email_queue->unqueue_emails($emails);
$this->email_queue->unqueue($emails);
return null;
}

View File

@ -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;
}
}

View File

@ -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)
]);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -163,6 +163,6 @@ class RedirectsTest extends TestCase
foreach ($redirects as $from => $to)
$extracted[$from] = $to;
self::assertTrue($extracted === $inserted);
self::assertSame($extracted, $inserted);
}
}

View File

@ -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;