Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
use core\test\message;
18
use Behat\Gherkin\Node\TableNode;
19
use Behat\Mink\Exception\ExpectationException;
20
use Moodle\BehatExtension\Exception\SkippedException;
21
 
22
require_once(__DIR__ . '/../../behat/behat_base.php');
23
 
24
/**
25
 * Steps definitions to assist with email testing.
26
 *
27
 * @package    core
28
 * @category   test
29
 * @copyright  Simey Lameze <simey@moodle.com>
30
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 */
32
class behat_email extends behat_base {
33
 
34
    /**
35
     * Check if email catcher is configured.
36
     *
37
     * @return bool
38
     */
39
    public static function is_email_catcher_configured(): bool {
40
        return defined('TEST_EMAILCATCHER_MAIL_SERVER') && defined('TEST_EMAILCATCHER_API_SERVER');
41
    }
42
 
43
    /**
44
     * Behat step to check if email catcher is configured.
45
     *
46
     * @Given /^an email catcher server is configured$/
47
     */
48
    public function email_catcher_is_configured(): void {
49
        if (!self::is_email_catcher_configured()) {
50
            throw new SkippedException(
51
                'The TEST_EMAILCATCHER_MAIL_SERVER and TEST_EMAILCATCHER_API_SERVER constants must be ' .
52
                'defined in config.php to use the mailcatcher steps.'
53
            );
54
        }
55
    }
56
 
57
    /**
58
     * Get the email catcher object or thrown a SkippedException if TEST_MAILPIT_SERVER is not defined.
59
     *
60
     * @return \core\test\email_catcher|null
61
     */
62
    private function get_catcher(): ?\core\test\email_catcher {
63
        if (self::is_email_catcher_configured()) {
64
            return new \core\test\mailpit_email_catcher(TEST_EMAILCATCHER_API_SERVER);
65
        }
66
 
67
        return null;
68
    }
69
 
70
    /**
71
     * Clean up the email inbox after each scenario.
72
     *
73
     * @AfterScenario @behat_email
74
     */
75
    public function reset_after_test(): void {
76
        $this->get_catcher()?->delete_all();
77
    }
78
 
79
    /**
80
     * Get the e-mail address of a user from the step input.
81
     *
82
     * This could be an e-mail address, or a username.
83
     *
84
     * @param string $input The input from the step.
85
     * @return string
86
     */
87
    private function get_email_address_from_input(string $input): string {
88
        if (strpos($input, '@') !== false) {
89
            return $input;
90
        }
91
 
92
        $user = $this->get_user_by_identifier($input);
93
        if (!$user) {
94
            throw new ExpectationException("No user found with identifier {$input}", $this->getSession()->getDriver());
95
        }
96
 
97
        return $user->email;
98
    }
99
 
100
    /**
101
     * Get any message matching the supplied user and subject.
102
     *
103
     * @param string $user The user to check for.
104
     * @param string $subject The subject to check for.
105
     * @return iterable<message>
106
     */
107
    private function get_messages_matching_address_and_subject(
108
        string $user,
109
        string $subject,
110
    ): iterable {
111
        $address = $this->get_email_address_from_input($user);
112
        return new \CallbackFilterIterator(
113
            iterator: $this->get_catcher()->get_messages(showdetails: true),
114
            callback: function (message $message) use ($address, $subject): bool {
115
                if (!$message->has_recipient($address)) {
116
                    return false;
117
                }
118
 
119
                if (strpos($message->get_subject(), $subject) === false) {
120
                    return false;
121
                }
122
 
123
                return true;
124
            },
125
        );
126
    }
127
 
128
    /**
129
     * Verifies the content of an email sent to a specific user and subject.
130
     *
131
     * @Given the email to :user with subject containing :subject should contain :content
132
     *
133
     * @param string $user The user to check for.
134
     * @param string $subject The subject to check for.
135
     * @param string $content The content to check for.
136
     */
137
    public function verify_email_content(string $user, string $subject, string $content): void {
138
        $messages = $this->get_messages_matching_address_and_subject($user, $subject);
139
 
140
        $count = 0;
141
        foreach ($messages as $message) {
142
            $count++;
143
            $this->validate_data('content', $message, $content);
144
        }
145
 
146
        if ($count === 0) {
147
            throw new ExpectationException(
148
                "No messages found with subject containing {$subject}",
149
                $this->getSession()->getDriver(),
150
            );
151
        }
152
    }
153
 
154
    /**
155
     * Custom Behat test to verify the number of emails for a user.
156
     *
157
     * @Then user :address should have :count emails
158
     *
159
     * @param string $address The user to check for.
160
     * @param int $expected The number of emails to check for.
161
     */
162
    public function verify_email_count(string $address, int $expected): void {
163
        $address = $this->get_email_address_from_input($address);
164
        $messages = new \CallbackFilterIterator(
165
            iterator: $this->get_catcher()->get_messages(),
166
            callback: fn($message) => $message->has_recipient($address),
167
        );
168
 
169
        $count = iterator_count($messages);
170
        if ($count !== $expected) {
171
            throw new ExpectationException(
172
                sprintf(
173
                    'Expected %d messages, but found %d',
174
                    $expected,
175
                    $count,
176
                ),
177
                $this->getSession(),
178
            );
179
        }
180
    }
181
 
182
    /**
183
     * Custom Behat test to empty the email inbox.
184
     *
185
     * @When I empty the email inbox
186
     */
187
    public function empty_email_inbox() {
188
        $this->get_catcher()->delete_all();
189
    }
190
 
191
    /**
192
     * Behat step to send emails.
193
     *
194
     * @Given the following emails have been sent:
195
     *
196
     * @param TableNode $table The table of emails to send.
197
     */
198
    public function the_following_emails_have_been_sent(TableNode $table): void {
199
        if (!$rows = $table->getRows()) {
200
            return;
201
        }
202
 
203
        // Allowed fields.
204
        $allowedfields = ['to', 'subject', 'message'];
205
 
206
        // Create a map of header to index.
207
        $headers = array_flip($rows[0]);
208
        // Remove header row.
209
        unset($rows[0]);
210
 
211
        // Validate supplied headers.
212
        foreach ($headers as $header => $index) {
213
            if (!in_array($header, $allowedfields)) {
214
                throw new ExpectationException("Invalid header {$header} found in table", $this->getSession()->getDriver());
215
            }
216
        }
217
 
218
        foreach ($rows as $row) {
219
            // Check if the required headers are set in the $headers map.
220
            $to = isset($headers['to']) ? $row[$headers['to']] : 'userto@example.com';
221
            $subject = isset($headers['subject']) ? $row[$headers['subject']] : 'Default test subject';
222
            $message = isset($headers['message']) ? $row[$headers['message']] : 'Default test message';
223
 
224
            // Use no-reply user as dummy user to send emails from.
225
            $noreplyuser = \core_user::get_user(\core_user::NOREPLY_USER);
226
 
227
            // Create a dummy user to send emails to.
228
            $emailuserto = new stdClass();
229
            $emailuserto->id = -99;
230
            $emailuserto->email = $to;
231
            $emailuserto->firstname = 'Test';
232
            $emailuserto->lastname = 'User';
233
 
234
            // Send test email.
235
            email_to_user($emailuserto, $noreplyuser, $subject, $message);
236
        }
237
    }
238
 
239
    /**
240
     * Validate the emails expected and actual values.
241
     *
242
     * @param string $field The field to validate.
243
     * @param message $message The expected value.
244
     * @param string $expected The actual value.
245
     */
246
    private function validate_data(
247
        string $field,
248
        message $message,
249
        string $expected,
250
    ): void {
251
        switch ($field) {
252
            case 'user':
253
                $actual = $message->get_recipients();
254
                foreach ($actual as $recipient) {
255
                    if ($recipient->get_address() === $expected) {
256
                        return;
257
                    }
258
                }
259
                throw new ExpectationException(
260
                    sprintf(
261
                        'Expected %s %s, but found %s',
262
                        $expected,
263
                        $field,
264
                        $actual,
265
                    ),
266
                    $this->getSession(),
267
                );
268
            case 'subject':
269
                $actual = $message->get_subject();
270
                if (str_contains($expected, $actual)) {
271
                    return;
272
                }
273
                throw new ExpectationException(
274
                    sprintf(
275
                        'Expected %s %s, but found %s',
276
                        $expected,
277
                        $field,
278
                        $actual,
279
                    ),
280
                    $this->getSession(),
281
                );
282
            case 'content':
283
                if (str_contains($expected, $message->get_body_text())) {
284
                    return;
285
                }
286
                if (str_contains($expected, $message->get_body_html())) {
287
                    return;
288
                }
289
                throw new ExpectationException(
290
                    sprintf(
291
                        'Expected %s to contain %s, but it does not. Actual text was:\n%s\nActual HTML content was:\n%s\n',
292
                        $field,
293
                        $expected,
294
                        $message->get_body_text(),
295
                        $message->get_body_html(),
296
                    ),
297
                    $this->getSession(),
298
                );
299
            default:
300
                throw new ExpectationException(
301
                    sprintf(
302
                        'Unknown field to validate: %s',
303
                        $field,
304
                    ),
305
                    $this->getSession(),
306
                );
307
        }
308
    }
309
}