Completed
Push — develop ( 6bec98...3062c0 )
by Carsten
12s
created

MailContext::mailShouldBeSentTo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 1
1
<?php
2
3
/**
4
 * YAWIK
5
 *
6
 * @filesource
7
 * @license MIT
8
 * @copyright  2013 - 2017 Cross Solution <http://cross-solution.de>
9
 */
10
11
namespace Yawik\Behat;
12
13
use Behat\Behat\Context\Context;
14
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
15
use Core\Mail\FileTransport;
16
use Webmozart\Assert\Assert;
17
18
class MailContext implements Context
19
{
20
    use CommonContextTrait;
21
22
    /**
23
     * @var array
24
     */
25
    private $messages = array();
26
27
    /**
28
     * @var array
29
     */
30
    private $toMails = [];
31
32
    /**
33
     * @var array
34
     */
35
    private $fromMails = [];
36
37
    /**
38
     * @var array
39
     */
40
    private $subjects = [];
41
42
    /**
43
     * Cleans all files before start test
44
     * @BeforeScenario @mail
45
     * @param BeforeScenarioScope $scope
46
     */
47
    public function beforeScenario(BeforeScenarioScope $scope)
48
    {
49
        $core = $scope->getEnvironment()->getContext(CoreContext::class);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Testwork\Environment\Environment as the method getContext() does only exist in the following implementations of said interface: Behat\Behat\Context\Envi...lizedContextEnvironment, FriendsOfBehat\ContextSe...ntextServiceEnvironment.

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...
50
        /* @var FileTransport $transport */
51
        $transport = $core->getServiceManager()->get('Core/MailService')->getTransport();
52
        $path = $transport->getOptions()->getPath() . '/*.eml';
53
        foreach (glob($path) as $filename) {
54
            unlink($filename);
55
        }
56
57
        $this->subjects = $this->fromMails = $this->toMails = array();
58
    }
59
60
    /**
61
     * @Then an email should be sent to :address
62
     * @param string $email
63
     */
64
    public function mailShouldBeSentTo($email)
65
    {
66
        $this->iHaveEmailSent();
67
        Assert::oneOf(
68
            $email,
69
            $this->toMails
70
        );
71
    }
72
73
    /**
74
     * @Then an email should be sent from :address
75
     * @param string $email
76
     */
77
    public function mailShouldBeSentFrom($email)
78
    {
79
        $this->iHaveEmailSent();
80
        Assert::oneOf(
81
            $email,
82
            $this->fromMails
83
        );
84
    }
85
86
    /**
87
     * @Then email subject should contain :subject
88
     * @param string $subject
89
     */
90
    public function mailSubjectShouldBe($subject)
91
    {
92
        $this->iHaveEmailSent();
93
        Assert::oneOf(
94
            $subject,
95
            $this->subjects
96
        );
97
    }
98
99
    /**
100
     * @Then sent email should be contain :text
101
     */
102
    public function sentEmailShouldBeContain($text)
103
    {
104
        $this->iHaveEmailSent();
105
        $regex = '/.*('.preg_quote($text).').*/im';
106
        $matches = [];
107
        $multiMessages = false;
108
        if(count($this->messages) > 1){
109
            $multiMessages = true;
110
        }
111
        $content = "";
112
        foreach($this->messages as $key=>$definition){
113
            $content = $definition['contents'];
114
            if(preg_match($regex,$content,$match)){
115
                $matches[] = $match;
116
            }
117
        }
118
        $failMessage = sprintf('Can not find text "%s" in any email sent',$text);
119
        if(!$multiMessages){
120
            $failMessage = sprintf(
121
                'Can not find text "%s" in sent email. Here\'s the email content: %s',
122
                $text,
123
                PHP_EOL.PHP_EOL.$content
124
            );
125
        }
126
        Assert::true(count($matches)>0,$failMessage);
127
    }
128
129
    /**
130
     * @Then I have email sent
131
     */
132
    public function iHaveEmailSent()
133
    {
134
        /* @var FileTransport $transport */
135
        $transport = $this->getService('Core/MailService')->getTransport();
136
137
        $path = $transport->getOptions()->getPath().'/*.eml';
138
139
        foreach(glob($path) as $filename){
140
            $id = md5($filename);
141
            if(!isset($this->messages[$id])){
142
                $contents = file_get_contents($filename);
143
                $this->messages[$id]  = $this->parseEmail($contents);
144
            }
145
        }
146
147
        Assert::true(
148
            count($this->messages)>0,
149
            'No email have been sent'
150
        );
151
    }
152
153
    private function parseEmail($contents)
154
    {
155
        $addresses = $this->parseEmailAddress($contents);
156
        $subject =$this->parseSubject($contents);
157
158
        $contents = strip_tags($contents);
159
        $contents = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $contents);
160
161
        return array_merge($addresses,$subject,['contents' => $contents]);
162
    }
163
164
    private function parseEmailAddress($contents)
165
    {
166
        // pattern to get email address
167
        $radd =  '(\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\b)';
168
169
        // get email from address
170
        $regex = sprintf('/^From\:.*%s/im',$radd);
171
        $hasMatch = preg_match($regex,$contents,$matches);
172
        $fromAddress = $hasMatch ? $matches[1]:null;
173
174
        // get email to address
175
        $regex = sprintf('/^To\:\s+%s/im',$radd);
176
        $hasMatch = preg_match($regex,$contents,$matches);
177
        $toAddress1 = $hasMatch ? $matches[1]:null;
178
179
        // get email to address
180
        $regex = sprintf('/^To\:.*%s/im',$radd);
181
        $hasMatch = preg_match($regex,$contents,$matches);
182
        $toAddress2 = $hasMatch ? $matches[1]:null;
183
184
        $this->fromMails[] = $fromAddress;
185
        $this->toMails[] = $toAddress1;
186
        $this->toMails[] = $toAddress2;
187
188
        return [
189
            'from' => $fromAddress,
190
            'to' => [$toAddress1,$toAddress2],
191
        ];
192
    }
193
194
    private function parseSubject($contents)
195
    {
196
        $pattern = '/Subject\:(.*)/i';
197
        preg_match($pattern,$contents,$matches);
198
        $subject = isset($matches[1]) ? $matches[1]:null;
199
        $this->subjects[] = $subject;
200
        return [
201
            'subject' => trim($subject)
202
        ];
203
    }
204
}
205