From ecbe11390ea4655843262232319be98fc78b21ad Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 29 Mar 2013 19:38:52 -0400 Subject: [PATCH] CRM-12193 - Implement CommunityMessages download/caching behavior ---------------------------------------- * CRM-12193: In-app fundraising for CiviCRM http://issues.civicrm.org/jira/browse/CRM-12193 --- CRM/Core/CommunityMessages.php | 144 ++++++++++ .../CRM/Core/CommunityMessagesTest.php | 254 ++++++++++++++++++ 2 files changed, 398 insertions(+) create mode 100644 CRM/Core/CommunityMessages.php create mode 100644 tests/phpunit/CRM/Core/CommunityMessagesTest.php diff --git a/CRM/Core/CommunityMessages.php b/CRM/Core/CommunityMessages.php new file mode 100644 index 0000000000..85062c0b15 --- /dev/null +++ b/CRM/Core/CommunityMessages.php @@ -0,0 +1,144 @@ +cache = $cache; + $this->client = $client; + } + + /** + * Get the messages document + * + * @return NULL|array + */ + public function getDocument() { + // FIXME register in settings + $url = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'communityMessagesUrl', NULL, TRUE); + if (empty($url)) { + return NULL; + } + + $isChanged = FALSE; + $document = $this->cache->get('communityMessages'); + + if (empty($document) || !is_array($document)) { + $document = array( + 'messages' => array(), + 'expires' => 0, // ASAP + 'ttl' => self::DEFAULT_RETRY, + 'retry' => self::DEFAULT_RETRY, + ); + $isChanged = TRUE; + } + + if ($document['expires'] <= CRM_Utils_Time::getTimeRaw()) { + $newDocument = $this->fetchDocument($url); + if ($newDocument) { + $document = $newDocument; + $document['expires'] = CRM_Utils_Time::getTimeRaw() + $document['ttl']; + } else { + $document['expires'] = CRM_Utils_Time::getTimeRaw() + $document['retry']; + } + $isChanged = TRUE; + } + + if ($isChanged) { + $this->cache->set('communityMessages', $document); + } + + return $document; + } + + /** + * Download document from URL and parse as JSON + * + * @param string $url + * @return NULL|array parsed JSON + */ + public function fetchDocument($url) { + list($status, $json) = $this->client->get(self::evalUrl($url)); + if ($status != CRM_Utils_HttpClient::STATUS_OK || empty($json)) { + return NULL; + } + $doc = json_decode($json, TRUE); + if (empty($doc) || json_last_error() != JSON_ERROR_NONE) { + return NULL; + } + return $doc; + } + + /** + * Pick one message + * + * @param callable $permChecker + * @param array $components + * @return NULL|array + */ + public function pick($permChecker, $components) { + throw new Exception('not implemented'); + } + + /** + * @param string $markup + * @return string + */ + public static function evalMarkup($markup) { + throw new Exception('not implemented'); + } + + /** + * @param string $markup + * @return string + */ + public static function evalUrl($url) { + return $url; // FIXME + } +} diff --git a/tests/phpunit/CRM/Core/CommunityMessagesTest.php b/tests/phpunit/CRM/Core/CommunityMessagesTest.php new file mode 100644 index 0000000000..0c545dac7f --- /dev/null +++ b/tests/phpunit/CRM/Core/CommunityMessagesTest.php @@ -0,0 +1,254 @@ +cache = new CRM_Utils_Cache_Arraycache(array()); + + $this->webResponses = array( + 'http-error' => array( + CRM_Utils_HttpClient::STATUS_DL_ERROR, + NULL + ), + 'bad-json' => array( + CRM_Utils_HttpClient::STATUS_OK, + 'this is not json!' + ), + 'hello-world' => array( + CRM_Utils_HttpClient::STATUS_OK, + json_encode(array( + 'ttl' => 600, + 'retry' => 600, + 'messages' => array( + array( + 'markup' => '

Hello world

', + ), + ), + )) + ), + 'salut-a-tout' => array( + CRM_Utils_HttpClient::STATUS_OK, + json_encode(array( + 'ttl' => 600, + 'retry' => 600, + 'messages' => array( + array( + 'markup' => '

Salut a tout

', + ), + ), + )) + ), + ); + } + + public function tearDown() { + parent::tearDown(); + CRM_Utils_Time::resetTime(); + } + + /** + * Download a document; after the set expiration period, download again. + */ + public function testNewOK_CacheOK_UpdateOK() { + // first try, good response + CRM_Utils_Time::setTime('2013-03-01 10:00:00'); + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['hello-world']) + ); + $doc1 = $communityMessages->getDocument(); + $this->assertEquals('

Hello world

', $doc1['messages'][0]['markup']); + $this->assertEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires']); + + // second try, $doc1 hasn't expired yet, so still use it + CRM_Utils_Time::setTime('2013-03-01 10:09:00'); + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectNoHttpRequest() + ); + $doc2 = $communityMessages->getDocument(); + $this->assertEquals('

Hello world

