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