1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* @copyright 2016 Mautic Contributors. All rights reserved |
5
|
|
|
* @author Mautic |
6
|
|
|
* |
7
|
|
|
* @link http://mautic.org |
8
|
|
|
* |
9
|
|
|
* @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Mautic\PageBundle\Tests\Controller; |
13
|
|
|
|
14
|
|
|
use Mautic\CoreBundle\Entity\IpAddress; |
15
|
|
|
use Mautic\CoreBundle\Exception\InvalidDecodedStringException; |
16
|
|
|
use Mautic\CoreBundle\Factory\ModelFactory; |
17
|
|
|
use Mautic\CoreBundle\Helper\CookieHelper; |
18
|
|
|
use Mautic\CoreBundle\Helper\IpLookupHelper; |
19
|
|
|
use Mautic\CoreBundle\Security\Permissions\CorePermissions; |
20
|
|
|
use Mautic\CoreBundle\Templating\Helper\AnalyticsHelper; |
21
|
|
|
use Mautic\CoreBundle\Templating\Helper\AssetsHelper; |
22
|
|
|
use Mautic\LeadBundle\Entity\Lead; |
23
|
|
|
use Mautic\LeadBundle\Helper\PrimaryCompanyHelper; |
24
|
|
|
use Mautic\LeadBundle\Model\LeadModel; |
25
|
|
|
use Mautic\PageBundle\Controller\PublicController; |
26
|
|
|
use Mautic\PageBundle\Entity\Page; |
27
|
|
|
use Mautic\PageBundle\Entity\Redirect; |
28
|
|
|
use Mautic\PageBundle\Event\TrackingEvent; |
|
|
|
|
29
|
|
|
use Mautic\PageBundle\Model\PageModel; |
30
|
|
|
use Mautic\PageBundle\Model\RedirectModel; |
31
|
|
|
use PHPUnit\Framework\TestCase; |
32
|
|
|
use Symfony\Bridge\Monolog\Logger; |
33
|
|
|
use Symfony\Component\DependencyInjection\Container; |
34
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcher; |
35
|
|
|
use Symfony\Component\HttpFoundation\RedirectResponse; |
36
|
|
|
use Symfony\Component\HttpFoundation\Request; |
37
|
|
|
use Symfony\Component\Routing\Router; |
38
|
|
|
|
39
|
|
|
class PublicControllerTest extends TestCase |
40
|
|
|
{ |
41
|
|
|
/** @var PublicControllerTest */ |
42
|
|
|
private $controller; |
43
|
|
|
|
44
|
|
|
/** @var Container */ |
45
|
|
|
private $container; |
46
|
|
|
|
47
|
|
|
/** @var Logger */ |
48
|
|
|
private $logger; |
49
|
|
|
|
50
|
|
|
/** @var ModelFactory */ |
51
|
|
|
private $modelFactory; |
52
|
|
|
|
53
|
|
|
/** @var RedirectModel */ |
54
|
|
|
private $redirectModel; |
55
|
|
|
|
56
|
|
|
/** @var Redirect */ |
57
|
|
|
private $redirect; |
58
|
|
|
|
59
|
|
|
/** @var Request */ |
60
|
|
|
private $request; |
61
|
|
|
|
62
|
|
|
/** @var IpLookupHelper */ |
63
|
|
|
private $ipLookupHelper; |
64
|
|
|
|
65
|
|
|
/** @var IpAddress */ |
66
|
|
|
private $ipAddress; |
67
|
|
|
|
68
|
|
|
/** @var LeadModel */ |
69
|
|
|
private $leadModel; |
70
|
|
|
|
71
|
|
|
/** @var PageModel */ |
72
|
|
|
private $pageModel; |
73
|
|
|
|
74
|
|
|
/** @var PrimaryCompanyHelper */ |
75
|
|
|
private $primaryCompanyHelper; |
76
|
|
|
|
77
|
|
|
protected function setUp(): void |
78
|
|
|
{ |
79
|
|
|
$this->controller = new PublicController(); |
|
|
|
|
80
|
|
|
$this->request = new Request(); |
81
|
|
|
$this->container = $this->createMock(Container::class); |
|
|
|
|
82
|
|
|
$this->logger = $this->createMock(Logger::class); |
|
|
|
|
83
|
|
|
$this->modelFactory = $this->createMock(ModelFactory::class); |
|
|
|
|
84
|
|
|
$this->redirectModel = $this->createMock(RedirectModel::class); |
|
|
|
|
85
|
|
|
$this->redirect = $this->createMock(Redirect::class); |
|
|
|
|
86
|
|
|
$this->ipLookupHelper = $this->createMock(IpLookupHelper::class); |
|
|
|
|
87
|
|
|
$this->ipAddress = $this->createMock(IpAddress::class); |
|
|
|
|
88
|
|
|
$this->leadModel = $this->createMock(LeadModel::class); |
|
|
|
|
89
|
|
|
$this->pageModel = $this->createMock(PageModel::class); |
|
|
|
|
90
|
|
|
$this->primaryCompanyHelper = $this->createMock(PrimaryCompanyHelper::class); |
|
|
|
|
91
|
|
|
|
92
|
|
|
$this->controller->setContainer($this->container); |
93
|
|
|
$this->controller->setRequest($this->request); |
94
|
|
|
|
95
|
|
|
parent::setUp(); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Test that the appropriate variant is displayed based on hit counts and variant weights. |
100
|
|
|
*/ |
101
|
|
|
public function testVariantPageWeightsAreAppropriate() |
102
|
|
|
{ |
103
|
|
|
// Each of these should return the one with the greatest weight deficit based on |
104
|
|
|
// A = 50% |
105
|
|
|
// B = 25% |
106
|
|
|
// C = 25% |
107
|
|
|
|
108
|
|
|
// A = 0/50; B = 0/25; C = 0/25 |
109
|
|
|
$this->assertEquals('pageA', $this->getVariantContent(0, 0, 0)); |
110
|
|
|
|
111
|
|
|
// A = 100/50; B = 0/25; C = 0/25 |
112
|
|
|
$this->assertEquals('pageB', $this->getVariantContent(1, 0, 0)); |
113
|
|
|
|
114
|
|
|
// A = 50/50; B = 50/25; C = 0/25; |
115
|
|
|
$this->assertEquals('pageC', $this->getVariantContent(1, 1, 0)); |
116
|
|
|
|
117
|
|
|
// A = 33/50; B = 33/25; C = 33/25; |
118
|
|
|
$this->assertEquals('pageA', $this->getVariantContent(1, 1, 1)); |
119
|
|
|
|
120
|
|
|
// A = 66/50; B = 33/25; C = 0/25 |
121
|
|
|
$this->assertEquals('pageC', $this->getVariantContent(2, 1, 0)); |
122
|
|
|
|
123
|
|
|
// A = 50/50; B = 25/25; C = 25/25 |
124
|
|
|
$this->assertEquals('pageA', $this->getVariantContent(2, 1, 1)); |
125
|
|
|
|
126
|
|
|
// A = 33/50; B = 66/50; C = 0/25 |
127
|
|
|
$this->assertEquals('pageC', $this->getVariantContent(1, 2, 0)); |
128
|
|
|
|
129
|
|
|
// A = 25/50; B = 50/50; C = 25/25 |
130
|
|
|
$this->assertEquals('pageA', $this->getVariantContent(1, 2, 1)); |
131
|
|
|
|
132
|
|
|
// A = 55/50; B = 18/25; C = 27/25 |
133
|
|
|
$this->assertEquals('pageB', $this->getVariantContent(6, 2, 3)); |
134
|
|
|
|
135
|
|
|
// A = 50/50; B = 25/25; C = 25/25 |
136
|
|
|
$this->assertEquals('pageA', $this->getVariantContent(6, 3, 3)); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* @param $aCount |
141
|
|
|
* @param $bCount |
142
|
|
|
* @param $cCount |
143
|
|
|
* |
144
|
|
|
* @return string |
145
|
|
|
*/ |
146
|
|
|
private function getVariantContent($aCount, $bCount, $cCount) |
147
|
|
|
{ |
148
|
|
|
$pageEntityB = $this->getMockBuilder(Page::class) |
149
|
|
|
->disableOriginalConstructor() |
150
|
|
|
->getMock(); |
151
|
|
|
$pageEntityB->method('getId') |
152
|
|
|
->will($this->returnValue(2)); |
153
|
|
|
$pageEntityB->method('isPublished') |
154
|
|
|
->will($this->returnValue(true)); |
155
|
|
|
$pageEntityB->method('getVariantHits') |
156
|
|
|
->will($this->returnValue($bCount)); |
157
|
|
|
$pageEntityB->method('getTranslations') |
158
|
|
|
->will($this->returnValue([])); |
159
|
|
|
$pageEntityB->method('isTranslation') |
160
|
|
|
->will($this->returnValue(false)); |
161
|
|
|
$pageEntityB->method('getContent') |
162
|
|
|
->will($this->returnValue(null)); |
163
|
|
|
$pageEntityB->method('getCustomHtml') |
164
|
|
|
->will($this->returnValue('pageB')); |
165
|
|
|
$pageEntityB->method('getVariantSettings') |
166
|
|
|
->will($this->returnValue(['weight' => '25'])); |
167
|
|
|
|
168
|
|
|
$pageEntityC = $this->getMockBuilder(Page::class) |
169
|
|
|
->disableOriginalConstructor() |
170
|
|
|
->getMock(); |
171
|
|
|
$pageEntityC->method('getId') |
172
|
|
|
->will($this->returnValue(3)); |
173
|
|
|
$pageEntityC->method('isPublished') |
174
|
|
|
->will($this->returnValue(true)); |
175
|
|
|
$pageEntityC->method('getVariantHits') |
176
|
|
|
->will($this->returnValue($cCount)); |
177
|
|
|
$pageEntityC->method('getTranslations') |
178
|
|
|
->will($this->returnValue([])); |
179
|
|
|
$pageEntityC->method('isTranslation') |
180
|
|
|
->will($this->returnValue(false)); |
181
|
|
|
$pageEntityC->method('getContent') |
182
|
|
|
->will($this->returnValue(null)); |
183
|
|
|
$pageEntityC->method('getCustomHtml') |
184
|
|
|
->will($this->returnValue('pageC')); |
185
|
|
|
$pageEntityC->method('getVariantSettings') |
186
|
|
|
->will($this->returnValue(['weight' => '25'])); |
187
|
|
|
|
188
|
|
|
$pageEntityA = $this->getMockBuilder(Page::class) |
189
|
|
|
->disableOriginalConstructor() |
190
|
|
|
->getMock(); |
191
|
|
|
$pageEntityA->method('getId') |
192
|
|
|
->will($this->returnValue(1)); |
193
|
|
|
$pageEntityA->method('isPublished') |
194
|
|
|
->will($this->returnValue(true)); |
195
|
|
|
$pageEntityA->method('getVariants') |
196
|
|
|
->will($this->returnValue([$pageEntityA, [2 => $pageEntityB, 3 => $pageEntityC]])); |
197
|
|
|
$pageEntityA->method('getVariantHits') |
198
|
|
|
->will($this->returnValue($aCount)); |
199
|
|
|
$pageEntityA->method('getTranslations') |
200
|
|
|
->will($this->returnValue([])); |
201
|
|
|
$pageEntityA->method('isTranslation') |
202
|
|
|
->will($this->returnValue(false)); |
203
|
|
|
$pageEntityA->method('getContent') |
204
|
|
|
->will($this->returnValue(null)); |
205
|
|
|
$pageEntityA->method('getCustomHtml') |
206
|
|
|
->will($this->returnValue('pageA')); |
207
|
|
|
$pageEntityA->method('getVariantSettings') |
208
|
|
|
->will($this->returnValue(['weight' => '50'])); |
209
|
|
|
|
210
|
|
|
$cookieHelper = $this->getMockBuilder(CookieHelper::class) |
211
|
|
|
->disableOriginalConstructor() |
212
|
|
|
->getMock(); |
213
|
|
|
|
214
|
|
|
$ipHelper = $this->getMockBuilder(IpLookupHelper::class) |
215
|
|
|
->disableOriginalConstructor() |
216
|
|
|
->getMock(); |
217
|
|
|
$ipHelper->method('getIpAddress') |
218
|
|
|
->will($this->returnValue(new IpAddress())); |
219
|
|
|
|
220
|
|
|
$assetHelper = $this->getMockBuilder(AssetsHelper::class) |
221
|
|
|
->disableOriginalConstructor() |
222
|
|
|
->getMock(); |
223
|
|
|
|
224
|
|
|
$mauticSecurity = $this->getMockBuilder(CorePermissions::class) |
225
|
|
|
->disableOriginalConstructor() |
226
|
|
|
->getMock(); |
227
|
|
|
$mauticSecurity->method('hasEntityAccess') |
228
|
|
|
->will($this->returnValue(false)); |
229
|
|
|
|
230
|
|
|
$analyticsHelper = $this->getMockBuilder(AnalyticsHelper::class) |
231
|
|
|
->disableOriginalConstructor() |
232
|
|
|
->getMock(); |
233
|
|
|
|
234
|
|
|
$pageModel = $this->getMockBuilder(PageModel::class) |
235
|
|
|
->disableOriginalConstructor() |
236
|
|
|
->getMock(); |
237
|
|
|
$pageModel->method('getHitQuery') |
238
|
|
|
->will($this->returnValue([])); |
239
|
|
|
$pageModel->method('getEntityBySlugs') |
240
|
|
|
->will($this->returnValue($pageEntityA)); |
241
|
|
|
$pageModel->method('hitPage') |
242
|
|
|
->will($this->returnValue(true)); |
243
|
|
|
|
244
|
|
|
$leadModel = $this->getMockBuilder(LeadModel::class) |
245
|
|
|
->disableOriginalConstructor() |
246
|
|
|
->getMock(); |
247
|
|
|
$leadModel->method('getContactFromRequest') |
248
|
|
|
->will($this->returnValue(new Lead())); |
249
|
|
|
|
250
|
|
|
$router = $this->getMockBuilder(Router::class) |
251
|
|
|
->disableOriginalConstructor() |
252
|
|
|
->getMock(); |
253
|
|
|
|
254
|
|
|
$dispatcher = new EventDispatcher(); |
255
|
|
|
|
256
|
|
|
$modelFactory = $this->getMockBuilder(ModelFactory::class) |
257
|
|
|
->disableOriginalConstructor() |
258
|
|
|
->getMock(); |
259
|
|
|
$modelFactory->method('getModel') |
260
|
|
|
->will( |
261
|
|
|
$this->returnValueMap( |
262
|
|
|
[ |
263
|
|
|
['page', $pageModel], |
264
|
|
|
['lead', $leadModel], |
265
|
|
|
] |
266
|
|
|
) |
267
|
|
|
); |
268
|
|
|
|
269
|
|
|
$container = $this->getMockBuilder(Container::class) |
270
|
|
|
->disableOriginalConstructor() |
271
|
|
|
->getMock(); |
272
|
|
|
$container->method('has') |
273
|
|
|
->will($this->returnValue(true)); |
274
|
|
|
$container->method('get') |
275
|
|
|
->will( |
276
|
|
|
$this->returnValueMap( |
277
|
|
|
[ |
278
|
|
|
['mautic.helper.cookie', Container::EXCEPTION_ON_INVALID_REFERENCE, $cookieHelper], |
279
|
|
|
['templating.helper.assets', Container::EXCEPTION_ON_INVALID_REFERENCE, $assetHelper], |
280
|
|
|
['mautic.helper.ip_lookup', Container::EXCEPTION_ON_INVALID_REFERENCE, $ipHelper], |
281
|
|
|
['mautic.security', Container::EXCEPTION_ON_INVALID_REFERENCE, $mauticSecurity], |
282
|
|
|
['mautic.helper.template.analytics', Container::EXCEPTION_ON_INVALID_REFERENCE, $analyticsHelper], |
283
|
|
|
['mautic.page.model.page', Container::EXCEPTION_ON_INVALID_REFERENCE, $pageModel], |
284
|
|
|
['mautic.lead.model.lead', Container::EXCEPTION_ON_INVALID_REFERENCE, $leadModel], |
285
|
|
|
['router', Container::EXCEPTION_ON_INVALID_REFERENCE, $router], |
286
|
|
|
['event_dispatcher', Container::EXCEPTION_ON_INVALID_REFERENCE, $dispatcher], |
287
|
|
|
['mautic.model.factory', Container::EXCEPTION_ON_INVALID_REFERENCE, $modelFactory], |
288
|
|
|
] |
289
|
|
|
) |
290
|
|
|
); |
291
|
|
|
|
292
|
|
|
$this->request->attributes->set('ignore_mismatch', true); |
293
|
|
|
|
294
|
|
|
$this->controller->setContainer($container); |
|
|
|
|
295
|
|
|
|
296
|
|
|
$response = $this->controller->indexAction('/page/a', $this->request); |
|
|
|
|
297
|
|
|
|
298
|
|
|
return $response->getContent(); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
public function testThatInvalidClickTroughGetsProcessed() |
302
|
|
|
{ |
303
|
|
|
$redirectId = 'someRedirectId'; |
304
|
|
|
$clickTrough = 'someClickTroughValue'; |
305
|
|
|
$redirectUrl = 'https://someurl.test/'; |
306
|
|
|
|
307
|
|
|
$this->redirectModel->expects($this->once()) |
|
|
|
|
308
|
|
|
->method('getRedirectById') |
309
|
|
|
->with($redirectId) |
310
|
|
|
->willReturn($this->redirect); |
311
|
|
|
|
312
|
|
|
$this->modelFactory->expects($this->exactly(3)) |
|
|
|
|
313
|
|
|
->method('getModel') |
314
|
|
|
->withConsecutive(['page.redirect'], ['lead'], ['page']) |
315
|
|
|
->willReturnOnConsecutiveCalls($this->redirectModel, $this->leadModel, $this->pageModel); |
316
|
|
|
|
317
|
|
|
$this->redirect->expects($this->once()) |
|
|
|
|
318
|
|
|
->method('isPublished') |
319
|
|
|
->with(false) |
320
|
|
|
->willReturn(true); |
321
|
|
|
|
322
|
|
|
$this->redirect->expects($this->once()) |
323
|
|
|
->method('getUrl') |
324
|
|
|
->willReturn($redirectUrl); |
325
|
|
|
|
326
|
|
|
$this->ipLookupHelper->expects($this->once()) |
|
|
|
|
327
|
|
|
->method('getIpAddress') |
328
|
|
|
->willReturn($this->ipAddress); |
329
|
|
|
|
330
|
|
|
$this->ipAddress->expects($this->once()) |
|
|
|
|
331
|
|
|
->method('isTrackable') |
332
|
|
|
->willReturn(true); |
333
|
|
|
|
334
|
|
|
$getContactFromRequestCallback = function ($queryFields) use ($clickTrough) { |
335
|
|
|
if (empty($queryFields)) { |
336
|
|
|
return null; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
throw new InvalidDecodedStringException($clickTrough); |
340
|
|
|
}; |
341
|
|
|
|
342
|
|
|
$this->leadModel->expects($this->exactly(2)) |
|
|
|
|
343
|
|
|
->method('getContactFromRequest') |
344
|
|
|
->will($this->returnCallback($getContactFromRequestCallback)); |
345
|
|
|
|
346
|
|
|
$this->container->expects($this->exactly(6)) |
|
|
|
|
347
|
|
|
->method('get') |
348
|
|
|
->withConsecutive( |
349
|
|
|
['monolog.logger.mautic'], |
350
|
|
|
['mautic.model.factory'], |
351
|
|
|
['mautic.helper.ip_lookup'], |
352
|
|
|
['mautic.model.factory'], |
353
|
|
|
['mautic.model.factory'], |
354
|
|
|
['mautic.lead.helper.primary_company'] |
355
|
|
|
) |
356
|
|
|
->willReturnOnConsecutiveCalls( |
357
|
|
|
$this->logger, |
358
|
|
|
$this->modelFactory, |
359
|
|
|
$this->ipLookupHelper, |
360
|
|
|
$this->modelFactory, |
361
|
|
|
$this->modelFactory, |
362
|
|
|
$this->primaryCompanyHelper |
363
|
|
|
); |
364
|
|
|
|
365
|
|
|
$this->request->query->set('ct', $clickTrough); |
366
|
|
|
|
367
|
|
|
$response = $this->controller->redirectAction($redirectId); |
|
|
|
|
368
|
|
|
$this->assertInstanceOf(RedirectResponse::class, $response); |
369
|
|
|
} |
370
|
|
|
} |
371
|
|
|
|
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths