2 require_once 'CiviTest/CiviCaseTestCase.php';
5 * Class CRM_Case_PseudoConstantTest
8 class CRM_Case_XMLProcessor_ProcessTest
extends CiviCaseTestCase
{
10 public function setUp() {
13 $this->defaultAssigneeOptionsValues
= [];
15 $this->setupContacts();
16 $this->setupDefaultAssigneeOptions();
17 $this->setupRelationships();
18 $this->setupMoreRelationshipTypes();
19 $this->setupActivityDefinitions();
21 $this->process
= new CRM_Case_XMLProcessor_Process();
24 public function tearDown() {
25 $this->deleteMoreRelationshipTypes();
31 * Creates sample contacts.
33 protected function setUpContacts() {
35 'ana' => $this->individualCreate(),
36 'beto' => $this->individualCreate(),
37 'carlos' => $this->individualCreate(),
42 * Adds the default assignee group and options to the test database.
43 * It also stores the IDs of the options in an index.
45 protected function setupDefaultAssigneeOptions() {
47 'NONE', 'BY_RELATIONSHIP', 'SPECIFIC_CONTACT', 'USER_CREATING_THE_CASE',
50 CRM_Core_BAO_OptionGroup
::ensureOptionGroupExists([
51 'name' => 'activity_default_assignee',
54 foreach ($options as $option) {
55 $optionValue = CRM_Core_BAO_OptionValue
::ensureOptionValueExists([
56 'option_group_id' => 'activity_default_assignee',
61 $this->defaultAssigneeOptionsValues
[$option] = $optionValue['value'];
66 * Adds a relationship between the activity's target contact and default assignee.
68 protected function setupRelationships() {
69 $this->relationships
= [
70 'ana_is_pupil_of_beto' => [
72 'name_a_b' => 'Pupil of',
73 'name_b_a' => 'Instructor',
74 'contact_id_a' => $this->contacts
['ana'],
75 'contact_id_b' => $this->contacts
['beto'],
77 'ana_is_spouse_of_carlos' => [
79 'name_a_b' => 'Spouse of',
80 'name_b_a' => 'Spouse of',
81 'contact_id_a' => $this->contacts
['ana'],
82 'contact_id_b' => $this->contacts
['carlos'],
84 'unassigned_employee' => [
86 'name_a_b' => 'Employee of',
87 'name_b_a' => 'Employer',
91 foreach ($this->relationships
as $name => &$relationship) {
92 $relationship['type_id'] = $this->relationshipTypeCreate([
93 'contact_type_a' => 'Individual',
94 'contact_type_b' => 'Individual',
95 'name_a_b' => $relationship['name_a_b'],
96 'label_a_b' => $relationship['name_a_b'],
97 'name_b_a' => $relationship['name_b_a'],
98 'label_b_a' => $relationship['name_b_a'],
101 if (isset($relationship['contact_id_a'])) {
102 $this->callAPISuccess('Relationship', 'create', [
103 'contact_id_a' => $relationship['contact_id_a'],
104 'contact_id_b' => $relationship['contact_id_b'],
105 'relationship_type_id' => $relationship['type_id'],
112 * Set up some additional relationship types for some specific tests.
114 protected function setupMoreRelationshipTypes() {
115 $this->moreRelationshipTypes
= [
116 'unidirectional_name_label_different' => [
118 'name_a_b' => 'jm7ab',
119 'label_a_b' => 'Jedi Master is',
120 'name_b_a' => 'jm7ba',
121 'label_b_a' => 'Jedi Master for',
122 'description' => 'Jedi Master',
124 'unidirectional_name_label_same' => [
126 'name_a_b' => 'Quilt Maker is',
127 'label_a_b' => 'Quilt Maker is',
128 'name_b_a' => 'Quilt Maker for',
129 'label_b_a' => 'Quilt Maker for',
130 'description' => 'Quilt Maker',
132 'bidirectional_name_label_different' => [
135 'label_a_b' => 'Friend of',
137 'label_b_a' => 'Friend of',
138 'description' => 'Friend',
140 'bidirectional_name_label_same' => [
142 'name_a_b' => 'Enemy of',
143 'label_a_b' => 'Enemy of',
144 'name_b_a' => 'Enemy of',
145 'label_b_a' => 'Enemy of',
146 'description' => 'Enemy',
150 foreach ($this->moreRelationshipTypes
as &$relationship) {
151 $relationship['type_id'] = $this->relationshipTypeCreate([
152 'contact_type_a' => 'Individual',
153 'contact_type_b' => 'Individual',
154 'name_a_b' => $relationship['name_a_b'],
155 'label_a_b' => $relationship['label_a_b'],
156 'name_b_a' => $relationship['name_b_a'],
157 'label_b_a' => $relationship['label_b_a'],
158 'description' => $relationship['description'],
164 * Clean up additional relationship types (tearDown).
166 protected function deleteMoreRelationshipTypes() {
167 foreach ($this->moreRelationshipTypes
as $relationship) {
168 $this->callAPISuccess('relationship_type', 'delete', ['id' => $relationship['type_id']]);
173 * Defines the the activity parameters and XML definitions. These can be used
174 * to create the activity.
176 protected function setupActivityDefinitions() {
177 $activityTypeXml = '<activity-type><name>Open Case</name></activity-type>';
178 $this->activityTypeXml
= new SimpleXMLElement($activityTypeXml);
179 $this->activityParams
= [
180 'activity_date_time' => date('Ymd'),
181 // @todo This seems wrong, it just happens to work out because both caseId and caseTypeId equal 1 in the stock setup here.
182 'caseID' => $this->caseTypeId
,
183 'clientID' => $this->contacts
['ana'],
184 'creatorID' => $this->_loggedInUser
,
189 * Tests the creation of activities where the default assignee should be the
190 * target contact's instructor. Beto is the instructor for Ana.
192 public function testCreateActivityWithDefaultContactByRelationship() {
193 $relationship = $this->relationships
['ana_is_pupil_of_beto'];
194 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
195 $this->activityTypeXml
->default_assignee_relationship
= "{$relationship['type_id']}_b_a";
197 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
198 $this->assertActivityAssignedToContactExists($this->contacts
['beto']);
202 * Test the creation of activities where the default assignee should not
203 * end up being a contact from another case where it has the same client
206 public function testCreateActivityWithDefaultContactByRelationshipTwoCases() {
208 At this point the stock setup looks like this:
209 Case 1: no roles assigned
210 Non-case relationship with ana as pupil of beto
211 Non-case relationship with ana as spouse of carlos
214 Make another case for the same client ana.
215 Add a pupil role on that new case with some other person.
216 Make an activity on the first case.
218 Since there is a non-case relationship of that type for the
219 right person we do want it to take that one even though there is no role
220 on the first case, i.e. it SHOULD fall back to non-case relationships.
223 Then we want to get rid of the non-case relationship and try again. In
224 this situation it should not make any assignment, i.e. it should not
225 take the other person from the other case. The original bug was that it
226 would assign the activity to that other person from the other case. This
230 $relationship = $this->relationships
['ana_is_pupil_of_beto'];
232 // Make another case and add a case role with the same relationship we
233 // want, but a different person.
234 $caseObj = $this->createCase($this->contacts
['ana'], $this->_loggedInUser
);
235 $this->callAPISuccess('Relationship', 'create', [
236 'contact_id_a' => $this->contacts
['ana'],
237 'contact_id_b' => $this->contacts
['carlos'],
238 'relationship_type_id' => $relationship['type_id'],
239 'case_id' => $caseObj->id
,
242 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
243 $this->activityTypeXml
->default_assignee_relationship
= "{$relationship['type_id']}_b_a";
245 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
247 // We can't use assertActivityAssignedToContactExists because it assumes
248 // there's only one activity in the database, but we have several from the
249 // second case. We want the one we just created on the first case.
250 $result = $this->callAPISuccess('Activity', 'get', [
251 'case_id' => $this->activityParams
['caseID'],
252 'return' => ['assignee_contact_id'],
254 $this->assertCount(1, $result);
255 foreach ($result as $activity) {
256 // Note the first parameter is turned into an array to match the second.
257 $this->assertEquals([$this->contacts
['beto']], $activity['assignee_contact_id']);
260 // Now remove the non-case relationship.
261 $result = $this->callAPISuccess('Relationship', 'get', [
262 'case_id' => ['IS NULL' => 1],
263 'relationship_type_id' => $relationship['type_id'],
264 'contact_id_a' => $this->contacts
['ana'],
265 'contact_id_b' => $this->contacts
['beto'],
267 $this->assertCount(1, $result);
268 foreach ($result as $activity) {
269 $result = $this->callAPISuccess('Relationship', 'delete', ['id' => $activity['id']]);
272 // Create another activity on the first case. Make it a different activity
273 // type so we can find it better.
274 $activityXml = '<activity-type><name>Follow up</name></activity-type>';
275 $activityXmlElement = new SimpleXMLElement($activityXml);
276 $activityXmlElement->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
277 $activityXmlElement->default_assignee_relationship
= "{$relationship['type_id']}_b_a";
278 $this->process
->createActivity($activityXmlElement, $this->activityParams
);
280 $result = $this->callAPISuccess('Activity', 'get', [
281 'case_id' => $this->activityParams
['caseID'],
282 'activity_type_id' => 'Follow up',
283 'return' => ['assignee_contact_id'],
285 $this->assertCount(1, $result);
286 foreach ($result as $activity) {
287 // It should be empty, not the contact from the second case.
288 $this->assertEmpty($activity['assignee_contact_id']);
293 * Create and return case object of given Client ID.
294 * @todo This is copy/paste from Case/BAO/CaseTest - should put into base class?
296 * @param $loggedInUser
297 * @return CRM_Case_BAO_Case
299 private function createCase($clientId, $loggedInUser = NULL) {
300 if (empty($loggedInUser)) {
301 // backwards compatibility - but it's more typical that the creator is a different person than the client
302 $loggedInUser = $clientId;
305 'activity_subject' => 'Case Subject',
306 'client_id' => $clientId,
309 'case_type' => 'housing_support',
310 'subject' => 'Case Subject',
311 'start_date' => date("Y-m-d"),
312 'start_date_time' => date("YmdHis"),
314 'activity_details' => '',
316 $form = new CRM_Case_Form_Case();
317 $caseObj = $form->testSubmit($caseParams, "OpenCase", $loggedInUser, "standalone");
322 * Tests when the default assignee relationship exists, but in the other direction only.
323 * Ana is a pupil, but has no pupils related to her.
325 public function testCreateActivityWithDefaultContactByRelationshipMissing() {
326 $relationship = $this->relationships
['ana_is_pupil_of_beto'];
327 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
328 $this->activityTypeXml
->default_assignee_relationship
= "{$relationship['type_id']}_a_b";
330 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
331 $this->assertActivityAssignedToContactExists(NULL);
335 * Tests when the the default assignee relationship exists and is a bidirectional
336 * relationship. Ana and Carlos are spouses.
338 public function testCreateActivityWithDefaultContactByRelationshipBidirectional() {
339 $relationship = $this->relationships
['ana_is_spouse_of_carlos'];
340 $this->activityParams
['clientID'] = $this->contacts
['carlos'];
341 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
342 $this->activityTypeXml
->default_assignee_relationship
= "{$relationship['type_id']}_a_b";
344 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
345 $this->assertActivityAssignedToContactExists($this->contacts
['ana']);
349 * Tests when the default assignee relationship does not exist. Ana is not an
350 * employee for anyone.
352 public function testCreateActivityWithDefaultContactByRelationButTheresNoRelationship() {
353 $relationship = $this->relationships
['unassigned_employee'];
354 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
355 $this->activityTypeXml
->default_assignee_relationship
= "{$relationship['type_id']}_b_a";
357 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
358 $this->assertActivityAssignedToContactExists(NULL);
362 * Tests the creation of activities with default assignee set to a specific contact.
364 public function testCreateActivityAssignedToSpecificContact() {
365 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['SPECIFIC_CONTACT'];
366 $this->activityTypeXml
->default_assignee_contact
= $this->contacts
['carlos'];
368 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
369 $this->assertActivityAssignedToContactExists($this->contacts
['carlos']);
373 * Tests the creation of activities with default assignee set to a specific contact,
374 * but the contact does not exist.
376 public function testCreateActivityAssignedToNonExistantSpecificContact() {
377 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['SPECIFIC_CONTACT'];
378 $this->activityTypeXml
->default_assignee_contact
= 987456321;
380 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
381 $this->assertActivityAssignedToContactExists(NULL);
385 * Tests the creation of activities with the default assignee being the one
386 * creating the case's activity.
388 public function testCreateActivityAssignedToUserCreatingTheCase() {
389 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['USER_CREATING_THE_CASE'];
391 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
392 $this->assertActivityAssignedToContactExists($this->_loggedInUser
);
396 * Tests the creation of activities when the default assignee is set to NONE.
398 public function testCreateActivityAssignedNoUser() {
399 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['NONE'];
401 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
402 $this->assertActivityAssignedToContactExists(NULL);
406 * Tests the creation of activities when the default assignee is set to NONE.
408 public function testCreateActivityWithNoDefaultAssigneeOption() {
409 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
410 $this->assertActivityAssignedToContactExists(NULL);
414 * Asserts that an activity was created where the assignee was the one related
415 * to the target contact.
417 * @param int|null $assigneeContactId the ID of the expected assigned contact or NULL if expected to be empty.
419 protected function assertActivityAssignedToContactExists($assigneeContactId) {
420 $expectedContact = $assigneeContactId === NULL ?
[] : [$assigneeContactId];
421 $result = $this->callAPISuccess('Activity', 'get', [
422 'target_contact_id' => $this->activityParams
['clientID'],
423 'return' => ['assignee_contact_id'],
425 $activity = CRM_Utils_Array
::first($result['values']);
427 $this->assertNotNull($activity, 'Target contact has no activities assigned to them');
428 $this->assertEquals($expectedContact, $activity['assignee_contact_id'], 'Activity is not assigned to expected contact');
432 * Test that caseRoles() doesn't have name and label mixed up.
434 * @param $key string The array key in the moreRelationshipTypes array that
435 * is the relationship type we're currently testing. So not necessarily
436 * unique for each entry in the dataprovider since want to test a given
437 * relationship type against multiple xml strings. It's not a test
438 * identifier, it's an array key to use to look up something.
439 * @param $xmlString string
440 * @param $expected array
441 * @param $dontcare array We're re-using the data provider for two tests and
442 * we don't care about those expected values.
444 * @dataProvider xmlCaseRoleDataProvider
446 public function testCaseRoles($key, $xmlString, $expected, $dontcare) {
447 $xmlObj = new SimpleXMLElement($xmlString);
449 // element 0 is direction (a_b), 1 is the text we want
450 $expectedArray = empty($expected) ?
[] : ["{$this->moreRelationshipTypes[$key]['type_id']}_{$expected[0]}" => $expected[1]];
452 $this->assertEquals($expectedArray, $this->process
->caseRoles($xmlObj->CaseRoles
, FALSE));
456 * Test that locateNameOrLabel doesn't have name and label mixed up.
458 * @param $key string The array key in the moreRelationshipTypes array that
459 * is the relationship type we're currently testing. So not necessarily
460 * unique for each entry in the dataprovider since want to test a given
461 * relationship type against multiple xml strings. It's not a test
462 * identifier, it's an array key to use to look up something.
463 * @param $xmlString string
464 * @param $dontcare array We're re-using the data provider for two tests and
465 * we don't care about those expected values.
466 * @param $expected array
468 * @dataProvider xmlCaseRoleDataProvider
470 public function testLocateNameOrLabel($key, $xmlString, $dontcare, $expected) {
471 $xmlObj = new SimpleXMLElement($xmlString);
473 // element 0 is direction (a_b), 1 is the text we want.
474 // In case of failure, the function is expected to return FALSE for the
475 // direction and then for the text it just gives us back the string we
477 $expectedArray = empty($expected[0])
478 ?
[FALSE, $expected[1]]
479 : ["{$this->moreRelationshipTypes[$key]['type_id']}_{$expected[0]}", $expected[1]];
481 $this->assertEquals($expectedArray, $this->process
->locateNameOrLabel($xmlObj->CaseRoles
->RelationshipType
));
485 * Data provider for testCaseRoles and testLocateNameOrLabel
488 public function xmlCaseRoleDataProvider() {
490 // Simulate one that has been converted to the format it should be going
491 // forward, where name is the actual name, i.e. same as machineName.
493 // this is the array key in the $this->moreRelationshipTypes array
494 'unidirectional_name_label_different',
496 '<CaseType><CaseRoles><RelationshipType><name>jm7ba</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
497 // this is the expected for testCaseRoles
498 ['a_b', 'Jedi Master is'],
499 // this is the expected for testLocateNameOrLabel
502 // Simulate one that is still in label format, i.e. one that is still in
503 // xml files that haven't been updated, or in the db but upgrade script
506 'unidirectional_name_label_different',
507 '<CaseType><CaseRoles><RelationshipType><name>Jedi Master for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
508 ['a_b', 'Jedi Master is'],
511 // Ditto but where we know name and label are the same in the db.
513 'unidirectional_name_label_same',
514 '<CaseType><CaseRoles><RelationshipType><name>Quilt Maker for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
515 ['a_b', 'Quilt Maker is'],
516 ['a_b', 'Quilt Maker for'],
518 // Simulate one that is messed up and should fail, e.g. like a typo
519 // in an xml file. Here we've made a typo on purpose.
521 'unidirectional_name_label_different',
522 '<CaseType><CaseRoles><RelationshipType><name>Jedi Masterrrr for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
524 [FALSE, 'Jedi Masterrrr for'],
526 // Now some similar tests to above but for bidirectional relationships.
527 // Bidirectional relationship, name and label different, using machine name.
529 'bidirectional_name_label_different',
530 '<CaseType><CaseRoles><RelationshipType><name>f12</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
531 ['b_a', 'Friend of'],
534 // Bidirectional relationship, name and label different, using display label.
536 'bidirectional_name_label_different',
537 '<CaseType><CaseRoles><RelationshipType><name>Friend of</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
538 ['b_a', 'Friend of'],
541 // Bidirectional relationship, name and label same.
543 'bidirectional_name_label_same',
544 '<CaseType><CaseRoles><RelationshipType><name>Enemy of</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
552 * Test XMLProcessor activityTypes()
554 public function testXmlProcessorActivityTypes() {
555 // First change an activity's label since we also test getting the labels.
556 // @todo Having a brain freeze or something - can't do this in one step?
557 $activity_type_id = $this->callApiSuccess('OptionValue', 'get', [
558 'option_group_id' => 'activity_type',
559 'name' => 'Medical evaluation',
561 $this->callApiSuccess('OptionValue', 'create', [
562 'id' => $activity_type_id,
563 'label' => 'Medical evaluation changed',
566 $p = new CRM_Case_XMLProcessor_Process();
567 $xml = $p->retrieve('housing_support');
569 // Test getting the `name`s
570 $activityTypes = $p->activityTypes($xml->ActivityTypes
, FALSE, FALSE, FALSE);
574 55 => 'Medical evaluation',
575 56 => 'Mental health evaluation',
576 57 => 'Secure temporary housing',
577 60 => 'Income and benefits stabilization',
578 58 => 'Long-term housing plan',
580 15 => 'Change Case Type',
581 16 => 'Change Case Status',
582 18 => 'Change Case Start Date',
588 // While we're here and have the `name`s check the editable types in
589 // Settings.xml which is something that gets called reasonably often
590 // thru CRM_Case_XMLProcessor_Process::activityTypes().
591 $activityTypeValues = array_flip($activityTypes);
592 $xml = $p->retrieve('Settings');
593 $settings = $p->activityTypes($xml->ActivityTypes
, FALSE, FALSE, 'edit');
597 0 => $activityTypeValues['Change Case Status'],
598 1 => $activityTypeValues['Change Case Start Date'],
605 $xml = $p->retrieve('housing_support');
606 $activityTypes = $p->activityTypes($xml->ActivityTypes
, FALSE, TRUE, FALSE);
610 55 => 'Medical evaluation changed',
611 56 => 'Mental health evaluation',
612 57 => 'Secure temporary housing',
613 60 => 'Income and benefits stabilization',
614 58 => 'Long-term housing plan',
616 15 => 'Change Case Type',
617 16 => 'Change Case Status',
618 18 => 'Change Case Start Date',