3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
29 * Class CRM_Core_CommunityMessagesTest
32 class CRM_Core_CommunityMessagesTest
extends CiviUnitTestCase
{
35 * The max difference between two times such that they should be
36 * treated as equals (expressed in seconds).
38 const APPROX_TIME_EQUALITY
= 2;
41 * @var CRM_Utils_Cache_Interface
46 * @var array list of possible web responses
48 protected static $webResponses = NULL;
53 public static function initWebResponses() {
54 if (self
::$webResponses === NULL) {
55 self
::$webResponses = [
57 CRM_Utils_HttpClient
::STATUS_DL_ERROR
,
61 CRM_Utils_HttpClient
::STATUS_OK
,
62 '<html>this is not json!</html>',
64 'invalid-ttl-document' => [
65 CRM_Utils_HttpClient
::STATUS_OK
,
73 'markup' => '<h1>Invalid document</h1>',
78 'first-valid-response' => [
79 CRM_Utils_HttpClient
::STATUS_OK
,
85 'markup' => '<h1>First valid response</h1>',
90 'second-valid-response' => [
91 CRM_Utils_HttpClient
::STATUS_OK
,
97 'markup' => '<h1>Second valid response</h1>',
103 CRM_Utils_HttpClient
::STATUS_OK
,
109 'markup' => '<h1>One</h1>',
110 'components' => ['CiviMail'],
113 'markup' => '<h1>Two</h1>',
114 'components' => ['CiviMail'],
119 'two-messages-halfbadcomp' => [
120 CRM_Utils_HttpClient
::STATUS_OK
,
126 'markup' => '<h1>One</h1>',
127 'components' => ['NotARealComponent'],
130 'markup' => '<h1>Two</h1>',
131 'components' => ['CiviMail'],
138 return self
::$webResponses;
141 public function setUp() {
143 $this->cache
= new CRM_Utils_Cache_Arraycache([]);
144 self
::initWebResponses();
147 public function tearDown() {
149 CRM_Utils_Time
::resetTime();
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.
159 public function badWebResponses() {
160 self
::initWebResponses();
162 [self
::$webResponses['http-error']],
163 [self
::$webResponses['bad-json']],
164 [self
::$webResponses['invalid-ttl-document']],
169 public function testIsEnabled() {
170 $communityMessages = new CRM_Core_CommunityMessages(
172 $this->expectNoHttpRequest()
174 $this->assertTrue($communityMessages->isEnabled());
177 public function testIsEnabled_false() {
178 $communityMessages = new CRM_Core_CommunityMessages(
180 $this->expectNoHttpRequest(),
183 $this->assertFalse($communityMessages->isEnabled());
187 * Download a document; after the set expiration period, download again.
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(
194 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
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
);
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(
204 $this->expectNoHttpRequest()
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
);
210 // third try, $doc1 expired, update it
211 // more than 2 hours later (DEFAULT_RETRY)
212 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
213 $communityMessages = new CRM_Core_CommunityMessages(
215 $this->expectOneHttpRequest(self
::$webResponses['second-valid-response'])
217 $doc3 = $communityMessages->getDocument();
218 $this->assertEquals('<h1>Second valid response</h1>', $doc3['messages'][0]['markup']);
219 $this->assertApproxEquals(strtotime('2013-03-01 12:10:02'), $doc3['expires'], self
::APPROX_TIME_EQUALITY
);
223 * First download attempt fails (due to some bad web request).
224 * Store the NACK and retry after the default time period (DEFAULT_RETRY).
226 * @dataProvider badWebResponses
227 * @param array $badWebResponse
228 * Description of a web request that returns some kind of failure.
230 public function testGetDocument_NewFailure_CacheOK_UpdateOK($badWebResponse) {
231 $this->assertNotEmpty($badWebResponse);
233 // first try, bad response
234 CRM_Utils_Time
::setTime('2013-03-01 10:00:00');
235 $communityMessages = new CRM_Core_CommunityMessages(
237 $this->expectOneHttpRequest($badWebResponse)
239 $doc1 = $communityMessages->getDocument();
240 $this->assertEquals([], $doc1['messages']);
241 $this->assertTrue($doc1['expires'] > CRM_Utils_Time
::getTimeRaw());
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(
247 $this->expectNoHttpRequest()
249 $doc2 = $communityMessages->getDocument();
250 $this->assertEquals([], $doc2['messages']);
251 $this->assertEquals($doc1['expires'], $doc2['expires']);
253 // third try, $doc1 expired, try again, get a good response
254 // more than 2 hours later (DEFAULT_RETRY)
255 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
256 $communityMessages = new CRM_Core_CommunityMessages(
258 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
260 $doc3 = $communityMessages->getDocument();
261 $this->assertEquals('<h1>First valid response</h1>', $doc3['messages'][0]['markup']);
262 $this->assertTrue($doc3['expires'] > CRM_Utils_Time
::getTimeRaw());
266 * First download of new doc is OK.
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.
272 * @dataProvider badWebResponses
273 * @param array $badWebResponse
274 * Description of a web request that returns some kind of failure.
276 public function testGetDocument_NewOK_UpdateFailure_CacheOK_UpdateOK($badWebResponse) {
277 $this->assertNotEmpty($badWebResponse);
279 // first try, good response
280 CRM_Utils_Time
::setTime('2013-03-01 10:00:00');
281 $communityMessages = new CRM_Core_CommunityMessages(
283 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
285 $doc1 = $communityMessages->getDocument();
286 $this->assertEquals('<h1>First valid response</h1>', $doc1['messages'][0]['markup']);
287 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires'], self
::APPROX_TIME_EQUALITY
);
289 // second try, $doc1 has expired; bad response; keep old data
290 // more than 2 hours later (DEFAULT_RETRY)
291 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
292 $communityMessages = new CRM_Core_CommunityMessages(
294 $this->expectOneHttpRequest($badWebResponse)
296 $doc2 = $communityMessages->getDocument();
297 $this->assertEquals('<h1>First valid response</h1>', $doc2['messages'][0]['markup']);
298 $this->assertTrue($doc2['expires'] > CRM_Utils_Time
::getTimeRaw());
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(
304 $this->expectNoHttpRequest()
306 $doc3 = $communityMessages->getDocument();
307 $this->assertEquals('<h1>First valid response</h1>', $doc3['messages'][0]['markup']);
308 $this->assertEquals($doc2['expires'], $doc3['expires']);
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(
314 $this->expectOneHttpRequest(self
::$webResponses['second-valid-response'])
316 $doc4 = $communityMessages->getDocument();
317 $this->assertEquals('<h1>Second valid response</h1>', $doc4['messages'][0]['markup']);
318 $this->assertApproxEquals(strtotime('2013-03-01 12:20:02'), $doc4['expires'], self
::APPROX_TIME_EQUALITY
);
322 * Randomly pick among two options.
324 public function testPick_rand() {
325 $communityMessages = new CRM_Core_CommunityMessages(
327 $this->expectOneHttpRequest(self
::$webResponses['two-messages'])
329 $doc1 = $communityMessages->getDocument();
330 $this->assertEquals('<h1>One</h1>', $doc1['messages'][0]['markup']);
331 $this->assertEquals('<h1>Two</h1>', $doc1['messages'][1]['markup']);
333 // randomly pick many times
335 // array($message => $count)
337 for ($i = 0; $i < $trials; $i++
) {
338 $message = $communityMessages->pick();
339 $freq[$message['markup']] = CRM_Utils_Array
::value($message['markup'], $freq, 0) +
1;
342 // assert the probabilities
343 $this->assertApproxEquals(0.5, $freq['<h1>One</h1>'] / $trials, 0.3);
344 $this->assertApproxEquals(0.5, $freq['<h1>Two</h1>'] / $trials, 0.3);
345 $this->assertEquals($trials, $freq['<h1>One</h1>'] +
$freq['<h1>Two</h1>']);
349 * When presented with two options using component filters, always
350 * choose the one which references an active component.
352 public function testPick_componentFilter() {
353 $communityMessages = new CRM_Core_CommunityMessages(
355 $this->expectOneHttpRequest(self
::$webResponses['two-messages-halfbadcomp'])
357 $doc1 = $communityMessages->getDocument();
358 $this->assertEquals('<h1>One</h1>', $doc1['messages'][0]['markup']);
359 $this->assertEquals('<h1>Two</h1>', $doc1['messages'][1]['markup']);
361 // randomly pick many times
363 // array($message => $count)
365 for ($i = 0; $i < $trials; $i++
) {
366 $message = $communityMessages->pick();
367 $freq[$message['markup']] = CRM_Utils_Array
::value($message['markup'], $freq, 0) +
1;
370 $this->assertEquals($trials, $freq['<h1>Two</h1>']);
373 public function testEvalMarkup() {
374 $communityMessages = new CRM_Core_CommunityMessages(
376 $this->expectNoHttpRequest()
378 $this->assertEquals('cms=UnitTests cms=UnitTests', $communityMessages->evalMarkup('cms=%%uf%% cms={{uf}}'));
382 * Generate a mock HTTP client with the expectation that it is never called.
384 * @return CRM_Utils_HttpClient|PHPUnit\Framework\MockObject\MockObject
386 protected function expectNoHttpRequest() {
387 $mockFunction = $this->mockMethod
;
388 $client = $this->$mockFunction('CRM_Utils_HttpClient');
389 $client->expects($this->never())
395 * Generate a mock HTTP client with the expectation that it is called once.
399 * @return CRM_Utils_HttpClient|PHPUnit\Framework\MockObject\MockObject
401 protected function expectOneHttpRequest($response) {
402 $mockFunction = $this->mockMethod
;
403 $client = $this->$mockFunction('CRM_Utils_HttpClient');
404 $client->expects($this->once())
406 ->will($this->returnValue($response));