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