First caching test:
[civicrm-core.git] / tests / phpunit / CRM / Contact / BAO / GroupContactCacheTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Test class for CRM_Contact_BAO_GroupContact BAO
30 *
31 * @package CiviCRM
32 * @group headless
33 */
34 class CRM_Contact_BAO_GroupContactCacheTest extends CiviUnitTestCase {
35
36 /**
37 * Manually add and remove contacts from a smart group.
38 */
39 public function testManualAddRemove() {
40 list($group, $living, $deceased) = $this->setupSmartGroup();
41
42 // Add $n1 to $g
43 $this->callAPISuccess('group_contact', 'create', array(
44 'contact_id' => $living[0]->id,
45 'group_id' => $group->id,
46 ));
47
48 CRM_Contact_BAO_GroupContactCache::load($group, TRUE);
49 $this->assertCacheMatches(
50 array($deceased[0]->id, $deceased[1]->id, $deceased[2]->id, $living[0]->id),
51 $group->id
52 );
53
54 // Remove $y1 from $g
55 $this->callAPISuccess('group_contact', 'create', array(
56 'contact_id' => $deceased[0]->id,
57 'group_id' => $group->id,
58 'status' => 'Removed',
59 ));
60
61 CRM_Contact_BAO_GroupContactCache::load($group, TRUE);
62 $this->assertCacheMatches(
63 array(
64 $deceased[1]->id,
65 $deceased[2]->id,
66 $living[0]->id,
67 ),
68 $group->id
69 );
70 }
71
72 /**
73 * Allow removing contact from a parent group even if contact is in a child group. (CRM-8858).
74 */
75 public function testRemoveFromParentSmartGroup() {
76 // Create smart group $parent
77 $params = array(
78 'name' => 'Deceased Contacts',
79 'title' => 'Deceased Contacts',
80 'is_active' => 1,
81 'formValues' => array('is_deceased' => 1),
82 );
83 $parent = CRM_Contact_BAO_Group::createSmartGroup($params);
84 $this->registerTestObjects(array($parent));
85
86 // Create group $child in $parent
87 $params = array(
88 'name' => 'Child Group',
89 'title' => 'Child Group',
90 'is_active' => 1,
91 'parents' => array($parent->id => 1),
92 );
93 $child = CRM_Contact_BAO_Group::create($params);
94 $this->registerTestObjects(array($child));
95
96 // Create $c1, $c2, $c3
97 $deceased = $this->createTestObject('CRM_Contact_DAO_Contact', array('is_deceased' => 1), 3);
98
99 // Add $c1, $c2, $c3 to $child
100 foreach ($deceased as $contact) {
101 $this->callAPISuccess('group_contact', 'create', array(
102 'contact_id' => $contact->id,
103 'group_id' => $child->id,
104 ));
105 }
106
107 CRM_Contact_BAO_GroupContactCache::load($parent, TRUE);
108 $this->assertCacheMatches(
109 array($deceased[0]->id, $deceased[1]->id, $deceased[2]->id),
110 $parent->id
111 );
112
113 // Remove $c1 from $parent
114 $this->callAPISuccess('group_contact', 'create', array(
115 'contact_id' => $deceased[0]->id,
116 'group_id' => $parent->id,
117 'status' => 'Removed',
118 ));
119
120 // Assert $c1 not in $parent
121 CRM_Contact_BAO_GroupContactCache::load($parent, TRUE);
122 $this->assertCacheMatches(
123 array(
124 $deceased[1]->id,
125 $deceased[2]->id,
126 ),
127 $parent->id
128 );
129
130 // Assert $c1 still in $child
131 $this->assertDBQuery(1,
132 'select count(*) from civicrm_group_contact where group_id=%1 and contact_id=%2 and status=%3',
133 array(
134 1 => array($child->id, 'Integer'),
135 2 => array($deceased[0]->id, 'Integer'),
136 3 => array('Added', 'String'),
137 )
138 );
139 }
140
141 /**
142 * Assert that the cache for a group contains exactly the listed contacts.
143 *
144 * @param array $expectedContactIds
145 * Array(int).
146 * @param int $groupId
147 */
148 public function assertCacheMatches($expectedContactIds, $groupId) {
149 $sql = 'SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id = %1';
150 $params = array(1 => array($groupId, 'Integer'));
151 $dao = CRM_Core_DAO::executeQuery($sql, $params);
152 $actualContactIds = array();
153 while ($dao->fetch()) {
154 $actualContactIds[] = $dao->contact_id;
155 }
156
157 sort($expectedContactIds);
158 sort($actualContactIds);
159 $this->assertEquals($expectedContactIds, $actualContactIds);
160 }
161
162 /**
163 * Test the opportunistic refresh cache function does not touch non-expired entries.
164 */
165 public function testOpportunisticRefreshCacheNoChangeIfNotExpired() {
166 list($group, $living, $deceased) = $this->setupSmartGroup();
167 $this->callAPISuccess('Contact', 'create', array('id' => $deceased[0]->id, 'is_deceased' => 0));
168 $this->assertCacheMatches(
169 array($deceased[0]->id, $deceased[1]->id, $deceased[2]->id),
170 $group->id
171 );
172 CRM_Contact_BAO_GroupContactCache::opportunisticCacheRefresh();
173
174 $this->assertCacheMatches(
175 array($deceased[0]->id, $deceased[1]->id, $deceased[2]->id),
176 $group->id
177 );
178 $afterGroup = $this->callAPISuccessGetSingle('Group', array('id' => $group->id));
179 $this->assertEquals($group->cache_date, $afterGroup['cache_date']);
180 // we don't check for refresh_date here as it was not set when building the cache & it
181 // feels like it should have been.... but that is out of scope.
182 }
183
184 /**
185 * Test the opportunistic refresh cache function does not touch non-expired entries.
186 */
187 public function testOpportunisticRefreshChangeIfCacheDateFieldStale() {
188 list($group, $living, $deceased) = $this->setupSmartGroup();
189 $this->callAPISuccess('Contact', 'create', array('id' => $deceased[0]->id, 'is_deceased' => 0));
190 CRM_Core_DAO::executeQuery('UPDATE civicrm_group SET cache_date = DATE_SUB(NOW(), INTERVAL 7 MINUTE) WHERE id = ' . $group->id);
191 Civi::$statics['CRM_Contact_BAO_GroupContactCache']['is_refresh_init'] = FALSE;
192 sleep(1);
193 CRM_Contact_BAO_GroupContactCache::opportunisticCacheRefresh();
194
195 $this->assertCacheMatches(
196 array(),
197 $group->id
198 );
199
200 $afterGroup = $this->callAPISuccessGetSingle('Group', array('id' => $group->id));
201 $this->assertTrue(empty($afterGroup['cache_date']), 'refresh date should not be set as the cache is not built');
202 $this->assertTrue(empty($afterGroup['refresh_date']), 'refresh date should not be set as the cache is not built');
203 }
204
205 // *** Everything below this should be moved to parent class ****
206
207 /**
208 * @var array(DAO_Name => array(int)) List of items to garbage-collect during tearDown
209 */
210 private $_testObjects;
211
212 /**
213 * Sets up the fixture, for example, opens a network connection.
214 *
215 * This method is called before a test is executed.
216 */
217 protected function setUp() {
218 $this->_testObjects = array();
219 parent::setUp();
220 }
221
222 /**
223 * Tears down the fixture, for example, closes a network connection.
224 *
225 * This method is called after a test is executed.
226 */
227 protected function tearDown() {
228 parent::tearDown();
229 $this->deleteTestObjects();
230 }
231
232 /**
233 * This is a wrapper for CRM_Core_DAO::createTestObject which tracks created entities.
234 *
235 * @see CRM_Core_DAO::createTestObject
236 *
237 * @param string $daoName
238 * @param array $params
239 * @param int $numObjects
240 * @param bool $createOnly
241 *
242 * @return array|NULL|object
243 */
244 public function createTestObject($daoName, $params = array(), $numObjects = 1, $createOnly = FALSE) {
245 $objects = CRM_Core_DAO::createTestObject($daoName, $params, $numObjects, $createOnly);
246 if (is_array($objects)) {
247 $this->registerTestObjects($objects);
248 }
249 else {
250 $this->registerTestObjects(array($objects));
251 }
252 return $objects;
253 }
254
255 /**
256 * Register test objects.
257 *
258 * @param array $objects
259 * DAO or BAO objects.
260 */
261 public function registerTestObjects($objects) {
262 foreach ($objects as $object) {
263 $daoName = preg_replace('/_BAO_/', '_DAO_', get_class($object));
264 $this->_testObjects[$daoName][] = $object->id;
265 }
266 }
267
268 /**
269 * Delete test objects.
270 *
271 * Note: You might argue that the FK relations between test
272 * objects could make this problematic; however, it should
273 * behave intuitively as long as we mentally split our
274 * test-objects between the "manual/primary records"
275 * and the "automatic/secondary records"
276 */
277 public function deleteTestObjects() {
278 foreach ($this->_testObjects as $daoName => $daoIds) {
279 foreach ($daoIds as $daoId) {
280 CRM_Core_DAO::deleteTestObjects($daoName, array('id' => $daoId));
281 }
282 }
283 $this->_testObjects = array();
284 }
285
286 /**
287 * Set up a smart group testing scenario.
288 *
289 * @return array
290 */
291 protected function setupSmartGroup() {
292 $params = array(
293 'name' => 'Deceased Contacts',
294 'title' => 'Deceased Contacts',
295 'is_active' => 1,
296 'formValues' => array('is_deceased' => 1),
297 );
298 $group = CRM_Contact_BAO_Group::createSmartGroup($params);
299 $this->registerTestObjects(array($group));
300
301 // Create contacts $y1, $y2, $y3 which do match $g; create $n1, $n2, $n3 which do not match $g
302 $living = $this->createTestObject('CRM_Contact_DAO_Contact', array('is_deceased' => 0), 3);
303 $deceased = $this->createTestObject('CRM_Contact_DAO_Contact', array('is_deceased' => 1), 3);
304 $this->assertEquals(3, count($deceased));
305 $this->assertEquals(3, count($living));
306
307 // Assert: $g cache has exactly $y1, $y2, $y3
308 CRM_Contact_BAO_GroupContactCache::load($group, TRUE);
309 $group->find(TRUE);
310 $this->assertCacheMatches(
311 array($deceased[0]->id, $deceased[1]->id, $deceased[2]->id),
312 $group->id
313 );
314 // Reload the group so we have the cache_date & refresh_date.
315 return array($group, $living, $deceased);
316 }
317
318 }