| 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
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
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. 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
|
|||||
| 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 |