Passed
Push — master ( 070130...a23132 )
by Dominik
14:44 queued 10:41
created

MailgunWebhookControllerTest   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 182
dl 0
loc 356
rs 9.92
c 0
b 0
f 0
wmc 31

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getInvalidPostData() 0 11 2
A getContainer() 0 7 2
A getRouter() 0 3 1
A testShowLog() 0 69 2
A loginUserIfRequired() 0 32 4
A checkApplication() 0 9 2
A getEntityManager() 0 3 1
A getEventDispatcher() 0 3 1
A testWebViewLinks() 0 36 2
A getValidPostData() 0 13 2
A testWebHookCreateAndEventDispatchingNewAPI() 0 7 1
B internalWebHookCreateAndEventDispatching() 0 75 5
A tearDown() 0 13 4
A setUp() 0 3 1
A testWebHookCreateAndEventDispatchingOldAPI() 0 7 1
1
<?php
2
3
namespace Azine\MailgunWebhooksBundle\Tests\Controller;
4
5
use Azine\MailgunWebhooksBundle\DependencyInjection\AzineMailgunWebhooksExtension;
6
use Azine\MailgunWebhooksBundle\Entity\EmailTrafficStatistics;
7
use Azine\MailgunWebhooksBundle\Tests\TestHelper;
8
use Doctrine\ORM\EntityManager;
9
use Symfony\Bundle\FrameworkBundle\Client;
10
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
11
use Symfony\Component\DependencyInjection\ContainerInterface;
12
use Symfony\Component\DomCrawler\Crawler;
13
use Symfony\Component\EventDispatcher\EventDispatcher;
14
use Symfony\Component\HttpFoundation\File\UploadedFile;
15
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
16
17
class MailgunWebhookControllerTest extends WebTestCase
18
{
19
    private $testStartTime;
20
21
    protected function setUp()
22
    {
23
        $this->testStartTime = new \DateTime();
24
    }
25
26
    protected function tearDown()
27
    {
28
        $manager = $this->getEntityManager();
29
        $queryBuilder = $manager->getRepository(EmailTrafficStatistics::class)->createQueryBuilder('e');
30
        $ets = $queryBuilder->where('e.created >= :testStartTime')
31
            ->setParameter('testStartTime', $this->testStartTime)
32
            ->getQuery()->execute();
33
34
        if (null != $ets && sizeof($ets) > 0) {
35
            foreach ($ets as $next) {
36
                $manager->remove($next);
37
            }
38
            $manager->flush();
39
        }
40
    }
41
42
    public function testWebHookCreateAndEventDispatchingOldAPI()
43
    {
44
        $this->checkApplication();
45
46
        $validPostData = $this->getValidPostData(false);
47
        $invalidPostData = $this->getInvalidPostData(false);
48
        $this->internalWebHookCreateAndEventDispatching($validPostData, $invalidPostData, false);
49
    }
50
51
    public function testWebHookCreateAndEventDispatchingNewAPI()
52
    {
53
        $this->checkApplication();
54
55
        $validPostData = $this->getValidPostData(true);
56
        $invalidPostData = $this->getInvalidPostData(true);
57
        $this->internalWebHookCreateAndEventDispatching($validPostData, $invalidPostData, true);
58
    }
59
60
    private function internalWebHookCreateAndEventDispatching($validPostData, $invalidPostData, $newApi)
61
    {
62
        $client = static::createClient();
63
        $client->request('GET', '/');
64
        $client->enableProfiler();
65
66
        // get webhook url
67
        $url = $this->getRouter()->generate('mailgunevent_webhook', array('_locale', 'en'), UrlGeneratorInterface::ABSOLUTE_URL);
68
69
        $manager = $this->getEntityManager();
70
        $eventReop = $manager->getRepository("Azine\MailgunWebhooksBundle\Entity\MailgunEvent");
71
        $count = sizeof($eventReop->findAll());
72
73
        $attachments = array(
74
            'attachment-1' => new UploadedFile(realpath(__DIR__.'/../testAttachment.small.png'), 'some.real.file.name1.png'),
75
            'attachment-2' => new UploadedFile(realpath(__DIR__.'/../testAttachment.small.png'), 'some.real.file.name2.png'),
76
            'attachment-3' => new UploadedFile(realpath(__DIR__.'/../testAttachment.small.png'), 'some.real.file.name3.png'),
77
        );
78
79
        // post invalid data to the webhook-url and check the response & database
80
        $webhookdata = json_encode($invalidPostData);
81
        if ($newApi) {
82
            $crawler = $client->request('POST', $url, array(), $attachments, array(), $webhookdata);
83
        } else {
84
            $crawler = $client->request('POST', $url, $invalidPostData, $attachments);
85
        }
86
87
        $this->assertSame(401, $client->getResponse()->getStatusCode(), "Response-Code 401 expected for post-data with invalid signature: \n\n$webhookdata\n\n\n");
88
        $this->assertContains('Signature verification failed.', $crawler->text(), 'Response expected.');
89
        $this->assertSame($count, sizeof($eventReop->findAll()), 'No new db entry for the webhook expected!');
90
91
        // post valid data to the webhook-url and check the response
92
        $webhookdata = json_encode($validPostData);
93
        if ($newApi) {
94
            $crawler = $client->request('POST', $url, array(), $attachments, array(), $webhookdata);
95
        } else {
96
            $crawler = $client->request('POST', $url, $validPostData, $attachments);
97
        }
98
        $this->assertSame(200, $client->getResponse()->getStatusCode(), "Response-Code 200 expected for '$url'.\n\n$webhookdata\n\n\n".$client->getResponse()->getContent());
99
        $this->assertContains('Thanx, for the info.', $crawler->text(), 'Response expected.');
100
        $this->assertSame($count + 1, sizeof($eventReop->findAll()), 'One new db entry for the webhook expected!');
101
102
        // post valid data to the webhook-url and check the response
103
        if ($newApi) {
104
            $validPostData['event-data']['event'] = 'opened';
105
            $webhookdata = json_encode($validPostData);
106
            $crawler = $client->request('POST', $url, array(), $attachments, array(), $webhookdata);
0 ignored issues
show
Unused Code introduced by
The assignment to $crawler is dead and can be removed.
Loading history...
107
            $crawler = $client->request('POST', $url, array(), $attachments, array(), $webhookdata);
108
        } else {
109
            $validPostData['event'] = 'opened';
110
            $webhookdata = json_encode($validPostData);
111
            $crawler = $client->request('POST', $url, $validPostData, $attachments);
112
            $crawler = $client->request('POST', $url, $validPostData, $attachments);
113
        }
114
        $this->assertSame(200, $client->getResponse()->getStatusCode(), "Response-Code 200 expected for '$url'.\n\n$webhookdata\n\n\n".$client->getResponse()->getContent());
115
        $this->assertContains('Thanx, for the info.', $crawler->text(), 'Response expected.');
116
117
        // post a complaint event to check if mail is triggered.
118
        if ($newApi) {
119
            $validPostData['event-data']['event'] = 'complained';
120
            $webhookdata = json_encode($validPostData);
121
            $crawler = $client->request('POST', $url, array(), $attachments, array(), $webhookdata);
122
        } else {
123
            $validPostData['event'] = 'complained';
124
            $webhookdata = json_encode($validPostData);
125
            $crawler = $client->request('POST', $url, $validPostData, $attachments);
126
        }
127
        $this->assertSame(200, $client->getResponse()->getStatusCode(), "Response-Code 200 expected for '$url'.\n\n$webhookdata\n\n\n".$client->getResponse()->getContent());
128
        $this->assertContains('Thanx, for the info.', $crawler->text(), 'Response expected.');
129
        $this->assertSame($count + 4, sizeof($eventReop->findAll()), 'One new db entry for the webhook expected!');
130
131
        $mailCollector = $client->getProfile()->getCollector('swiftmailer');
132
133
        // checks that an email was sent from the listener
134
        $this->assertSame(1, $mailCollector->getMessageCount());
0 ignored issues
show
Bug introduced by
The method getMessageCount() does not exist on Symfony\Component\HttpKe...\DataCollectorInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

134
        $this->assertSame(1, $mailCollector->/** @scrutinizer ignore-call */ getMessageCount());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
135
    }
136
137
    private function getValidPostData($newApi)
138
    {
139
        $postData = TestHelper::getPostDataWithoutSignature($newApi);
140
141
        $key = 'fake_api_key'; //$this->getContainer()->getParameter(AzineMailgunWebhooksExtension::PREFIX.'_'.AzineMailgunWebhooksExtension::API_KEY);
142
143
        if ($newApi) {
144
            $postData['signature']['signature'] = hash_hmac('SHA256', $postData['signature']['timestamp'].$postData['signature']['token'], $key);
145
        } else {
146
            $postData['signature'] = hash_hmac('SHA256', $postData['timestamp'].$postData['token'], $key);
147
        }
148
149
        return $postData;
150
    }
151
152
    private function getInvalidPostData($newApi)
153
    {
154
        $postData = TestHelper::getPostDataWithoutSignature($newApi);
155
156
        if ($newApi) {
157
            $postData['signature']['signature'] = 'invalid-signature';
158
        } else {
159
            $postData['signature'] = 'invalid-signature';
160
        }
161
162
        return $postData;
163
    }
164
165
    public function testShowLog()
166
    {
167
        $this->checkApplication();
168
169
        // Create a new client to browse the application
170
        $client = static::createClient();
171
        $client->followRedirects();
172
173
        $manager = $this->getEntityManager();
174
        $eventReop = $manager->getRepository("Azine\MailgunWebhooksBundle\Entity\MailgunEvent");
175
176
        $apiKey = $this->getContainer()->getParameter(AzineMailgunWebhooksExtension::PREFIX.'_'.AzineMailgunWebhooksExtension::API_KEY);
177
178
        // make sure there is plenty of data in the application to be able to verify paging
179
        if (sizeof($eventReop->findAll()) < 102) {
180
            TestHelper::addMailgunEvents($manager, 102, $apiKey);
181
        }
182
        $count = sizeof($eventReop->findAll());
183
184
        // view the list of events
185
        $pageSize = 25;
186
        $listUrl = substr($this->getRouter()->generate('mailgunevent_list', array('_locale' => 'en', 'page' => 1, 'pageSize' => $pageSize, 'clear' => true)), 13);
187
        $crawler = $this->loginUserIfRequired($client, $listUrl);
188
        $this->assertSame($pageSize + 1, $crawler->filter('.eventsTable tr')->count(), "$pageSize Mailgun events (+1 header row) expected on this page ($listUrl)!");
189
190
        // view a single event
191
        $link = $crawler->filter('.eventsTable tr a:first-child')->first()->link();
192
        $posLastSlash = strrpos($link->getUri(), '/');
193
        $posOfIdStart = strrpos($link->getUri(), '/', -6) + 1;
194
        $eventId = substr($link->getUri(), $posOfIdStart, $posLastSlash - $posOfIdStart);
195
        $crawler = $client->click($link);
196
        $this->assertSame(200, $client->getResponse()->getStatusCode(), 'Status 200 expected.');
197
        $this->assertSame($eventId, $crawler->filter('.mailgunEvent td')->eq(1)->html(), "Content should be the eventId ($eventId)".$client->getResponse()->getContent());
198
199
        // delete the event from show-page
200
        $link = $crawler->selectLink('Delete')->link();
201
        $crawler = $client->click($link);
0 ignored issues
show
Unused Code introduced by
The assignment to $crawler is dead and can be removed.
Loading history...
202
        $crawler = $client->followRedirect();
203
        // check that it is gone from the list
204
        $this->assertSame(0, $crawler->filter("#event$eventId")->count(), 'The deleted event should not be in the list anymore.');
205
206
        // filter the list for something
207
        $form = $crawler->selectButton('Filter')->form();
208
        $form['filter[eventType]']->select('delivered');
0 ignored issues
show
Bug introduced by
The method select() does not exist on Symfony\Component\DomCrawler\Field\FormField. It seems like you code against a sub-type of Symfony\Component\DomCrawler\Field\FormField such as Symfony\Component\DomCrawler\Field\ChoiceFormField. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

208
        $form['filter[eventType]']->/** @scrutinizer ignore-call */ 
209
                                    select('delivered');
Loading history...
209
        $crawler = $client->submit($form);
210
        $this->assertSame($crawler->filter('.eventsTable tr')->count() - 1, $crawler->filter(".eventsTable a:contains('delivered')")->count(), "There should only be 'delivered' events in the list");
211
212
        // delete entry with xmlHttpRequest
213
        $eventToDelete = $eventReop->findOneBy(array());
214
        $ajaxUrl = $this->getRouter()->generate('mailgunevent_delete_ajax', array('_locale' => 'en'));
215
        $client->request('POST', $ajaxUrl, array('eventId' => $eventToDelete->getId()), array(), array('HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'));
216
        $this->assertSame('{"success":true}', $client->getResponse()->getContent(), "JSON response expcted from $ajaxUrl. for event with id:".$eventToDelete->getId());
217
218
        // show/delete inexistent log entry
219
        $inexistentEventId = md5('123invalid');
220
        $url = substr($this->getRouter()->generate('mailgunevent_delete', array('_locale' => 'en', 'eventId' => $inexistentEventId)), 13);
221
        $client->request('GET', $url);
222
        $this->assertSame(404, $client->getResponse()->getStatusCode(), "404 expected for invalid eventId ($inexistentEventId).");
223
224
        $url = substr($this->getRouter()->generate('mailgunevent_show', array('_locale' => 'en', 'id' => $inexistentEventId)), 13);
225
        $client->request('GET', $url);
226
        $this->assertSame(404, $client->getResponse()->getStatusCode(), '404 expected.');
227
228
        // show inexistent page
229
        $maxPage = floor($count / $pageSize);
230
        $beyondListUrl = $this->getRouter()->generate('mailgunevent_list', array('_locale' => 'en', 'page' => $maxPage + 1, 'pageSize' => $pageSize, 'clear' => true));
231
        $crawler = $client->followRedirects();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $crawler is correct as $client->followRedirects() targeting Symfony\Component\Browse...ient::followRedirects() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
232
        $crawler = $client->request('GET', $beyondListUrl);
233
        $this->assertSame(2, $crawler->filter(".pagination .disabled:contains('Next')")->count(), 'Expected to be on the last page => the next button should be disabled.');
234
    }
235
236
    public function testWebViewLinks()
237
    {
238
        $this->checkApplication();
239
240
        // Create a new client to browse the application
241
        $client = static::createClient();
242
        $client->followRedirects();
243
244
        $manager = $this->getEntityManager();
245
        $eventReop = $manager->getRepository("Azine\MailgunWebhooksBundle\Entity\MailgunEvent");
246
247
        $apiKey = $this->getContainer()->getParameter(AzineMailgunWebhooksExtension::PREFIX.'_'.AzineMailgunWebhooksExtension::API_KEY);
248
249
        // make sure there is data in the application
250
        $events = $eventReop->findAll();
251
        if (sizeof($events) < 5) {
252
            TestHelper::addMailgunEvents($manager, 5, $apiKey);
253
            $events = $eventReop->findAll();
254
        }
255
256
        $webViewTokenName = $this->getContainer()->getParameter(AzineMailgunWebhooksExtension::PREFIX.'_'.AzineMailgunWebhooksExtension::WEB_VIEW_TOKEN);
257
258
        $testTokenValue = 'testValue';
259
        $messageHeader = array($webViewTokenName => $testTokenValue);
260
        $events[0]->setMessageHeaders(json_encode($messageHeader));
261
        $manager->persist($events[0]);
262
        $manager->flush();
263
264
        $events = $eventReop->findAll();
265
        $pageSize = count($events);
266
267
        $listUrl = substr($this->getRouter()->generate('mailgunevent_list', array('_locale' => 'en', 'page' => 1, 'pageSize' => $pageSize, 'clear' => true)), 13);
268
        $crawler = $this->loginUserIfRequired($client, $listUrl);
269
270
        $this->assertSame(1, $crawler->filter("ul:contains('$webViewTokenName')")->count(), 'There should be events with the webView headers in the list');
271
        $this->assertSame(1, $crawler->filter("ul a:contains('$testTokenValue')")->count(), 'There should be events with the webView links in the list');
272
    }
273
274
    /**
275
     * Load the url and login if required.
276
     *
277
     * @param string $url
278
     * @param string $username
279
     * @param Client $client
280
     *
281
     * @return Crawler $crawler of the page of the url or the page after the login
282
     */
283
    private function loginUserIfRequired(Client $client, $url, $username = 'dominik', $password = 'lkjlkjlkjlkj')
284
    {
285
        // try to get the url
286
        $client->followRedirects();
287
        $crawler = $client->request('GET', $url);
288
289
        $this->assertSame(200, $client->getResponse()->getStatusCode(), 'Status-Code 200 expected.');
290
291
        // if redirected to a login-page, login as admin-user
292
        if (5 == $crawler->filter('input')->count() && 1 == $crawler->filter('#username')->count() && 1 == $crawler->filter('#password')->count()) {
293
            // set the password of the admin
294
            $userProvider = $this->getContainer()->get('fos_user.user_provider.username_email');
295
            $user = $userProvider->loadUserByUsername($username);
296
            $user->setPlainPassword($password);
297
            $user->addRole('ROLE_ADMIN');
298
299
            $userManager = $this->getContainer()->get('fos_user.user_manager');
300
            $userManager->updateUser($user);
301
302
            $crawler = $crawler->filter("input[type='submit']");
303
            $form = $crawler->form();
304
            $form->get('_username')->setValue($username);
305
            $form->get('_password')->setValue($password);
306
            $crawler = $client->submit($form);
307
        }
308
309
        $this->assertSame(200, $client->getResponse()->getStatusCode(), 'Login failed.');
310
        $client->followRedirects(false);
311
312
        $this->assertStringEndsWith($url, $client->getRequest()->getUri(), "Login failed or not redirected to requested url: $url vs. ".$client->getRequest()->getUri());
313
314
        return $crawler;
315
    }
316
317
    /**
318
     * @var ContainerInterface
319
     */
320
    private $appContainer;
321
322
    /**
323
     * Get the current container.
324
     *
325
     * @return \Symfony\Component\DependencyInjection\ContainerInterface
326
     */
327
    private function getContainer()
328
    {
329
        if (null == $this->appContainer) {
330
            $this->appContainer = static::$kernel->getContainer();
331
        }
332
333
        return $this->appContainer;
334
    }
335
336
    /**
337
     * @return UrlGeneratorInterface
338
     */
339
    private function getRouter()
340
    {
341
        return $this->getContainer()->get('router');
342
    }
343
344
    /**
345
     * @return EntityManager
346
     */
347
    private function getEntityManager()
348
    {
349
        return $this->getContainer()->get('doctrine.orm.entity_manager');
350
    }
351
352
    /**
353
     * @return EventDispatcher
354
     */
355
    private function getEventDispatcher()
0 ignored issues
show
Unused Code introduced by
The method getEventDispatcher() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
356
    {
357
        return $this->getContainer()->get('event_dispatcher');
358
    }
359
360
    /**
361
     * Check if the current setup is a full application.
362
     * If not, mark the test as skipped else continue.
363
     */
364
    private function checkApplication()
365
    {
366
        try {
367
            static::$kernel = static::createKernel(array());
368
            //static::$kernel->boot();
369
        } catch (\RuntimeException $ex) {
370
            $this->markTestSkipped('There does not seem to be a full application available (e.g. running tests on travis.org). So this test is skipped.');
371
372
            return;
373
        }
374
    }
375
}
376