Completed
Pull Request — master (#121)
by
unknown
02:10
created

EmailContext::iClickOnHttpLinkInEmail()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 16
c 1
b 0
f 1
nc 4
nop 1
dl 0
loc 23
rs 8.7972
1
<?php
2
3
namespace SilverStripe\BehatExtension\Context;
4
5
use Behat\Behat\Context\ClosuredContextInterface,
6
Behat\Behat\Context\TranslatedContextInterface,
7
Behat\Behat\Context\BehatContext,
8
Behat\Behat\Context\Step,
9
Behat\Behat\Event\FeatureEvent,
10
Behat\Behat\Event\ScenarioEvent,
11
Behat\Behat\Exception\PendingException;
12
use Behat\Gherkin\Node\PyStringNode,
13
Behat\Gherkin\Node\TableNode;
14
use Symfony\Component\DomCrawler\Crawler;
15
16
// PHPUnit
17
require_once BASE_PATH . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php';
18
19
/**
20
 * Context used to define steps related to email sending.
21
 */
22
class EmailContext extends BehatContext
23
{
24
    protected $context;
25
26
    protected $mailer;
27
28
    /**
29
     * Stored to simplify later assertions
30
     */
31
    protected $lastMatchedEmail;
32
33
    /**
34
     * Initializes context.
35
     * Every scenario gets it's own context object.
36
     *
37
     * @param array $parameters context parameters (set them up through behat.yml)
38
     */
39
    public function __construct(array $parameters)
40
    {
41
        // Initialize your context here
42
        $this->context = $parameters;
43
    }
44
45
    /**
46
     * Get Mink session from MinkContext
47
     */
48
    public function getSession($name = null)
49
    {
50
        return $this->getMainContext()->getSession($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method getSession() does only exist in the following implementations of said interface: Behat\MinkExtension\Context\MinkContext, Behat\MinkExtension\Context\RawMinkContext, SilverStripe\BehatExtension\Context\BasicContext, SilverStripe\BehatExtension\Context\EmailContext, SilverStripe\BehatExtension\Context\FixtureContext, SilverStripe\BehatExtension\Context\LoginContext, SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
51
    }
52
53
    /**
54
     * @BeforeScenario
55
     */
56
    public function before(ScenarioEvent $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
57
    {
58
        // Also set through the 'supportbehat' extension
59
        // to ensure its available both in CLI execution and the tested browser session
60
        $this->mailer = new \SilverStripe\BehatExtension\Utility\TestMailer();
61
        \Email::set_mailer($this->mailer);
0 ignored issues
show
Deprecated Code introduced by
The method Email::set_mailer() has been deprecated with message: since version 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
62
        \Config::inst()->update("Email","send_all_emails_to", null);
63
    }
64
65
    /**
66
     * @Given /^there should (not |)be an email (to|from) "([^"]*)"$/
67
     */
68
    public function thereIsAnEmailFromTo($negate, $direction, $email)
69
    {
70
        $to = ($direction == 'to') ? $email : null;
71
        $from = ($direction == 'from') ? $email : null;
72
        $match = $this->mailer->findEmail($to, $from);
73
        if(trim($negate)) {
74
            assertNull($match);
75
        } else {
76
            assertNotNull($match);
77
        }
78
        $this->lastMatchedEmail = $match;
79
    }
80
81
    /**
82
     * @Given /^there should (not |)be an email (to|from) "([^"]*)" titled "([^"]*)"$/
83
     */
84
    public function thereIsAnEmailFromToTitled($negate, $direction, $email, $subject)
85
    {
86
        $to = ($direction == 'to') ? $email : null;
87
        $from = ($direction == 'from') ? $email : null;
88
        $match = $this->mailer->findEmail($to, $from, $subject);
89
        $allMails = $this->mailer->findEmails($to, $from);
90
        $allTitles = $allMails ? '"' . implode('","', array_map(function($email) {return $email->Subject;}, $allMails)) . '"' : null;
91
        if(trim($negate)) {
92
            assertNull($match);
93
        } else {
94
            $msg = sprintf(
95
                'Could not find email %s "%s" titled "%s".',
96
                $direction,
97
                $email,
98
                $subject
99
            );
100
            if($allTitles) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $allTitles of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
101
                $msg .= ' Existing emails: ' . $allTitles;
102
            }
103
            assertNotNull($match,$msg);
104
        }
105
        $this->lastMatchedEmail = $match;
106
    }
107
108
    /**
109
     * Example: Given the email should contain "Thank you for registering!".
110
     * Assumes an email has been identified by a previous step,
111
     * e.g. through 'Given there should be an email to "[email protected]"'.
112
     * 
113
	 * @Given /^the email should (not |)contain "([^"]*)"$/
114
	 */
115
	public function thereTheEmailContains($negate, $content)
116
	{
117
		if(!$this->lastMatchedEmail) {
118
			throw new \LogicException('No matched email found from previous step');
119
		}
120
121
		$email = $this->lastMatchedEmail;
122
		$emailContent = null;
0 ignored issues
show
Unused Code introduced by
$emailContent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
123
		if($email->Content) {
124
			$emailContent = $email->Content;
125
		} else {
126
			$emailContent = $email->PlainContent;
127
		}
128
129
		if(trim($negate)) {
130
			assertNotContains($content, $emailContent);
131
		} else {
132
			assertContains($content, $emailContent);
133
		}
134
	}
135
136
	/**
137
	 * Example: Given the email contains "Thank you for <strong>registering!<strong>".
138
	 * Then the email should contain plain text "Thank you for registering!"
139
	 * Assumes an email has been identified by a previous step,
140
	 * e.g. through 'Given there should be an email to "[email protected]"'.
141
	 * 
142
	 * @Given /^the email should contain plain text "([^"]*)"$/
143
	 */
144
	public function thereTheEmailContainsPlainText($content)
145
	{
146
		if(!$this->lastMatchedEmail) {
147
			throw new \LogicException('No matched email found from previous step');
148
		}
149
150
		$email = $this->lastMatchedEmail;
151
		$emailContent = ($email->Content) ? ($email->Content) : ($email->PlainContent);
152
		$emailPlainText = strip_tags($emailContent);
153
		$emailPlainText = preg_replace("/\h+/", " ", $emailPlainText);
154
155
		assertContains($content, $emailPlainText);
156
	}
157
158
    /**
159
     * @When /^I click on the "([^"]*)" link in the email (to|from) "([^"]*)"$/
160
     */
161 View Code Duplication
    public function iGoToInTheEmailTo($linkSelector, $direction, $email)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
    {
163
        $to = ($direction == 'to') ? $email : null;
164
        $from = ($direction == 'from') ? $email : null;
165
        $match = $this->mailer->findEmail($to, $from);
166
        assertNotNull($match);
167
168
        $crawler = new Crawler($match->Content);
169
        $linkEl = $crawler->selectLink($linkSelector);
170
        assertNotNull($linkEl);
171
        $link = $linkEl->attr('href');
172
        assertNotNull($link);
173
        
174
        return new Step\When(sprintf('I go to "%s"', $link));
175
    }
176
177
    /**
178
     * @When /^I click on the "([^"]*)" link in the email (to|from) "([^"]*)" titled "([^"]*)"$/
179
     */
180 View Code Duplication
    public function iGoToInTheEmailToTitled($linkSelector, $direction, $email, $title)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
    {
182
        $to = ($direction == 'to') ? $email : null;
183
        $from = ($direction == 'from') ? $email : null;
184
        $match = $this->mailer->findEmail($to, $from, $title);
185
        assertNotNull($match);
186
187
        $crawler = new Crawler($match->Content);
188
        $linkEl = $crawler->selectLink($linkSelector);
189
        assertNotNull($linkEl);
190
        $link = $linkEl->attr('href');
191
        assertNotNull($link);
192
        return new Step\When(sprintf('I go to "%s"', $link));
193
    }
194
    
195
    /**
196
     * Assumes an email has been identified by a previous step,
197
     * e.g. through 'Given there should be an email to "[email protected]"'.
198
     * 
199
     * @When /^I click on the "([^"]*)" link in the email"$/
200
     */
201
    public function iGoToInTheEmail($linkSelector)
202
    {
203
        if(!$this->lastMatchedEmail) {
204
            throw new \LogicException('No matched email found from previous step');
205
        }
206
207
        $match = $this->lastMatchedEmail;
208
        $crawler = new Crawler($match->Content);
209
        $linkEl = $crawler->selectLink($linkSelector);
210
        assertNotNull($linkEl);
211
        $link = $linkEl->attr('href');
212
        assertNotNull($link);
213
214
        return new Step\When(sprintf('I go to "%s"', $link));
215
    }
216
217
    /**
218
     * @Given /^I clear all emails$/
219
     */
220
    public function iClearAllEmails()
221
    {
222
        $this->lastMatchedEmail = null;
223
        return $this->mailer->clearEmails();
224
    }
225
226
	/**
227
	 * Example: Then the email should contain the following data:
228
	 * | row1 |
229
	 * | row2 |
230
	 * Assumes an email has been identified by a previous step.
231
	 * @Then /^the email should (not |)contain the following data:$/
232
	 */
233
	public function theEmailContainFollowingData($negate, TableNode $table) {
234
		if(!$this->lastMatchedEmail) {
235
			throw new \LogicException('No matched email found from previous step');
236
		}
237
238
		$email = $this->lastMatchedEmail;
239
		$emailContent = null;
0 ignored issues
show
Unused Code introduced by
$emailContent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
240
		if($email->Content) {
241
			$emailContent = $email->Content;
242
		} else {
243
			$emailContent = $email->PlainContent;
244
		}
245
		// Convert html content to plain text
246
		$emailContent = strip_tags($emailContent);
247
		$emailContent = preg_replace("/\h+/", " ", $emailContent);
248
		$rows = $table->getRows();
249
		
250
		// For "should not contain"
251
		if(trim($negate)) {
252
			foreach($rows as $row) {
253
				assertNotContains($row[0], $emailContent);
254
			}
255
		} else {
256
			foreach($rows as $row) {
257
				assertContains($row[0], $emailContent);
258
			}
259
		}
260
	}
261
262
	/**
263
	 * @Then /^there should (not |)be an email titled "([^"]*)"$/
264
	 */
265
	public function thereIsAnEmailTitled($negate, $subject)
266
	{
267
		$match = $this->mailer->findEmail(null, null, $subject);
268
		if(trim($negate)) {
269
			assertNull($match);
270
		} else {
271
			$msg = sprintf(
272
				'Could not find email titled "%s".',
273
				$subject
274
			);
275
			assertNotNull($match,$msg);
276
		}
277
		$this->lastMatchedEmail = $match;
278
	}
279
280
	/**
281
	 * @Then /^the email should (not |)be sent from "([^"]*)"$/
282
	 */
283 View Code Duplication
	public function theEmailSentFrom($negate, $from)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
284
	{
285
		if(!$this->lastMatchedEmail) {
286
			throw new \LogicException('No matched email found from previous step');
287
		}
288
289
		$match = $this->lastMatchedEmail;
290
		if(trim($negate)) {
291
			assertNotContains($from, $match->From);
292
		} else {
293
			assertContains($from, $match->From);
294
		}
295
	}
296
297
	/**
298
	 * @Then /^the email should (not |)be sent to "([^"]*)"$/
299
	 */
300 View Code Duplication
	public function theEmailSentTo($negate, $to)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
301
	{
302
		if(!$this->lastMatchedEmail) {
303
			throw new \LogicException('No matched email found from previous step');
304
		}
305
306
		$match = $this->lastMatchedEmail;
307
		if(trim($negate)) {
308
			assertNotContains($to, $match->To);
309
		} else {
310
			assertContains($to, $match->To);
311
		}
312
	}
313
314
    /**
315
     * The link text is the link address itself which contains special characters
316
     * e.g. http://localhost/Security/changepassword?m=199&title=reset
317
     * Example: When I click on the http link "changepassword" in the email
318
     * @When /^I click on the http link "([^"]*)" in the email$/
319
     */
320
    public function iClickOnHttpLinkInEmail($httpText) {
321
        if(!$this->lastMatchedEmail) {
322
            throw new \LogicException('No matched email found from previous step');
323
        }
324
325
        $email = $this->lastMatchedEmail;
326
        $html = $email->Content;
327
        $dom = new \DOMDocument();
328
        $dom->loadHTML($html);
329
330
        $tags = $dom->getElementsByTagName('a');
331
        $href = null;
332
        foreach ($tags as $tag) {
333
            $linkText = $tag->nodeValue;
334
            if(strpos($linkText, $httpText) !== false) {
335
                $href = $linkText;
336
                break;
337
            }
338
        }
339
        assertNotNull($href);
340
341
        return new Step\When(sprintf('I go to "%s"', $href));
342
    }
343
}
344