3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 |
9 +--------------------------------------------------------------------+
13 * Class CRM_Core_CommunityMessagesTest
16 class CRM_Core_CommunityMessagesTest
extends CiviUnitTestCase
{
19 * The max difference between two times such that they should be
20 * treated as equals (expressed in seconds).
22 const APPROX_TIME_EQUALITY
= 2;
25 * @var CRM_Utils_Cache_Interface
30 * @var array list of possible web responses
32 protected static $webResponses = NULL;
37 public static function initWebResponses() {
38 if (self
::$webResponses === NULL) {
39 self
::$webResponses = [
41 CRM_Utils_HttpClient
::STATUS_DL_ERROR
,
45 CRM_Utils_HttpClient
::STATUS_OK
,
46 '<html>this is not json!</html>',
48 'invalid-ttl-document' => [
49 CRM_Utils_HttpClient
::STATUS_OK
,
57 'markup' => '<h1>Invalid document</h1>',
62 'first-valid-response' => [
63 CRM_Utils_HttpClient
::STATUS_OK
,
69 'markup' => '<h1>First valid response</h1>',
74 'second-valid-response' => [
75 CRM_Utils_HttpClient
::STATUS_OK
,
81 'markup' => '<h1>Second valid response</h1>',
87 CRM_Utils_HttpClient
::STATUS_OK
,
93 'markup' => '<h1>One</h1>',
94 'components' => ['CiviMail'],
97 'markup' => '<h1>Two</h1>',
98 'components' => ['CiviMail'],
103 'two-messages-halfbadcomp' => [
104 CRM_Utils_HttpClient
::STATUS_OK
,
110 'markup' => '<h1>One</h1>',
111 'components' => ['NotARealComponent'],
114 'markup' => '<h1>Two</h1>',
115 'components' => ['CiviMail'],
122 return self
::$webResponses;
125 public function setUp() {
127 $this->cache
= new CRM_Utils_Cache_Arraycache([]);
128 self
::initWebResponses();
131 public function tearDown() {
133 CRM_Utils_Time
::resetTime();
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.
143 public function badWebResponses() {
144 self
::initWebResponses();
146 [self
::$webResponses['http-error']],
147 [self
::$webResponses['bad-json']],
148 [self
::$webResponses['invalid-ttl-document']],
153 public function testIsEnabled() {
154 $communityMessages = new CRM_Core_CommunityMessages(
156 $this->expectNoHttpRequest()
158 $this->assertTrue($communityMessages->isEnabled());
161 public function testIsEnabled_false() {
162 $communityMessages = new CRM_Core_CommunityMessages(
164 $this->expectNoHttpRequest(),
167 $this->assertFalse($communityMessages->isEnabled());
171 * Download a document; after the set expiration period, download again.
173 public function testGetDocument_NewOK_CacheOK_UpdateOK() {
174 // first try, good response
175 CRM_Utils_Time
::setTime('2013-03-01 10:00:00');
176 $communityMessages = new CRM_Core_CommunityMessages(
178 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
180 $doc1 = $communityMessages->getDocument();
181 $this->assertEquals('<h1>First valid response</h1>', $doc1['messages'][0]['markup']);
182 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires'], self
::APPROX_TIME_EQUALITY
);
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(
188 $this->expectNoHttpRequest()
190 $doc2 = $communityMessages->getDocument();
191 $this->assertEquals('<h1>First valid response</h1>', $doc2['messages'][0]['markup']);
192 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc2['expires'], self
::APPROX_TIME_EQUALITY
);
194 // third try, $doc1 expired, update it
195 // more than 2 hours later (DEFAULT_RETRY)
196 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
197 $communityMessages = new CRM_Core_CommunityMessages(
199 $this->expectOneHttpRequest(self
::$webResponses['second-valid-response'])
201 $doc3 = $communityMessages->getDocument();
202 $this->assertEquals('<h1>Second valid response</h1>', $doc3['messages'][0]['markup']);
203 $this->assertApproxEquals(strtotime('2013-03-01 12:10:02'), $doc3['expires'], self
::APPROX_TIME_EQUALITY
);
207 * First download attempt fails (due to some bad web request).
208 * Store the NACK and retry after the default time period (DEFAULT_RETRY).
210 * @dataProvider badWebResponses
211 * @param array $badWebResponse
212 * Description of a web request that returns some kind of failure.
214 public function testGetDocument_NewFailure_CacheOK_UpdateOK($badWebResponse) {
215 $this->assertNotEmpty($badWebResponse);
217 // first try, bad response
218 CRM_Utils_Time
::setTime('2013-03-01 10:00:00');
219 $communityMessages = new CRM_Core_CommunityMessages(
221 $this->expectOneHttpRequest($badWebResponse)
223 $doc1 = $communityMessages->getDocument();
224 $this->assertEquals([], $doc1['messages']);
225 $this->assertTrue($doc1['expires'] > CRM_Utils_Time
::getTimeRaw());
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(
231 $this->expectNoHttpRequest()
233 $doc2 = $communityMessages->getDocument();
234 $this->assertEquals([], $doc2['messages']);
235 $this->assertEquals($doc1['expires'], $doc2['expires']);
237 // third try, $doc1 expired, try again, get a good response
238 // more than 2 hours later (DEFAULT_RETRY)
239 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
240 $communityMessages = new CRM_Core_CommunityMessages(
242 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
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());
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.
256 * @dataProvider badWebResponses
257 * @param array $badWebResponse
258 * Description of a web request that returns some kind of failure.
260 public function testGetDocument_NewOK_UpdateFailure_CacheOK_UpdateOK($badWebResponse) {
261 $this->assertNotEmpty($badWebResponse);
263 // first try, good response
264 CRM_Utils_Time
::setTime('2013-03-01 10:00:00');
265 $communityMessages = new CRM_Core_CommunityMessages(
267 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
269 $doc1 = $communityMessages->getDocument();
270 $this->assertEquals('<h1>First valid response</h1>', $doc1['messages'][0]['markup']);
271 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires'], self
::APPROX_TIME_EQUALITY
);
273 // second try, $doc1 has expired; bad response; keep old data
274 // more than 2 hours later (DEFAULT_RETRY)
275 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
276 $communityMessages = new CRM_Core_CommunityMessages(
278 $this->expectOneHttpRequest($badWebResponse)
280 $doc2 = $communityMessages->getDocument();
281 $this->assertEquals('<h1>First valid response</h1>', $doc2['messages'][0]['markup']);
282 $this->assertTrue($doc2['expires'] > CRM_Utils_Time
::getTimeRaw());
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(
288 $this->expectNoHttpRequest()
290 $doc3 = $communityMessages->getDocument();
291 $this->assertEquals('<h1>First valid response</h1>', $doc3['messages'][0]['markup']);
292 $this->assertEquals($doc2['expires'], $doc3['expires']);
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(
298 $this->expectOneHttpRequest(self
::$webResponses['second-valid-response'])
300 $doc4 = $communityMessages->getDocument();
301 $this->assertEquals('<h1>Second valid response</h1>', $doc4['messages'][0]['markup']);
302 $this->assertApproxEquals(strtotime('2013-03-01 12:20:02'), $doc4['expires'], self
::APPROX_TIME_EQUALITY
);
306 * Randomly pick among two options.
308 public function testPick_rand() {
309 $communityMessages = new CRM_Core_CommunityMessages(
311 $this->expectOneHttpRequest(self
::$webResponses['two-messages'])
313 $doc1 = $communityMessages->getDocument();
314 $this->assertEquals('<h1>One</h1>', $doc1['messages'][0]['markup']);
315 $this->assertEquals('<h1>Two</h1>', $doc1['messages'][1]['markup']);
317 // randomly pick many times
319 // array($message => $count)
321 for ($i = 0; $i < $trials; $i++
) {
322 $message = $communityMessages->pick();
323 $freq[$message['markup']] = CRM_Utils_Array
::value($message['markup'], $freq, 0) +
1;
326 // assert the probabilities
327 $this->assertApproxEquals(0.5, $freq['<h1>One</h1>'] / $trials, 0.3);
328 $this->assertApproxEquals(0.5, $freq['<h1>Two</h1>'] / $trials, 0.3);
329 $this->assertEquals($trials, $freq['<h1>One</h1>'] +
$freq['<h1>Two</h1>']);
333 * When presented with two options using component filters, always
334 * choose the one which references an active component.
336 public function testPick_componentFilter() {
337 $communityMessages = new CRM_Core_CommunityMessages(
339 $this->expectOneHttpRequest(self
::$webResponses['two-messages-halfbadcomp'])
341 $doc1 = $communityMessages->getDocument();
342 $this->assertEquals('<h1>One</h1>', $doc1['messages'][0]['markup']);
343 $this->assertEquals('<h1>Two</h1>', $doc1['messages'][1]['markup']);
345 // randomly pick many times
347 // array($message => $count)
349 for ($i = 0; $i < $trials; $i++
) {
350 $message = $communityMessages->pick();
351 $freq[$message['markup']] = CRM_Utils_Array
::value($message['markup'], $freq, 0) +
1;
354 $this->assertEquals($trials, $freq['<h1>Two</h1>']);
357 public function testEvalMarkup() {
358 $communityMessages = new CRM_Core_CommunityMessages(
360 $this->expectNoHttpRequest()
362 $this->assertEquals('cms=UnitTests cms=UnitTests', $communityMessages->evalMarkup('cms=%%uf%% cms={{uf}}'));
366 * Generate a mock HTTP client with the expectation that it is never called.
368 * @return CRM_Utils_HttpClient|PHPUnit\Framework\MockObject\MockObject
370 protected function expectNoHttpRequest() {
371 $mockFunction = $this->mockMethod
;
372 $client = $this->$mockFunction('CRM_Utils_HttpClient');
373 $client->expects($this->never())
379 * Generate a mock HTTP client with the expectation that it is called once.
383 * @return CRM_Utils_HttpClient|PHPUnit\Framework\MockObject\MockObject
385 protected function expectOneHttpRequest($response) {
386 $mockFunction = $this->mockMethod
;
387 $client = $this->$mockFunction('CRM_Utils_HttpClient');
388 $client->expects($this->once())
390 ->will($this->returnValue($response));