', $doc2['messages'][0]['markup']); + $this->assertEquals(strtotime('2013-03-01 10:10:00'), $doc2['expires']); + + // third try, $doc1 expired, update it + CRM_Utils_Time::setTime('2013-03-01 12:00:02'); // more than 2 hours later (DEFAULT_RETRY) + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['salut-a-tout']) + ); + $doc3 = $communityMessages->getDocument(); + $this->assertEquals('

Salut a tout

', $doc3['messages'][0]['markup']); + $this->assertEquals(strtotime('2013-03-01 12:10:02'), $doc3['expires']); + } + + /** + * First download attempt fails. Store the NACK and retry after + * the default time period (DEFAULT_RETRY). + */ + public function testNewFailure_CacheOK_UpdateOK() { + // first try, bad response + CRM_Utils_Time::setTime('2013-03-01 10:00:00'); + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['http-error']) + ); + $doc1 = $communityMessages->getDocument(); + $this->assertEquals(array(), $doc1['messages']); + $this->assertTrue($doc1['expires'] > CRM_Utils_Time::getTimeRaw()); + + // second try, $doc1 hasn't expired yet, so still use it + CRM_Utils_Time::setTime('2013-03-01 10:09:00'); + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectNoHttpRequest() + ); + $doc2 = $communityMessages->getDocument(); + $this->assertEquals(array(), $doc2['messages']); + $this->assertEquals($doc1['expires'], $doc2['expires']); + + // third try, $doc1 expired, try again, get a good response + CRM_Utils_Time::setTime('2013-03-01 12:00:02'); // more than 2 hours later (DEFAULT_RETRY) + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['hello-world']) + ); + $doc3 = $communityMessages->getDocument(); + $this->assertEquals('

Hello world

', $doc3['messages'][0]['markup']); + $this->assertTrue($doc3['expires'] > CRM_Utils_Time::getTimeRaw()); + } + + /** + * First download of new doc is OK. + * The update fails. + * The failure cached. + * The failure eventually expires and new update succeeds. + */ + public function testNewOK_UpdateFailure_CacheOK_UpdateOK() { + // first try, good response + CRM_Utils_Time::setTime('2013-03-01 10:00:00'); + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['hello-world']) + ); + $doc1 = $communityMessages->getDocument(); + $this->assertEquals('

Hello world

', $doc1['messages'][0]['markup']); + $this->assertEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires']); + + // second try, $doc1 has expired; bad response; keep old data + CRM_Utils_Time::setTime('2013-03-01 12:00:02'); // more than 2 hours later (DEFAULT_RETRY) + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['http-error']) + ); + $doc2 = $communityMessages->getDocument(); + $this->assertEquals('

Hello world

', $doc2['messages'][0]['markup']); + $this->assertTrue($doc2['expires'] > CRM_Utils_Time::getTimeRaw()); + + // third try, $doc2 hasn't expired yet; no request; keep old data + CRM_Utils_Time::setTime('2013-03-01 12:09:00'); + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectNoHttpRequest() + ); + $doc3 = $communityMessages->getDocument(); + $this->assertEquals('

Hello world

', $doc3['messages'][0]['markup']); + $this->assertEquals($doc2['expires'], $doc3['expires']); + + // fourth try, $doc2 has expired yet; new request; replace data + CRM_Utils_Time::setTime('2013-03-01 12:10:02'); + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['salut-a-tout']) + ); + $doc4 = $communityMessages->getDocument(); + $this->assertEquals('

Salut a tout

', $doc4['messages'][0]['markup']); + $this->assertEquals(strtotime('2013-03-01 12:20:02'), $doc4['expires']); + } + + public function testNewOK_UpdateParseError() { + // first try, good response + CRM_Utils_Time::setTime('2013-03-01 10:00:00'); + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['hello-world']) + ); + $doc1 = $communityMessages->getDocument(); + $this->assertEquals('

Hello world

', $doc1['messages'][0]['markup']); + $this->assertEquals(strtotime('2013-03-01 10:10:00'), $doc1['expires']); + + // second try, $doc1 has expired; bad response; keep old data + CRM_Utils_Time::setTime('2013-03-01 12:00:02'); // more than 2 hours later (DEFAULT_RETRY) + $communityMessages = new CRM_Core_CommunityMessages( + $this->cache, + $this->expectOneHttpRequest($this->webResponses['bad-json']) + ); + $doc2 = $communityMessages->getDocument(); + $this->assertEquals('

Hello world

', $doc2['messages'][0]['markup']); + $this->assertEquals(strtotime('2013-03-01 12:10:02'), $doc2['expires']); + } + + /** + * Generate a mock HTTP client with the expectation that it is never called. + * + * @return CRM_Utils_HttpClient|PHPUnit_Framework_MockObject_MockObject + */ + protected function expectNoHttpRequest() { + $client = $this->getMock('CRM_Utils_HttpClient'); + $client->expects($this->never()) + ->method('get'); + return $client; + } + + /** + * Generate a mock HTTP client with the expectation that it is called once. + * + * @return CRM_Utils_HttpClient|PHPUnit_Framework_MockObject_MockObject + */ + protected function expectOneHttpRequest($response) { + $client = $this->getMock('CRM_Utils_HttpClient'); + $client->expects($this->once()) + ->method('get') + ->will($this->returnValue($response)); + return $client; + } +} -- 2.25.1