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