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
31 * list of possible web responses
33 protected static $webResponses = NULL;
38 public static function initWebResponses() {
39 if (self
::$webResponses === NULL) {
40 self
::$webResponses = [
42 CRM_Utils_HttpClient
::STATUS_DL_ERROR
,
46 CRM_Utils_HttpClient
::STATUS_OK
,
47 '<html>this is not json!</html>',
49 'invalid-ttl-document' => [
50 CRM_Utils_HttpClient
::STATUS_OK
,
58 'markup' => '<h1>Invalid document</h1>',
63 'first-valid-response' => [
64 CRM_Utils_HttpClient
::STATUS_OK
,
70 'markup' => '<h1>First valid response</h1>',
75 'second-valid-response' => [
76 CRM_Utils_HttpClient
::STATUS_OK
,
82 'markup' => '<h1>Second valid response</h1>',
88 CRM_Utils_HttpClient
::STATUS_OK
,
94 'markup' => '<h1>One</h1>',
95 'components' => ['CiviMail'],
98 'markup' => '<h1>Two</h1>',
99 'components' => ['CiviMail'],
104 'two-messages-halfbadcomp' => [
105 CRM_Utils_HttpClient
::STATUS_OK
,
111 'markup' => '<h1>One</h1>',
112 'components' => ['NotARealComponent'],
115 'markup' => '<h1>Two</h1>',
116 'components' => ['CiviMail'],
123 return self
::$webResponses;
126 public function setUp() {
128 $this->cache
= new CRM_Utils_Cache_Arraycache([]);
129 self
::initWebResponses();
132 public function tearDown() {
134 CRM_Utils_Time
::resetTime();
138 * A list of bad web-responses; in general, whenever the downloader
139 * encounters one of these bad responses, it should ignore the
140 * document, retain the old data, and retry again later.
144 public function badWebResponses() {
145 self
::initWebResponses();
147 [self
::$webResponses['http-error']],
148 [self
::$webResponses['bad-json']],
149 [self
::$webResponses['invalid-ttl-document']],
154 public function testIsEnabled() {
155 $communityMessages = new CRM_Core_CommunityMessages(
157 $this->expectNoHttpRequest()
159 $this->assertTrue($communityMessages->isEnabled());
162 public function testIsEnabled_false() {
163 $communityMessages = new CRM_Core_CommunityMessages(
165 $this->expectNoHttpRequest(),
168 $this->assertFalse($communityMessages->isEnabled());
172 * Download a document; after the set expiration period, download again.
174 public function testGetDocument_NewOK_CacheOK_UpdateOK() {
175 // first try, good response
176 CRM_Utils_Time
::setTime('2013-03-01 10:00:00');
177 $communityMessages = new CRM_Core_CommunityMessages(
179 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
181 $doc1 = $communityMessages->getDocument();
182 $this->assertEquals('<h1>First valid response</h1>', $doc1['messages'][0]['markup']);
183 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires'], self
::APPROX_TIME_EQUALITY
);
185 // second try, $doc1 hasn't expired yet, so still use it
186 CRM_Utils_Time
::setTime('2013-03-01 10:09:00');
187 $communityMessages = new CRM_Core_CommunityMessages(
189 $this->expectNoHttpRequest()
191 $doc2 = $communityMessages->getDocument();
192 $this->assertEquals('<h1>First valid response</h1>', $doc2['messages'][0]['markup']);
193 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc2['expires'], self
::APPROX_TIME_EQUALITY
);
195 // third try, $doc1 expired, update it
196 // more than 2 hours later (DEFAULT_RETRY)
197 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
198 $communityMessages = new CRM_Core_CommunityMessages(
200 $this->expectOneHttpRequest(self
::$webResponses['second-valid-response'])
202 $doc3 = $communityMessages->getDocument();
203 $this->assertEquals('<h1>Second valid response</h1>', $doc3['messages'][0]['markup']);
204 $this->assertApproxEquals(strtotime('2013-03-01 12:10:02'), $doc3['expires'], self
::APPROX_TIME_EQUALITY
);
208 * First download attempt fails (due to some bad web request).
209 * Store the NACK and retry after the default time period (DEFAULT_RETRY).
211 * @dataProvider badWebResponses
212 * @param array $badWebResponse
213 * Description of a web request that returns some kind of failure.
215 public function testGetDocument_NewFailure_CacheOK_UpdateOK($badWebResponse) {
216 $this->assertNotEmpty($badWebResponse);
218 // first try, bad response
219 CRM_Utils_Time
::setTime('2013-03-01 10:00:00');
220 $communityMessages = new CRM_Core_CommunityMessages(
222 $this->expectOneHttpRequest($badWebResponse)
224 $doc1 = $communityMessages->getDocument();
225 $this->assertEquals([], $doc1['messages']);
226 $this->assertTrue($doc1['expires'] > CRM_Utils_Time
::getTimeRaw());
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(
232 $this->expectNoHttpRequest()
234 $doc2 = $communityMessages->getDocument();
235 $this->assertEquals([], $doc2['messages']);
236 $this->assertEquals($doc1['expires'], $doc2['expires']);
238 // third try, $doc1 expired, try again, get a good response
239 // more than 2 hours later (DEFAULT_RETRY)
240 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
241 $communityMessages = new CRM_Core_CommunityMessages(
243 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
245 $doc3 = $communityMessages->getDocument();
246 $this->assertEquals('<h1>First valid response</h1>', $doc3['messages'][0]['markup']);
247 $this->assertTrue($doc3['expires'] > CRM_Utils_Time
::getTimeRaw());
251 * First download of new doc is OK.
252 * The update fails (due to some bad web response).
253 * The old data is retained in the cache.
254 * The failure eventually expires.
255 * A new update succeeds.
257 * @dataProvider badWebResponses
258 * @param array $badWebResponse
259 * Description of a web request that returns some kind of failure.
261 public function testGetDocument_NewOK_UpdateFailure_CacheOK_UpdateOK($badWebResponse) {
262 $this->assertNotEmpty($badWebResponse);
264 // first try, good response
265 CRM_Utils_Time
::setTime('2013-03-01 10:00:00');
266 $communityMessages = new CRM_Core_CommunityMessages(
268 $this->expectOneHttpRequest(self
::$webResponses['first-valid-response'])
270 $doc1 = $communityMessages->getDocument();
271 $this->assertEquals('<h1>First valid response</h1>', $doc1['messages'][0]['markup']);
272 $this->assertApproxEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires'], self
::APPROX_TIME_EQUALITY
);
274 // second try, $doc1 has expired; bad response; keep old data
275 // more than 2 hours later (DEFAULT_RETRY)
276 CRM_Utils_Time
::setTime('2013-03-01 12:00:02');
277 $communityMessages = new CRM_Core_CommunityMessages(
279 $this->expectOneHttpRequest($badWebResponse)
281 $doc2 = $communityMessages->getDocument();
282 $this->assertEquals('<h1>First valid response</h1>', $doc2['messages'][0]['markup']);
283 $this->assertTrue($doc2['expires'] > CRM_Utils_Time
::getTimeRaw());
285 // third try, $doc2 hasn't expired yet; no request; keep old data
286 CRM_Utils_Time
::setTime('2013-03-01 12:09:00');
287 $communityMessages = new CRM_Core_CommunityMessages(
289 $this->expectNoHttpRequest()
291 $doc3 = $communityMessages->getDocument();
292 $this->assertEquals('<h1>First valid response</h1>', $doc3['messages'][0]['markup']);
293 $this->assertEquals($doc2['expires'], $doc3['expires']);
295 // fourth try, $doc2 has expired yet; new request; replace data
296 CRM_Utils_Time
::setTime('2013-03-01 12:10:02');
297 $communityMessages = new CRM_Core_CommunityMessages(
299 $this->expectOneHttpRequest(self
::$webResponses['second-valid-response'])
301 $doc4 = $communityMessages->getDocument();
302 $this->assertEquals('<h1>Second valid response</h1>', $doc4['messages'][0]['markup']);
303 $this->assertApproxEquals(strtotime('2013-03-01 12:20:02'), $doc4['expires'], self
::APPROX_TIME_EQUALITY
);
307 * Randomly pick among two options.
309 public function testPick_rand() {
310 $communityMessages = new CRM_Core_CommunityMessages(
312 $this->expectOneHttpRequest(self
::$webResponses['two-messages'])
314 $doc1 = $communityMessages->getDocument();
315 $this->assertEquals('<h1>One</h1>', $doc1['messages'][0]['markup']);
316 $this->assertEquals('<h1>Two</h1>', $doc1['messages'][1]['markup']);
318 // randomly pick many times
320 // array($message => $count)
322 for ($i = 0; $i < $trials; $i++
) {
323 $message = $communityMessages->pick();
324 $freq[$message['markup']] = CRM_Utils_Array
::value($message['markup'], $freq, 0) +
1;
327 // assert the probabilities
328 $this->assertApproxEquals(0.5, $freq['<h1>One</h1>'] / $trials, 0.3);
329 $this->assertApproxEquals(0.5, $freq['<h1>Two</h1>'] / $trials, 0.3);
330 $this->assertEquals($trials, $freq['<h1>One</h1>'] +
$freq['<h1>Two</h1>']);
334 * When presented with two options using component filters, always
335 * choose the one which references an active component.
337 public function testPick_componentFilter() {
338 $communityMessages = new CRM_Core_CommunityMessages(
340 $this->expectOneHttpRequest(self
::$webResponses['two-messages-halfbadcomp'])
342 $doc1 = $communityMessages->getDocument();
343 $this->assertEquals('<h1>One</h1>', $doc1['messages'][0]['markup']);
344 $this->assertEquals('<h1>Two</h1>', $doc1['messages'][1]['markup']);
346 // randomly pick many times
348 // array($message => $count)
350 for ($i = 0; $i < $trials; $i++
) {
351 $message = $communityMessages->pick();
352 $freq[$message['markup']] = CRM_Utils_Array
::value($message['markup'], $freq, 0) +
1;
355 $this->assertEquals($trials, $freq['<h1>Two</h1>']);
358 public function testEvalMarkup() {
359 $communityMessages = new CRM_Core_CommunityMessages(
361 $this->expectNoHttpRequest()
363 $this->assertEquals('cms=UnitTests cms=UnitTests', $communityMessages->evalMarkup('cms=%%uf%% cms={{uf}}'));
367 * Generate a mock HTTP client with the expectation that it is never called.
369 * @return CRM_Utils_HttpClient|PHPUnit\Framework\MockObject\MockObject
371 protected function expectNoHttpRequest() {
372 $mockFunction = $this->mockMethod
;
373 $client = $this->$mockFunction('CRM_Utils_HttpClient');
374 $client->expects($this->never())
380 * Generate a mock HTTP client with the expectation that it is called once.
384 * @return CRM_Utils_HttpClient|PHPUnit\Framework\MockObject\MockObject
386 protected function expectOneHttpRequest($response) {
387 $mockFunction = $this->mockMethod
;
388 $client = $this->$mockFunction('CRM_Utils_HttpClient');
389 $client->expects($this->once())
391 ->will($this->returnValue($response));