Merge branch 'MDL-75971-404' of https://github.com/lameze/moodle into MOODLE_404_STABLE

This commit is contained in:
Jun Pataleta
2025-04-03 14:36:47 +08:00
15 changed files with 1031 additions and 42 deletions
+28 -28
View File
@@ -4,7 +4,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
As a user
I need to be able to accept site policy during sign up
Scenario: Accept policy on sign up, no site policy
Scenario: Accept policy on sign up manage by plugin, no site policy
Given the following config values are set as admin:
| registerauth | email |
| passwordpolicy | 0 |
@@ -16,13 +16,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -50,13 +50,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -95,13 +95,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -154,13 +154,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -207,13 +207,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -519,13 +519,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
When I press "Create my new account"
Then I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I follow "Log in"
When I click on "Create new account" "link"
# Confirm that the user can view and accept policies when attempting to create another account.
@@ -641,13 +641,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -710,13 +710,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -852,13 +852,13 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -83,13 +83,13 @@ Feature: Optional policies
And I set the following fields to these values:
| Username | user3 |
| Password | user3 |
| Email address | user3@address.invalid |
| Email (again) | user3@address.invalid |
| Email address | user3@example.com |
| Email (again) | user3@example.com |
| First name | User3 |
| Last name | L3 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user3@address.invalid"
And I should see "An email should have been sent to your address at user3@example.com"
And I confirm email for "user3"
And I should see "Thanks, User3 L3"
And I should see "Your registration has been confirmed"
+8 -8
View File
@@ -15,13 +15,13 @@ Feature: User must accept policy when logging in and signing up
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | student1@example.com |
| Email (again) | student1@example.com |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at student1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -45,14 +45,14 @@ Feature: User must accept policy when logging in and signing up
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| Email address | user1@example.com |
| Email (again) | user1@example.com |
| First name | User1 |
| Last name | L1 |
| I understand and agree | 1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I should see "An email should have been sent to your address at user1@example.com"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
@@ -67,8 +67,8 @@ Feature: User must accept policy when logging in and signing up
Scenario Outline: Email validation during email registration
Given the following config values are set as admin:
| allowaccountssameemail | <allowsameemail> |
| registerauth | email |
| passwordpolicy | 0 |
| registerauth | email |
| passwordpolicy | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| s1 | John | Doe | s1@example.com |
+26
View File
@@ -1694,6 +1694,32 @@ EOF;
return !empty($matches);
}
/**
* Get the user object from an identifier.
*
* The user username and email fields are checked.
*
* @param string $identifier The user's username or email.
* @return stdClass|null The user id or null if not found.
*/
protected function get_user_by_identifier(string $identifier): ?stdClass {
global $DB;
$sql = <<<EOF
SELECT *
FROM {user}
WHERE username = :username
OR email = :email
EOF;
$result = $DB->get_record_sql($sql, [
'username' => $identifier,
'email' => $identifier,
]);
return $result ?: null;
}
/**
* Get the user id from an identifier.
*
+6
View File
@@ -233,6 +233,12 @@ function behat_clean_init_config() {
unset($CFG->{$key});
}
}
// Allow email catcher settings.
if (defined('TEST_EMAILCATCHER_MAIL_SERVER')) {
$CFG->noemailever = false;
$CFG->smtphosts = TEST_EMAILCATCHER_MAIL_SERVER;
}
}
/**
+49
View File
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core\test;
/**
* Generic email catcher interface.
*
* @package core
* @category test
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright Simey Lameze <[email protected]>
*/
interface email_catcher {
/**
* Get a list of messages from the mailpit server.
*
* @param bool $showdetails Optional. Whether to include detailed information in the messages. Default is false.
* @return iterable<message>
*/
public function get_messages(bool $showdetails = false): iterable;
/**
* Delete all messages from the mailpit server.
*/
public function delete_all();
/**
* Search for a message in the mailpit server.
*
* @param string $query The search query.
* @return iterable
*/
public function search(string $query): iterable;
}
+122
View File
@@ -0,0 +1,122 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Mailpit mail handling implementation.
*
* @package core
* @category test
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright Simey Lameze <[email protected]>
*/
namespace core\test;
use core\http_client;
use stdClass;
/**
* Mailpit email handling class.
*
* @package core
* @category test
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mailpit_email_catcher implements email_catcher {
/** @var http_client The http client object. */
protected http_client $httpclient;
/**
* Constructor.
*
* @param string $baseuri The base uri for the mailpit server.
*/
public function __construct(string $baseuri) {
$this->httpclient = new http_client(['base_uri' => $baseuri]);
}
/**
* Reset the mailpit server after a test.
*/
public function reset_after_test(): void {
$this->httpclient->delete_all();
}
/**
* Delete all messages from the mailpit server.
*/
public function delete_all() {
$this->httpclient->delete('api/v1/messages');
}
/**
* Get a list of messages from the mailpit server.
*
* @param bool $showdetails Optional. Whether to include detailed information in the messages. Default is false.
* @return iterable
*/
public function get_messages(bool $showdetails = false): iterable {
$uri = 'api/v1/messages';
$options = [
'query' => [
'start' => 0,
],
];
do {
$response = $this->httpclient->get(
uri: $uri,
options: $options,
);
$data = json_decode($response->getBody());
foreach ($data->messages as $messagedata) {
yield mailpit_message::create_from_api_response($this, $messagedata, $showdetails);
}
$options['query']['start'] = $data->start + $data->count;
} while ($data->total > ($options['query']['start']));
}
/**
* Get the message summary for a specific message.
*
* @param string $id The message id.
* @return stdClass
*/
public function get_message_data(string $id): stdClass {
$response = $this->httpclient->get("api/v1/message/{$id}");
return json_decode($response->getBody());
}
/**
* Search for a message in the mailpit server.
*
* @param string $query The search query.
* @return mixed
*/
public function search(string $query): iterable {
$uri = "api/v1/search?query={$query}";
$response = $this->httpclient->get($uri);
$data = json_decode($response->getBody());
foreach ($data->messages as $messagedata) {
yield mailpit_message::create_from_api_response($this, $messagedata);
}
}
}
+227
View File
@@ -0,0 +1,227 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core\test;
use stdClass;
/**
* Mailpit message handling implementation.
*
* @package core
* @category test
* @copyright Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mailpit_message implements message {
/** @var bool Whether the message has been loaded. */
protected bool $messageloaded = false;
/**
* Constructor.
*
* @param email_catcher $client The email catcher client.
* @param string $id The message ID.
* @param stdClass $sender The sender.
* @param string $subject The subject.
* @param array $recipients The recipients.
* @param array $cc The cc recipients.
* @param array $bcc The bcc recipients.
* @param int $attachmentcount The number of attachments.
* @param string|null $text The text body.
* @param string|null $html The HTML body.
* @param array $attachments The attachments.
* @param array $inline The inline attachments.
*/
protected function __construct(
/** @var email_catcher $client The email catcher client used for message operations.*/
private readonly email_catcher $client,
/** @var string $id The unique identifier for the message. */
private readonly string $id,
/** @var stdClass $sender The sender of the message, represented as an object with email details. */
private readonly stdClass $sender,
/** @var string $subject The subject line of the message. */
private readonly string $subject,
/** @var array $recipients List of primary recipients for the message. */
private readonly array $recipients,
/** @var array $cc List of carbon copy recipients (optional, defaults to an empty array). */
private readonly array $cc = [],
/** @var array $bcc List of blind carbon copy recipients (optional, defaults to an empty array). */
private readonly array $bcc = [],
/** @var int $attachmentcount The number of attachments in the message (default is 0). */
private readonly int $attachmentcount = 0,
/** @var ?string $text The plain text body of the message (nullable, might be loaded later). */
private ?string $text = null,
/** @var ?string $html The HTML body of the message (nullable, might be loaded later). */
private ?string $html = null,
/** @var array $attachments An array of attachment details (defaults to empty array). */
private array $attachments = [],
/** @var array $inline An array of inline elements like images or styles (defaults to empty array). */
private array $inline = [],
) {
}
/**
* Load the message content.
*/
protected function load_message_content(): void {
if (!$this->messageloaded) {
$message = $this->client->get_message_data($this->id);
$this->text = $message->Text;
$this->html = $message->HTML;
$this->attachments = $message->Attachments;
$this->inline = $message->Inline;
$this->messageloaded = true;
}
}
/**
* Create a message from an api response.
*
* @param email_catcher $client The email catcher client.
* @param stdClass $message The api response.
* @param bool $showdetails Optional. Whether to include detailed information in the messages. Default is false.
* @return mailpit_message The created mailpit message instance.
*/
public static function create_from_api_response(
email_catcher $client,
stdClass $message,
bool $showdetails = false,
): self {
$message = new self(
client: $client,
id: $message->ID,
sender: $message->From,
subject: $message->Subject,
recipients: $message->To,
cc: $message->Cc,
attachmentcount: $message->Attachments,
);
if ($showdetails) {
$message->load_message_content();
}
return $message;
}
/**
* Get the text representation of the body, if one was provided.
*
* @return null|string The text body.
*/
public function get_body_text(): ?string {
return $this->text;
}
/**
* Get the HTML representation of the body, if one was provided.
*
* @return null|string The HTML body.
*/
public function get_body_html(): ?string {
return $this->html;
}
/**
* Get the message ID.
*
* @return string The ID.
*/
public function get_id(): string {
return $this->id;
}
/**
* Get the message recipients.
*
* @return iterable The recipients.
*/
public function get_recipients(): iterable {
foreach ($this->recipients as $user) {
yield mailpit_message_user::from_recipient($user);
}
}
/**
* Get the first recipient of the message.
*
* @return string The email address of the first recipient.
*/
public function get_first_recipient(): string {
$recipients = $this->get_recipients();
foreach ($recipients as $recipient) {
return $recipient->get_address();
}
return '';
}
/**
* Whether the message has the specified recipient.
*
* @param string $email The email address.
* @return bool Whether the message has the recipient.
*/
public function has_recipient(string $email): bool {
foreach ($this->get_recipients() as $recipient) {
if ($recipient->get_address() === $email) {
return true;
}
}
return false;
}
/**
* Get the message cc recipients.
*
* @return iterable The cc recipients.
*/
public function get_cc(): iterable {
foreach ($this->cc as $user) {
yield mailpit_message_user::from_recipient($user);
}
}
/**
* Get the message bcc recipients.
*
* @return iterable The bcc recipients.
*/
public function get_bcc(): iterable {
foreach ($this->bcc as $user) {
yield mailpit_message_user::from_recipient($user);
}
}
/**
* Get the message subject.
*
* @return string The subject.
*/
public function get_subject(): string {
return $this->subject;
}
/**
* Get the message sender.
*
* @return message_user The sender.
*/
public function get_sender(): message_user {
return mailpit_message_user::from_sender($this->sender);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core\test;
/**
* Message user interface for Mailpit.
*
* @package core
* @category test
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright Andrew Lyons <[email protected]>
*/
class mailpit_message_user implements message_user {
/**
* Create a new message user object from a sender.
*
* @param \stdClass $user The user object.
* @return self
*/
public static function from_sender(
\stdClass $user,
): self {
return new self(
name: $user->Name,
address: $user->Address,
);
}
/**
* Create a new message user object from a recipient.
*
* @param \stdClass $user The user object.
* @return self
*/
public static function from_recipient(
\stdClass $user,
): self {
return new self(
name: $user->Name,
address: $user->Address,
);
}
/**
* Constructor.
*
* @param string $name The name of the user.
* @param string $address The email address of the user.
*/
protected function __construct(
/** @var string The name of the user. */
protected string $name,
/** @var string The email address of the user. */
protected string $address
) {
}
/**
* Get the display name of the user.
*
* @return string
*/
public function get_name(): string {
return $this->name;
}
/**
* Get the email address of the user.
*
* @return string
*/
public function get_address(): string {
return $this->address;
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core\test;
/**
* Generic message interface.
*
* @package core
* @category test
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright Simey Lameze <[email protected]>
*/
interface message {
/**
* Get the message subject.
*
* @return string
*/
public function get_subject(): string;
/**
* Get the text representation of the body, if one was provided.
*
* @return null|string
*/
public function get_body_text(): ?string;
/**
* Get the HTML representation of the body, if one was provided.
*
* @return null|string
*/
public function get_body_html(): ?string;
/**
* Get the message sender.
*
* @return message_user
*/
public function get_sender(): message_user;
/**
* Get the message recipients.
*
* @return iterable<message_user>
*/
public function get_recipients(): iterable;
/**
* Whether the message has the specified recipient.
*
* @param string $email The email address.
* @return bool
*/
public function has_recipient(string $email): bool;
/**
* Get the message cc recipients.
*
* @return iterable<message_user>
*/
public function get_cc(): iterable;
/**
* Get the message cc recipients.
*
* @return iterable<message_user>
*/
public function get_bcc(): iterable;
}
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core\test;
/**
* Generic message user interface.
*
* @package core
* @category test
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright Andrew Lyons <[email protected]>
*/
interface message_user {
/**
* Get the display name of the user.
*
* @return string
*/
public function get_name(): string;
/**
* Get the email address of the user.
*
* @return string
*/
public function get_address(): string;
}
+5 -3
View File
@@ -5595,14 +5595,16 @@ function email_to_user($user, $from, $subject, $messagetext, $messagehtml = '',
return false;
}
if (defined('BEHAT_SITE_RUNNING')) {
// Fake email sending in behat.
if (defined('BEHAT_SITE_RUNNING') && !defined('TEST_EMAILCATCHER_MAIL_SERVER') &&
!defined('TEST_EMAILCATCHER_API_SERVER')) {
// Behat tests are running and we are not using email catcher so fake email sending.
return true;
}
if (!empty($CFG->noemailever)) {
// Hidden setting for development sites, set in config.php if needed.
debugging('Not sending email due to $CFG->noemailever config setting', DEBUG_NORMAL);
debugging('Not sending email due to $CFG->noemailever config setting', DEBUG_DEVELOPER);
return true;
}
+309
View File
@@ -0,0 +1,309 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use core\test\message;
use Behat\Gherkin\Node\TableNode;
use Behat\Mink\Exception\ExpectationException;
use Moodle\BehatExtension\Exception\SkippedException;
require_once(__DIR__ . '/../../behat/behat_base.php');
/**
* Steps definitions to assist with email testing.
*
* @package core
* @category test
* @copyright Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_email extends behat_base {
/**
* Check if email catcher is configured.
*
* @return bool
*/
public static function is_email_catcher_configured(): bool {
return defined('TEST_EMAILCATCHER_MAIL_SERVER') && defined('TEST_EMAILCATCHER_API_SERVER');
}
/**
* Behat step to check if email catcher is configured.
*
* @Given /^an email catcher server is configured$/
*/
public function email_catcher_is_configured(): void {
if (!self::is_email_catcher_configured()) {
throw new SkippedException(
'The TEST_EMAILCATCHER_MAIL_SERVER and TEST_EMAILCATCHER_API_SERVER constants must be ' .
'defined in config.php to use the mailcatcher steps.'
);
}
}
/**
* Get the email catcher object or thrown a SkippedException if TEST_MAILPIT_SERVER is not defined.
*
* @return \core\test\email_catcher|null
*/
private function get_catcher(): ?\core\test\email_catcher {
if (self::is_email_catcher_configured()) {
return new \core\test\mailpit_email_catcher(TEST_EMAILCATCHER_API_SERVER);
}
return null;
}
/**
* Clean up the email inbox after each scenario.
*
* @AfterScenario @behat_email
*/
public function reset_after_test(): void {
$this->get_catcher()?->delete_all();
}
/**
* Get the e-mail address of a user from the step input.
*
* This could be an e-mail address, or a username.
*
* @param string $input The input from the step.
* @return string
*/
private function get_email_address_from_input(string $input): string {
if (strpos($input, '@') !== false) {
return $input;
}
$user = $this->get_user_by_identifier($input);
if (!$user) {
throw new ExpectationException("No user found with identifier {$input}", $this->getSession()->getDriver());
}
return $user->email;
}
/**
* Get any message matching the supplied user and subject.
*
* @param string $user The user to check for.
* @param string $subject The subject to check for.
* @return iterable<message>
*/
private function get_messages_matching_address_and_subject(
string $user,
string $subject,
): iterable {
$address = $this->get_email_address_from_input($user);
return new \CallbackFilterIterator(
iterator: $this->get_catcher()->get_messages(showdetails: true),
callback: function (message $message) use ($address, $subject): bool {
if (!$message->has_recipient($address)) {
return false;
}
if (strpos($message->get_subject(), $subject) === false) {
return false;
}
return true;
},
);
}
/**
* Verifies the content of an email sent to a specific user and subject.
*
* @Given the email to :user with subject containing :subject should contain :content
*
* @param string $user The user to check for.
* @param string $subject The subject to check for.
* @param string $content The content to check for.
*/
public function verify_email_content(string $user, string $subject, string $content): void {
$messages = $this->get_messages_matching_address_and_subject($user, $subject);
$count = 0;
foreach ($messages as $message) {
$count++;
$this->validate_data('content', $message, $content);
}
if ($count === 0) {
throw new ExpectationException(
"No messages found with subject containing {$subject}",
$this->getSession()->getDriver(),
);
}
}
/**
* Custom Behat test to verify the number of emails for a user.
*
* @Then user :address should have :count emails
*
* @param string $address The user to check for.
* @param int $expected The number of emails to check for.
*/
public function verify_email_count(string $address, int $expected): void {
$address = $this->get_email_address_from_input($address);
$messages = new \CallbackFilterIterator(
iterator: $this->get_catcher()->get_messages(),
callback: fn($message) => $message->has_recipient($address),
);
$count = iterator_count($messages);
if ($count !== $expected) {
throw new ExpectationException(
sprintf(
'Expected %d messages, but found %d',
$expected,
$count,
),
$this->getSession(),
);
}
}
/**
* Custom Behat test to empty the email inbox.
*
* @When I empty the email inbox
*/
public function empty_email_inbox() {
$this->get_catcher()->delete_all();
}
/**
* Behat step to send emails.
*
* @Given the following emails have been sent:
*
* @param TableNode $table The table of emails to send.
*/
public function the_following_emails_have_been_sent(TableNode $table): void {
if (!$rows = $table->getRows()) {
return;
}
// Allowed fields.
$allowedfields = ['to', 'subject', 'message'];
// Create a map of header to index.
$headers = array_flip($rows[0]);
// Remove header row.
unset($rows[0]);
// Validate supplied headers.
foreach ($headers as $header => $index) {
if (!in_array($header, $allowedfields)) {
throw new ExpectationException("Invalid header {$header} found in table", $this->getSession()->getDriver());
}
}
foreach ($rows as $row) {
// Check if the required headers are set in the $headers map.
$to = isset($headers['to']) ? $row[$headers['to']] : '[email protected]';
$subject = isset($headers['subject']) ? $row[$headers['subject']] : 'Default test subject';
$message = isset($headers['message']) ? $row[$headers['message']] : 'Default test message';
// Use no-reply user as dummy user to send emails from.
$noreplyuser = \core_user::get_user(\core_user::NOREPLY_USER);
// Create a dummy user to send emails to.
$emailuserto = new stdClass();
$emailuserto->id = -99;
$emailuserto->email = $to;
$emailuserto->firstname = 'Test';
$emailuserto->lastname = 'User';
// Send test email.
email_to_user($emailuserto, $noreplyuser, $subject, $message);
}
}
/**
* Validate the emails expected and actual values.
*
* @param string $field The field to validate.
* @param message $message The expected value.
* @param string $expected The actual value.
*/
private function validate_data(
string $field,
message $message,
string $expected,
): void {
switch ($field) {
case 'user':
$actual = $message->get_recipients();
foreach ($actual as $recipient) {
if ($recipient->get_address() === $expected) {
return;
}
}
throw new ExpectationException(
sprintf(
'Expected %s %s, but found %s',
$expected,
$field,
$actual,
),
$this->getSession(),
);
case 'subject':
$actual = $message->get_subject();
if (str_contains($expected, $actual)) {
return;
}
throw new ExpectationException(
sprintf(
'Expected %s %s, but found %s',
$expected,
$field,
$actual,
),
$this->getSession(),
);
case 'content':
if (str_contains($expected, $message->get_body_text())) {
return;
}
if (str_contains($expected, $message->get_body_html())) {
return;
}
throw new ExpectationException(
sprintf(
'Expected %s to contain %s, but it does not. Actual text was:\n%s\nActual HTML content was:\n%s\n',
$field,
$expected,
$message->get_body_text(),
$message->get_body_html(),
),
$this->getSession(),
);
default:
throw new ExpectationException(
sprintf(
'Unknown field to validate: %s',
$field,
),
$this->getSession(),
);
}
}
}
+21
View File
@@ -0,0 +1,21 @@
@behat_test @behat_email
Feature: Testing Email Functionality
Background:
Given an email catcher server is configured
Scenario: Verifying email content to user
When the following emails have been sent:
| to | subject | message |
| student1@example.com | A testing subject | This is a test message |
Then the email to "student1@example.com" with subject containing "A testing subject" should contain "This is a test message"
Scenario: Test emptying the email inbox
When the following emails have been sent:
| to | subject |
| student1@example.com | Apple |
| student1@example.com | Banana |
| student1@example.com | Chocolate |
Then user "student1@example.com" should have 3 emails
And I empty the email inbox
And user "student1@example.com" should have 0 emails
+11
View File
@@ -1,6 +1,17 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 4.4.8 ===
* Behat now supports email content verification using Mailpit.
You can check the contents of an email using the step `Then the email to "[email protected]" with subject containing "subject" should contain "content".`
To use this feature:
1. Ensure that Mailpit is running
2. Define the following constants in your `config.php`:
- `TEST_EMAILCATCHER_MAIL_SERVER` - The Mailpit server address (e.g. `0.0.0.0:1025`)
- `TEST_EMAILCATCHER_API_SERVER` - The Mailpit API server (qe.g. `http://localhost:8025`)
3. Ensure that the email catcher is set up using the step `Given an email catcher server is configured`.
=== 4.4.6 ===
* A new core\ip_utils::normalize_internet_address() method is created to