4 * Class CRM_Core_ManagedEntitiesTest
7 class CRM_Core_ManagedEntitiesTest
extends CiviUnitTestCase
{
9 * @var \Civi\API\Kernel
14 * @var \Civi\API\Provider\AdhocProvider
16 protected $adhocProvider;
25 public function setUp() {
26 $this->useTransaction(TRUE);
29 'one' => new CRM_Core_Module('com.example.one', TRUE),
30 'two' => new CRM_Core_Module('com.example.two', TRUE),
33 // Testing on drupal-demo fails because some extensions have mgd ents.
34 CRM_Core_DAO
::singleValueQuery('DELETE FROM civicrm_managed');
36 $this->fixtures
['com.example.one-foo'] = [
37 'module' => 'com.example.one',
39 'entity' => 'CustomSearch',
42 'class_name' => 'CRM_Example_One_Foo',
46 $this->fixtures
['com.example.one-bar'] = [
47 'module' => 'com.example.one',
49 'entity' => 'CustomSearch',
52 'class_name' => 'CRM_Example_One_Bar',
56 $this->fixtures
['com.example.one-CustomGroup'] = [
57 'module' => 'com.example.one',
58 'name' => 'CustomGroup',
59 'entity' => 'CustomGroup',
62 'name' => 'test_custom_group',
63 'title' => 'Test custom group',
64 'extends' => 'Individual',
67 $this->fixtures
['com.example.one-CustomField'] = [
68 'module' => 'com.example.one',
69 'name' => 'CustomField',
70 'entity' => 'CustomField',
73 'name' => 'test_custom_field',
74 'label' => 'Test custom field',
75 'custom_group_id' => 'test_custom_group',
76 'data_type' => 'String',
77 'html_type' => 'Text',
81 $this->fixtures
['com.example.one-Job'] = [
82 'module' => 'com.example.one',
88 'run_frequency' => 'Daily',
89 'api_entity' => 'Job',
90 'api_action' => 'Get',
95 $this->apiKernel
= \Civi
::service('civi_api_kernel');
96 $this->adhocProvider
= new \Civi\API\Provider\
AdhocProvider(3, 'CustomSearch');
97 $this->apiKernel
->registerApiProvider($this->adhocProvider
);
100 public function tearDown() {
106 * Set up an active module and, over time, the hook implementation changes
107 * to (1) create 'foo' entity, (2) create 'bar' entity', (3) remove 'foo'
110 public function testAddRemoveEntitiesModule_UpdateAlways_DeleteAlways() {
113 // create first managed entity ('foo')
114 $decls[] = $this->fixtures
['com.example.one-foo'];
115 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
117 $foo = $me->get('com.example.one', 'foo');
118 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
119 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
121 // later on, hook returns an extra managed entity ('bar')
122 $decls[] = $this->fixtures
['com.example.one-bar'];
123 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
125 $foo = $me->get('com.example.one', 'foo');
126 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
127 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
128 $bar = $me->get('com.example.one', 'bar');
129 $this->assertEquals('CRM_Example_One_Bar', $bar['name']);
130 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Bar"');
132 // and then hook changes its mind, removing 'foo' (first of two entities)
134 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
136 $foo = $me->get('com.example.one', 'foo');
137 $this->assertTrue($foo === NULL);
138 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
139 $bar = $me->get('com.example.one', 'bar');
140 $this->assertEquals('CRM_Example_One_Bar', $bar['name']);
141 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Bar"');
143 // and then hook changes its mind, removing 'bar' (the last remaining entity)
145 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
147 $foo = $me->get('com.example.one', 'foo');
148 $this->assertTrue($foo === NULL);
149 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
150 $bar = $me->get('com.example.one', 'bar');
151 $this->assertTrue($bar === NULL);
152 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Bar"');
156 * Set up an active module with one managed-entity and, over
157 * time, the content of the entity changes
159 public function testModifyDeclaration_UpdateAlways() {
162 // create first managed entity ('foo')
163 $decls[] = $this->fixtures
['com.example.one-foo'];
164 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
166 $foo = $me->get('com.example.one', 'foo');
167 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
168 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
170 // later on, hook specification changes
171 $decls[0]['params']['class_name'] = 'CRM_Example_One_Foobar';
172 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
174 $foo2 = $me->get('com.example.one', 'foo');
175 $this->assertEquals('CRM_Example_One_Foobar', $foo2['name']);
176 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
177 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_FooBar"');
178 $this->assertEquals($foo['id'], $foo2['id']);
182 * Set up an active module with one managed-entity and, over
183 * time, the content of the entity changes
185 public function testModifyDeclaration_UpdateNever() {
188 // create first managed entity ('foo')
189 $decls[] = array_merge($this->fixtures
['com.example.one-foo'], [
190 // Policy is to never update after initial creation
193 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
195 $foo = $me->get('com.example.one', 'foo');
196 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
197 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
199 // later on, hook specification changes
200 $decls[0]['params']['class_name'] = 'CRM_Example_One_Foobar';
201 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
203 $foo2 = $me->get('com.example.one', 'foo');
204 $this->assertEquals('CRM_Example_One_Foo', $foo2['name']);
205 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
206 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_FooBar"');
207 $this->assertEquals($foo['id'], $foo2['id']);
211 * Set up an active module with one managed-entity using the
212 * policy "cleanup=>never". When the managed-entity goes away,
213 * ensure that the policy is followed (ie the entity is not
216 public function testRemoveDeclaration_CleanupNever() {
219 // create first managed entity ('foo')
220 $decls[] = array_merge($this->fixtures
['com.example.one-foo'], [
221 'cleanup' => 'never',
223 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
225 $foo = $me->get('com.example.one', 'foo');
226 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
227 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
229 // later on, entity definition disappears; but we decide not to do any cleanup (per policy)
231 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
233 $foo2 = $me->get('com.example.one', 'foo');
234 $this->assertEquals('CRM_Example_One_Foo', $foo2['name']);
235 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
236 $this->assertEquals($foo['id'], $foo2['id']);
240 * Set up an active module with one managed-entity using the
241 * policy "cleanup=>never". When the managed-entity goes away,
242 * ensure that the policy is followed (ie the entity is not
245 public function testRemoveDeclaration_CleanupUnused() {
248 // create first managed entity ('foo')
249 $decls[] = array_merge($this->fixtures
['com.example.one-foo'], [
250 'cleanup' => 'unused',
252 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
254 $foo = $me->get('com.example.one', 'foo');
255 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
256 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
258 // Override 'getrefcount' ==> The refcount is 1
259 $this->adhocProvider
->addAction('getrefcount', 'access CiviCRM', function ($apiRequest) {
260 return civicrm_api3_create_success([
269 // Later on, entity definition disappears; but we decide not to do any cleanup (per policy)
271 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
273 $foo2 = $me->get('com.example.one', 'foo');
274 $this->assertEquals('CRM_Example_One_Foo', $foo2['name']);
275 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
276 $this->assertEquals($foo['id'], $foo2['id']);
278 // Override 'getrefcount' ==> The refcount is 0
279 $this->adhocProvider
->addAction('getrefcount', 'access CiviCRM', function ($apiRequest) {
280 return civicrm_api3_create_success([]);
283 // The entity definition disappeared and there's no reference; we decide to cleanup (per policy)
285 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
287 $foo3 = $me->get('com.example.one', 'foo');
288 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
289 $this->assertTrue($foo3 === NULL);
293 * Setup an active module with a malformed entity declaration.
295 public function testInvalidDeclarationModule() {
296 // create first managed entity ('foo')
300 'module' => 'com.example.unknown',
302 'entity' => 'CustomSearch',
305 'class_name' => 'CRM_Example_One_Foo',
309 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
312 $this->fail('Expected exception when using invalid declaration');
314 catch (Exception
$e) {
320 * Setup an active module with a malformed entity declaration.
322 public function testMissingName() {
323 // create first managed entity ('foo')
326 'module' => 'com.example.unknown',
329 'entity' => 'CustomSearch',
332 'class_name' => 'CRM_Example_One_Foo',
336 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
339 $this->fail('Expected exception when using invalid declaration');
341 catch (Exception
$e) {
347 * Setup an active module with a malformed entity declaration.
349 public function testMissingEntity() {
350 // create first managed entity ('foo')
353 'module' => 'com.example.unknown',
359 'class_name' => 'CRM_Example_One_Foo',
363 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
366 $this->fail('Expected exception when using invalid declaration');
368 catch (Exception
$e) {
374 * Setup an active module with an entity -- then disable and re-enable the
377 public function testDeactivateReactivateModule() {
378 $manager = CRM_Extension_System
::singleton()->getManager();
380 // create first managed entity ('foo')
382 $decls[] = $this->fixtures
['com.example.one-foo'];
383 // Mock the contextual process info that would be added by CRM_Extension_Manager::install
384 $manager->setProcessesForTesting(['com.example.one' => ['install']]);
385 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
387 $foo = $me->get('com.example.one', 'foo');
388 $this->assertEquals(1, $foo['is_active']);
389 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
390 $this->assertDBQuery(1, 'SELECT is_active FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
392 // now deactivate module, which has empty decls and which cascades to managed object
393 $this->modules
['one']->is_active
= FALSE;
394 // Mock the contextual process info that would be added by CRM_Extension_Manager::disable
395 $manager->setProcessesForTesting(['com.example.one' => ['disable']]);
396 $me = new CRM_Core_ManagedEntities($this->modules
, []);
398 $foo = $me->get('com.example.one', 'foo');
399 $this->assertEquals(0, $foo['is_active']);
400 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
401 $this->assertDBQuery(0, 'SELECT is_active FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
403 // and reactivate module, which again provides decls and which cascades to managed object
404 $this->modules
['one']->is_active
= TRUE;
405 // Mock the contextual process info that would be added by CRM_Extension_Manager::enable
406 $manager->setProcessesForTesting(['com.example.one' => ['enable']]);
407 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
409 $foo = $me->get('com.example.one', 'foo');
410 $this->assertEquals(1, $foo['is_active']);
411 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
412 $this->assertDBQuery(1, 'SELECT is_active FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
414 // Special case: Job entities.
416 // First we repeat the above steps, but adding the context that
417 // CRM_Extension_Manager adds when installing/enabling extensions.
419 // The behaviour should be as above.
420 $decls = [$this->fixtures
['com.example.one-Job']];
421 // Mock the contextual process info that would be added by CRM_Extension_Manager::install
422 $manager->setProcessesForTesting(['com.example.one' => ['install']]);
423 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
425 $job = $me->get('com.example.one', 'Job');
426 $this->assertEquals(1, $job['is_active']);
427 $this->assertEquals('test_job', $job['name']);
428 $this->assertDBQuery(1, 'SELECT is_active FROM civicrm_job WHERE name = "test_job"');
430 $manager->setProcessesForTesting([]);
432 // now deactivate module, which has empty decls and which cascades to managed object
433 $this->modules
['one']->is_active
= FALSE;
434 // Mock the contextual process info that would be added by CRM_Extension_Manager::disable
435 $manager->setProcessesForTesting(['com.example.one' => ['disable']]);
436 $me = new CRM_Core_ManagedEntities($this->modules
, []);
438 $job = $me->get('com.example.one', 'Job');
439 $this->assertEquals(0, $job['is_active']);
440 $this->assertEquals('test_job', $job['name']);
441 $this->assertDBQuery(0, 'SELECT is_active FROM civicrm_job WHERE name = "test_job"');
443 // and reactivate module, which again provides decls and which cascades to managed object
444 $this->modules
['one']->is_active
= TRUE;
445 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
446 // Mock the contextual process info that would be added by CRM_Extension_Manager::enable
447 $manager->setProcessesForTesting(['com.example.one' => ['enable']]);
449 $job = $me->get('com.example.one', 'Job');
450 $this->assertEquals(1, $job['is_active']);
451 $this->assertEquals('test_job', $job['name']);
452 $this->assertDBQuery(1, 'SELECT is_active FROM civicrm_job WHERE name = "test_job"');
454 // Currently: module enabled, job enabled.
455 // Test that if we now manually disable the job, calling reconcile in a
456 // normal flush situation does NOT re-enable it.
457 // ... manually disable job.
458 $this->callAPISuccess('Job', 'create', ['id' => $job['id'], 'is_active' => 0]);
460 // ... now call reconcile in the context of a normal flush operation.
461 // Mock the contextual process info - there would not be any
462 $manager->setProcessesForTesting([]);
463 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
465 $job = $me->get('com.example.one', 'Job');
466 $this->assertEquals(0, $job['is_active'], "Job that was manually set inactive should not have been set active again, but it was.");
467 $this->assertDBQuery(0, 'SELECT is_active FROM civicrm_job WHERE name = "test_job"');
469 // Now call reconcile again, but in the context of the job's extension being installed/enabled. This should re-enable the job.
470 foreach (['enable', 'install'] as $process) {
471 // Manually disable the job
472 $this->callAPISuccess('Job', 'create', ['id' => $job['id'], 'is_active' => 0]);
473 // Mock the contextual process info that would be added by CRM_Extension_Manager::enable
474 $manager->setProcessesForTesting(['com.example.one' => [$process]]);
475 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
477 $job = $me->get('com.example.one', 'Job');
478 $this->assertEquals(1, $job['is_active']);
479 $this->assertEquals('test_job', $job['name']);
480 $this->assertDBQuery(1, 'SELECT is_active FROM civicrm_job WHERE name = "test_job"');
484 $manager->setProcessesForTesting([]);
488 * Setup an active module with an entity -- then entirely uninstall the
491 public function testUninstallModule() {
492 // create first managed entity ('foo')
494 $decls[] = $this->fixtures
['com.example.one-foo'];
495 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
497 $foo = $me->get('com.example.one', 'foo');
498 $this->assertEquals('CRM_Example_One_Foo', $foo['name']);
499 $this->assertDBQuery(1, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
501 // then destroy module; note that decls go away
502 unset($this->modules
['one']);
503 $me = new CRM_Core_ManagedEntities($this->modules
, []);
505 $fooNew = $me->get('com.example.one', 'foo');
506 $this->assertTrue(NULL === $fooNew);
507 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_option_value WHERE name = "CRM_Example_One_Foo"');
510 public function testDependentEntitiesUninstallCleanly() {
512 // Install a module with two dependent managed entities
514 $decls[] = $this->fixtures
['com.example.one-CustomGroup'];
515 $decls[] = $this->fixtures
['com.example.one-CustomField'];
516 $me = new CRM_Core_ManagedEntities($this->modules
, $decls);
519 // Uninstall the module
520 unset($this->modules
['one']);
521 $me = new CRM_Core_ManagedEntities($this->modules
, []);
524 // Ensure that no managed entities remain in the civicrm_managed
525 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_managed');
527 // Ensure that com.example.one-CustomGroup is deleted
528 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_custom_group WHERE name = "test_custom_group"');
530 // Ensure that com.example.one-CustomField is deleted
531 $this->assertDBQuery(0, 'SELECT count(*) FROM civicrm_custom_field WHERE name = "test_custom_field"');