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
![]() |
|||||
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
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
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. ![]() |
|||||
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
|
|||||
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'); |
||||
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(); |
||||
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
|
|||||
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 |