Merge pull request #15815 from artfulrobot/issue-1108-fix-unsubscribe
[civicrm-core.git] / tests / phpunit / CRM / Core / CommunityMessagesTest.php
CommitLineData
ecbe1139 1<?php
ecbe1139
TO
2/*
3 +--------------------------------------------------------------------+
7d61e75f 4 | Copyright CiviCRM LLC. All rights reserved. |
ecbe1139 5 | |
7d61e75f
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
ecbe1139 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
ecbe1139 11
e9479dcf
EM
12/**
13 * Class CRM_Core_CommunityMessagesTest
acb109b7 14 * @group headless
e9479dcf 15 */
ecbe1139 16class CRM_Core_CommunityMessagesTest extends CiviUnitTestCase {
6a522e8b
TO
17
18 /**
19 * The max difference between two times such that they should be
20 * treated as equals (expressed in seconds).
21 */
22 const APPROX_TIME_EQUALITY = 2;
23
ecbe1139
TO
24 /**
25 * @var CRM_Utils_Cache_Interface
26 */
27 protected $cache;
28
29 /**
30 * @var array list of possible web responses
31 */
376d8353 32 protected static $webResponses = NULL;
ecbe1139 33
4cbe18b8
EM
34 /**
35 * @return array
36 */
376d8353
TO
37 public static function initWebResponses() {
38 if (self::$webResponses === NULL) {
9099cab3
CW
39 self::$webResponses = [
40 'http-error' => [
376d8353 41 CRM_Utils_HttpClient::STATUS_DL_ERROR,
21dfd5f5 42 NULL,
9099cab3
CW
43 ],
44 'bad-json' => [
376d8353 45 CRM_Utils_HttpClient::STATUS_OK,
21dfd5f5 46 '<html>this is not json!</html>',
9099cab3
CW
47 ],
48 'invalid-ttl-document' => [
376d8353 49 CRM_Utils_HttpClient::STATUS_OK,
9099cab3 50 json_encode([
39b959db
SL
51 // not an integer!
52 'ttl' => 'z',
53 // not an integer!
54 'retry' => 'z',
9099cab3
CW
55 'messages' => [
56 [
376d8353 57 'markup' => '<h1>Invalid document</h1>',
9099cab3
CW
58 ],
59 ],
60 ]),
61 ],
62 'first-valid-response' => [
376d8353 63 CRM_Utils_HttpClient::STATUS_OK,
9099cab3 64 json_encode([
376d8353
TO
65 'ttl' => 600,
66 'retry' => 600,
9099cab3
CW
67 'messages' => [
68 [
3dcd50d8 69 'markup' => '<h1>First valid response</h1>',
9099cab3
CW
70 ],
71 ],
72 ]),
73 ],
74 'second-valid-response' => [
376d8353 75 CRM_Utils_HttpClient::STATUS_OK,
9099cab3 76 json_encode([
376d8353
TO
77 'ttl' => 600,
78 'retry' => 600,
9099cab3
CW
79 'messages' => [
80 [
3dcd50d8 81 'markup' => '<h1>Second valid response</h1>',
9099cab3
CW
82 ],
83 ],
84 ]),
85 ],
86 'two-messages' => [
dc92f2f8 87 CRM_Utils_HttpClient::STATUS_OK,
9099cab3 88 json_encode([
dc92f2f8
TO
89 'ttl' => 600,
90 'retry' => 600,
9099cab3
CW
91 'messages' => [
92 [
dc92f2f8 93 'markup' => '<h1>One</h1>',
9099cab3
CW
94 'components' => ['CiviMail'],
95 ],
96 [
dc92f2f8 97 'markup' => '<h1>Two</h1>',
9099cab3
CW
98 'components' => ['CiviMail'],
99 ],
100 ],
101 ]),
102 ],
103 'two-messages-halfbadcomp' => [
dc92f2f8 104 CRM_Utils_HttpClient::STATUS_OK,
9099cab3 105 json_encode([
dc92f2f8
TO
106 'ttl' => 600,
107 'retry' => 600,
9099cab3
CW
108 'messages' => [
109 [
dc92f2f8 110 'markup' => '<h1>One</h1>',
9099cab3
CW
111 'components' => ['NotARealComponent'],
112 ],
113 [
dc92f2f8 114 'markup' => '<h1>Two</h1>',
9099cab3
CW
115 'components' => ['CiviMail'],
116 ],
117 ],
118 ]),
119 ],
120 ];
376d8353
TO
121 }
122 return self::$webResponses;
123 }
124
125 public function setUp() {
126 parent::setUp();
9099cab3 127 $this->cache = new CRM_Utils_Cache_Arraycache([]);
376d8353 128 self::initWebResponses();
ecbe1139
TO
129 }
130
131 public function tearDown() {
132 parent::tearDown();
133 CRM_Utils_Time::resetTime();
134 }
135
acdf9ae2
TO
136 /**
137 * A list of bad web-responses; in general, whenever the downloader
138 * encounters one of these bad responses, it should ignore the
139 * document, retain the old data, and retry again later.
140 *
141 * @return array
142 */
143 public function badWebResponses() {
376d8353 144 self::initWebResponses();
9099cab3
CW
145 $result = [
146 [self::$webResponses['http-error']],
147 [self::$webResponses['bad-json']],
148 [self::$webResponses['invalid-ttl-document']],
149 ];
376d8353
TO
150 return $result;
151 }
152
847c93ac
TO
153 public function testIsEnabled() {
154 $communityMessages = new CRM_Core_CommunityMessages(
155 $this->cache,
156 $this->expectNoHttpRequest()
157 );
158 $this->assertTrue($communityMessages->isEnabled());
159 }
160
161 public function testIsEnabled_false() {
e8977170
TO
162 $communityMessages = new CRM_Core_CommunityMessages(
163 $this->cache,
164 $this->expectNoHttpRequest(),
165 FALSE
166 );
847c93ac 167 $this->assertFalse($communityMessages->isEnabled());
e8977170
TO
168 }
169
ecbe1139
TO
170 /**
171 * Download a document; after the set expiration period, download again.
172 */
e8977170 173 public function testGetDocument_NewOK_CacheOK_UpdateOK() {
ecbe1139
TO
174 // first try, good response
175 CRM_Utils_Time::setTime('2013-03-01 10:00:00');
176 $communityMessages = new CRM_Core_CommunityMessages(
177 $this->cache,
3dcd50d8 178 $this->expectOneHttpRequest(self::$webResponses['first-valid-response'])
ecbe1139
TO
179 );
180 $doc1 = $communityMessages->getDocument();
3dcd50d8 181 $this->assertEquals('<h1>First valid response</h1>', $doc1['messages'][0]['markup']);
6a522e8b 182 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires'], self::APPROX_TIME_EQUALITY);
ecbe1139
TO
183
184 // second try, $doc1 hasn't expired yet, so still use it
185 CRM_Utils_Time::setTime('2013-03-01 10:09:00');
186 $communityMessages = new CRM_Core_CommunityMessages(
187 $this->cache,
188 $this->expectNoHttpRequest()
189 );
190 $doc2 = $communityMessages->getDocument();
3dcd50d8 191 $this->assertEquals('<h1>First valid response</h1>', $doc2['messages'][0]['markup']);
6a522e8b 192 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc2['expires'], self::APPROX_TIME_EQUALITY);
ecbe1139
TO
193
194 // third try, $doc1 expired, update it
39b959db
SL
195 // more than 2 hours later (DEFAULT_RETRY)
196 CRM_Utils_Time::setTime('2013-03-01 12:00:02');
ecbe1139
TO
197 $communityMessages = new CRM_Core_CommunityMessages(
198 $this->cache,
3dcd50d8 199 $this->expectOneHttpRequest(self::$webResponses['second-valid-response'])
ecbe1139
TO
200 );
201 $doc3 = $communityMessages->getDocument();
3dcd50d8 202 $this->assertEquals('<h1>Second valid response</h1>', $doc3['messages'][0]['markup']);
6a522e8b 203 $this->assertApproxEquals(strtotime('2013-03-01 12:10:02'), $doc3['expires'], self::APPROX_TIME_EQUALITY);
ecbe1139
TO
204 }
205
206 /**
376d8353
TO
207 * First download attempt fails (due to some bad web request).
208 * Store the NACK and retry after the default time period (DEFAULT_RETRY).
209 *
acdf9ae2 210 * @dataProvider badWebResponses
e16033b4
TO
211 * @param array $badWebResponse
212 * Description of a web request that returns some kind of failure.
ecbe1139 213 */
acdf9ae2
TO
214 public function testGetDocument_NewFailure_CacheOK_UpdateOK($badWebResponse) {
215 $this->assertNotEmpty($badWebResponse);
376d8353 216
ecbe1139
TO
217 // first try, bad response
218 CRM_Utils_Time::setTime('2013-03-01 10:00:00');
219 $communityMessages = new CRM_Core_CommunityMessages(
220 $this->cache,
acdf9ae2 221 $this->expectOneHttpRequest($badWebResponse)
ecbe1139
TO
222 );
223 $doc1 = $communityMessages->getDocument();
9099cab3 224 $this->assertEquals([], $doc1['messages']);
ecbe1139
TO
225 $this->assertTrue($doc1['expires'] > CRM_Utils_Time::getTimeRaw());
226
227 // second try, $doc1 hasn't expired yet, so still use it
228 CRM_Utils_Time::setTime('2013-03-01 10:09:00');
229 $communityMessages = new CRM_Core_CommunityMessages(
230 $this->cache,
231 $this->expectNoHttpRequest()
232 );
233 $doc2 = $communityMessages->getDocument();
9099cab3 234 $this->assertEquals([], $doc2['messages']);
ecbe1139
TO
235 $this->assertEquals($doc1['expires'], $doc2['expires']);
236
237 // third try, $doc1 expired, try again, get a good response
39b959db
SL
238 // more than 2 hours later (DEFAULT_RETRY)
239 CRM_Utils_Time::setTime('2013-03-01 12:00:02');
ecbe1139
TO
240 $communityMessages = new CRM_Core_CommunityMessages(
241 $this->cache,
3dcd50d8 242 $this->expectOneHttpRequest(self::$webResponses['first-valid-response'])
ecbe1139
TO
243 );
244 $doc3 = $communityMessages->getDocument();
3dcd50d8 245 $this->assertEquals('<h1>First valid response</h1>', $doc3['messages'][0]['markup']);
ecbe1139
TO
246 $this->assertTrue($doc3['expires'] > CRM_Utils_Time::getTimeRaw());
247 }
248
249 /**
250 * First download of new doc is OK.
acdf9ae2
TO
251 * The update fails (due to some bad web response).
252 * The old data is retained in the cache.
253 * The failure eventually expires.
254 * A new update succeeds.
376d8353 255 *
acdf9ae2 256 * @dataProvider badWebResponses
e16033b4
TO
257 * @param array $badWebResponse
258 * Description of a web request that returns some kind of failure.
ecbe1139 259 */
acdf9ae2
TO
260 public function testGetDocument_NewOK_UpdateFailure_CacheOK_UpdateOK($badWebResponse) {
261 $this->assertNotEmpty($badWebResponse);
376d8353 262
ecbe1139
TO
263 // first try, good response
264 CRM_Utils_Time::setTime('2013-03-01 10:00:00');
265 $communityMessages = new CRM_Core_CommunityMessages(
266 $this->cache,
3dcd50d8 267 $this->expectOneHttpRequest(self::$webResponses['first-valid-response'])
ecbe1139
TO
268 );
269 $doc1 = $communityMessages->getDocument();
3dcd50d8 270 $this->assertEquals('<h1>First valid response</h1>', $doc1['messages'][0]['markup']);
6a522e8b 271 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires'], self::APPROX_TIME_EQUALITY);
ecbe1139
TO
272
273 // second try, $doc1 has expired; bad response; keep old data
39b959db
SL
274 // more than 2 hours later (DEFAULT_RETRY)
275 CRM_Utils_Time::setTime('2013-03-01 12:00:02');
ecbe1139
TO
276 $communityMessages = new CRM_Core_CommunityMessages(
277 $this->cache,
acdf9ae2 278 $this->expectOneHttpRequest($badWebResponse)
ecbe1139
TO
279 );
280 $doc2 = $communityMessages->getDocument();
3dcd50d8 281 $this->assertEquals('<h1>First valid response</h1>', $doc2['messages'][0]['markup']);
ecbe1139
TO
282 $this->assertTrue($doc2['expires'] > CRM_Utils_Time::getTimeRaw());
283
284 // third try, $doc2 hasn't expired yet; no request; keep old data
285 CRM_Utils_Time::setTime('2013-03-01 12:09:00');
286 $communityMessages = new CRM_Core_CommunityMessages(
287 $this->cache,
288 $this->expectNoHttpRequest()
289 );
290 $doc3 = $communityMessages->getDocument();
3dcd50d8 291 $this->assertEquals('<h1>First valid response</h1>', $doc3['messages'][0]['markup']);
ecbe1139
TO
292 $this->assertEquals($doc2['expires'], $doc3['expires']);
293
294 // fourth try, $doc2 has expired yet; new request; replace data
295 CRM_Utils_Time::setTime('2013-03-01 12:10:02');
296 $communityMessages = new CRM_Core_CommunityMessages(
297 $this->cache,
3dcd50d8 298 $this->expectOneHttpRequest(self::$webResponses['second-valid-response'])
ecbe1139
TO
299 );
300 $doc4 = $communityMessages->getDocument();
3dcd50d8 301 $this->assertEquals('<h1>Second valid response</h1>', $doc4['messages'][0]['markup']);
6a522e8b 302 $this->assertApproxEquals(strtotime('2013-03-01 12:20:02'), $doc4['expires'], self::APPROX_TIME_EQUALITY);
ecbe1139
TO
303 }
304
dc92f2f8 305 /**
eceb18cc 306 * Randomly pick among two options.
dc92f2f8
TO
307 */
308 public function testPick_rand() {
309 $communityMessages = new CRM_Core_CommunityMessages(
310 $this->cache,
311 $this->expectOneHttpRequest(self::$webResponses['two-messages'])
312 );
313 $doc1 = $communityMessages->getDocument();
314 $this->assertEquals('<h1>One</h1>', $doc1['messages'][0]['markup']);
315 $this->assertEquals('<h1>Two</h1>', $doc1['messages'][1]['markup']);
316
317 // randomly pick many times
2e794830 318 $trials = 80;
39b959db 319 // array($message => $count)
9099cab3 320 $freq = [];
dc92f2f8
TO
321 for ($i = 0; $i < $trials; $i++) {
322 $message = $communityMessages->pick();
af2dfa5c 323 $freq[$message['markup']] = CRM_Utils_Array::value($message['markup'], $freq, 0) + 1;
dc92f2f8
TO
324 }
325
326 // assert the probabilities
2e794830
TO
327 $this->assertApproxEquals(0.5, $freq['<h1>One</h1>'] / $trials, 0.3);
328 $this->assertApproxEquals(0.5, $freq['<h1>Two</h1>'] / $trials, 0.3);
dc92f2f8
TO
329 $this->assertEquals($trials, $freq['<h1>One</h1>'] + $freq['<h1>Two</h1>']);
330 }
331
332 /**
333 * When presented with two options using component filters, always
334 * choose the one which references an active component.
335 */
336 public function testPick_componentFilter() {
337 $communityMessages = new CRM_Core_CommunityMessages(
338 $this->cache,
339 $this->expectOneHttpRequest(self::$webResponses['two-messages-halfbadcomp'])
340 );
341 $doc1 = $communityMessages->getDocument();
342 $this->assertEquals('<h1>One</h1>', $doc1['messages'][0]['markup']);
343 $this->assertEquals('<h1>Two</h1>', $doc1['messages'][1]['markup']);
344
345 // randomly pick many times
346 $trials = 10;
39b959db 347 // array($message => $count)
9099cab3 348 $freq = [];
dc92f2f8
TO
349 for ($i = 0; $i < $trials; $i++) {
350 $message = $communityMessages->pick();
af2dfa5c 351 $freq[$message['markup']] = CRM_Utils_Array::value($message['markup'], $freq, 0) + 1;
dc92f2f8
TO
352 }
353
354 $this->assertEquals($trials, $freq['<h1>Two</h1>']);
355 }
356
00be9182 357 public function testEvalMarkup() {
d1b65097
TO
358 $communityMessages = new CRM_Core_CommunityMessages(
359 $this->cache,
360 $this->expectNoHttpRequest()
361 );
362 $this->assertEquals('cms=UnitTests cms=UnitTests', $communityMessages->evalMarkup('cms=%%uf%% cms={{uf}}'));
363 }
364
ecbe1139
TO
365 /**
366 * Generate a mock HTTP client with the expectation that it is never called.
367 *
a6439b6a 368 * @return CRM_Utils_HttpClient|PHPUnit\Framework\MockObject\MockObject
ecbe1139
TO
369 */
370 protected function expectNoHttpRequest() {
dcadbba4
SL
371 $mockFunction = $this->mockMethod;
372 $client = $this->$mockFunction('CRM_Utils_HttpClient');
ecbe1139
TO
373 $client->expects($this->never())
374 ->method('get');
375 return $client;
376 }
377
378 /**
379 * Generate a mock HTTP client with the expectation that it is called once.
380 *
2a6da8d7
EM
381 * @param $response
382 *
a6439b6a 383 * @return CRM_Utils_HttpClient|PHPUnit\Framework\MockObject\MockObject
ecbe1139
TO
384 */
385 protected function expectOneHttpRequest($response) {
dcadbba4
SL
386 $mockFunction = $this->mockMethod;
387 $client = $this->$mockFunction('CRM_Utils_HttpClient');
ecbe1139
TO
388 $client->expects($this->once())
389 ->method('get')
390 ->will($this->returnValue($response));
391 return $client;
392 }
96025800 393
ecbe1139 394}