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 * Tests when the default assignee relationship exists, but in the other direction only.
294 * Ana is a pupil, but has no pupils related to her.
296 public function testCreateActivityWithDefaultContactByRelationshipMissing() {
297 $relationship = $this->relationships
['ana_is_pupil_of_beto'];
298 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
299 $this->activityTypeXml
->default_assignee_relationship
= "{$relationship['type_id']}_a_b";
301 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
302 $this->assertActivityAssignedToContactExists(NULL);
306 * Tests when the the default assignee relationship exists and is a bidirectional
307 * relationship. Ana and Carlos are spouses.
309 public function testCreateActivityWithDefaultContactByRelationshipBidirectional() {
310 $relationship = $this->relationships
['ana_is_spouse_of_carlos'];
311 $this->activityParams
['clientID'] = $this->contacts
['carlos'];
312 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
313 $this->activityTypeXml
->default_assignee_relationship
= "{$relationship['type_id']}_a_b";
315 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
316 $this->assertActivityAssignedToContactExists($this->contacts
['ana']);
320 * Tests when the default assignee relationship does not exist. Ana is not an
321 * employee for anyone.
323 public function testCreateActivityWithDefaultContactByRelationButTheresNoRelationship() {
324 $relationship = $this->relationships
['unassigned_employee'];
325 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['BY_RELATIONSHIP'];
326 $this->activityTypeXml
->default_assignee_relationship
= "{$relationship['type_id']}_b_a";
328 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
329 $this->assertActivityAssignedToContactExists(NULL);
333 * Tests the creation of activities with default assignee set to a specific contact.
335 public function testCreateActivityAssignedToSpecificContact() {
336 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['SPECIFIC_CONTACT'];
337 $this->activityTypeXml
->default_assignee_contact
= $this->contacts
['carlos'];
339 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
340 $this->assertActivityAssignedToContactExists($this->contacts
['carlos']);
344 * Tests the creation of activities with default assignee set to a specific contact,
345 * but the contact does not exist.
347 public function testCreateActivityAssignedToNonExistantSpecificContact() {
348 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['SPECIFIC_CONTACT'];
349 $this->activityTypeXml
->default_assignee_contact
= 987456321;
351 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
352 $this->assertActivityAssignedToContactExists(NULL);
356 * Tests the creation of activities with the default assignee being the one
357 * creating the case's activity.
359 public function testCreateActivityAssignedToUserCreatingTheCase() {
360 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['USER_CREATING_THE_CASE'];
362 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
363 $this->assertActivityAssignedToContactExists($this->_loggedInUser
);
367 * Tests the creation of activities when the default assignee is set to NONE.
369 public function testCreateActivityAssignedNoUser() {
370 $this->activityTypeXml
->default_assignee_type
= $this->defaultAssigneeOptionsValues
['NONE'];
372 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
373 $this->assertActivityAssignedToContactExists(NULL);
377 * Tests the creation of activities when the default assignee is set to NONE.
379 public function testCreateActivityWithNoDefaultAssigneeOption() {
380 $this->process
->createActivity($this->activityTypeXml
, $this->activityParams
);
381 $this->assertActivityAssignedToContactExists(NULL);
385 * Asserts that an activity was created where the assignee was the one related
386 * to the target contact.
388 * @param int|null $assigneeContactId the ID of the expected assigned contact or NULL if expected to be empty.
390 protected function assertActivityAssignedToContactExists($assigneeContactId) {
391 $expectedContact = $assigneeContactId === NULL ?
[] : [$assigneeContactId];
392 $result = $this->callAPISuccess('Activity', 'get', [
393 'target_contact_id' => $this->activityParams
['clientID'],
394 'return' => ['assignee_contact_id'],
396 $activity = CRM_Utils_Array
::first($result['values']);
398 $this->assertNotNull($activity, 'Target contact has no activities assigned to them');
399 $this->assertEquals($expectedContact, $activity['assignee_contact_id'], 'Activity is not assigned to expected contact');
403 * Test that caseRoles() doesn't have name and label mixed up.
405 * @param $key string The array key in the moreRelationshipTypes array that
406 * is the relationship type we're currently testing. So not necessarily
407 * unique for each entry in the dataprovider since want to test a given
408 * relationship type against multiple xml strings. It's not a test
409 * identifier, it's an array key to use to look up something.
410 * @param $xmlString string
411 * @param $expected array
412 * @param $dontcare array We're re-using the data provider for two tests and
413 * we don't care about those expected values.
415 * @dataProvider xmlCaseRoleDataProvider
417 public function testCaseRoles($key, $xmlString, $expected, $dontcare) {
418 $xmlObj = new SimpleXMLElement($xmlString);
420 // element 0 is direction (a_b), 1 is the text we want
421 $expectedArray = empty($expected) ?
[] : ["{$this->moreRelationshipTypes[$key]['type_id']}_{$expected[0]}" => $expected[1]];
423 $this->assertEquals($expectedArray, $this->process
->caseRoles($xmlObj->CaseRoles
, FALSE));
427 * Test that locateNameOrLabel doesn't have name and label mixed up.
429 * @param $key string The array key in the moreRelationshipTypes array that
430 * is the relationship type we're currently testing. So not necessarily
431 * unique for each entry in the dataprovider since want to test a given
432 * relationship type against multiple xml strings. It's not a test
433 * identifier, it's an array key to use to look up something.
434 * @param $xmlString string
435 * @param $dontcare array We're re-using the data provider for two tests and
436 * we don't care about those expected values.
437 * @param $expected array
439 * @dataProvider xmlCaseRoleDataProvider
441 public function testLocateNameOrLabel($key, $xmlString, $dontcare, $expected) {
442 $xmlObj = new SimpleXMLElement($xmlString);
444 // element 0 is direction (a_b), 1 is the text we want.
445 // In case of failure, the function is expected to return FALSE for the
446 // direction and then for the text it just gives us back the string we
448 $expectedArray = empty($expected[0])
449 ?
[FALSE, $expected[1]]
450 : ["{$this->moreRelationshipTypes[$key]['type_id']}_{$expected[0]}", $expected[1]];
452 $this->assertEquals($expectedArray, $this->process
->locateNameOrLabel($xmlObj->CaseRoles
->RelationshipType
));
456 * Data provider for testCaseRoles and testLocateNameOrLabel
459 public function xmlCaseRoleDataProvider() {
461 // Simulate one that has been converted to the format it should be going
462 // forward, where name is the actual name, i.e. same as machineName.
464 // this is the array key in the $this->moreRelationshipTypes array
465 'unidirectional_name_label_different',
467 '<CaseType><CaseRoles><RelationshipType><name>jm7ba</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
468 // this is the expected for testCaseRoles
469 ['a_b', 'Jedi Master is'],
470 // this is the expected for testLocateNameOrLabel
473 // Simulate one that is still in label format, i.e. one that is still in
474 // xml files that haven't been updated, or in the db but upgrade script
477 'unidirectional_name_label_different',
478 '<CaseType><CaseRoles><RelationshipType><name>Jedi Master for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
479 ['a_b', 'Jedi Master is'],
482 // Ditto but where we know name and label are the same in the db.
484 'unidirectional_name_label_same',
485 '<CaseType><CaseRoles><RelationshipType><name>Quilt Maker for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
486 ['a_b', 'Quilt Maker is'],
487 ['a_b', 'Quilt Maker for'],
489 // Simulate one that is messed up and should fail, e.g. like a typo
490 // in an xml file. Here we've made a typo on purpose.
492 'unidirectional_name_label_different',
493 '<CaseType><CaseRoles><RelationshipType><name>Jedi Masterrrr for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
495 [FALSE, 'Jedi Masterrrr for'],
497 // Now some similar tests to above but for bidirectional relationships.
498 // Bidirectional relationship, name and label different, using machine name.
500 'bidirectional_name_label_different',
501 '<CaseType><CaseRoles><RelationshipType><name>f12</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
502 ['b_a', 'Friend of'],
505 // Bidirectional relationship, name and label different, using display label.
507 'bidirectional_name_label_different',
508 '<CaseType><CaseRoles><RelationshipType><name>Friend of</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
509 ['b_a', 'Friend of'],
512 // Bidirectional relationship, name and label same.
514 'bidirectional_name_label_same',
515 '<CaseType><CaseRoles><RelationshipType><name>Enemy of</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
523 * Test XMLProcessor activityTypes()
525 public function testXmlProcessorActivityTypes() {
526 // First change an activity's label since we also test getting the labels.
527 // @todo Having a brain freeze or something - can't do this in one step?
528 $activity_type_id = $this->callApiSuccess('OptionValue', 'get', [
529 'option_group_id' => 'activity_type',
530 'name' => 'Medical evaluation',
532 $this->callApiSuccess('OptionValue', 'create', [
533 'id' => $activity_type_id,
534 'label' => 'Medical evaluation changed',
537 $p = new CRM_Case_XMLProcessor_Process();
538 $xml = $p->retrieve('housing_support');
540 // Test getting the `name`s
541 $activityTypes = $p->activityTypes($xml->ActivityTypes
, FALSE, FALSE, FALSE);
545 55 => 'Medical evaluation',
546 56 => 'Mental health evaluation',
547 57 => 'Secure temporary housing',
548 60 => 'Income and benefits stabilization',
549 58 => 'Long-term housing plan',
551 15 => 'Change Case Type',
552 16 => 'Change Case Status',
553 18 => 'Change Case Start Date',
559 // While we're here and have the `name`s check the editable types in
560 // Settings.xml which is something that gets called reasonably often
561 // thru CRM_Case_XMLProcessor_Process::activityTypes().
562 $activityTypeValues = array_flip($activityTypes);
563 $xml = $p->retrieve('Settings');
564 $settings = $p->activityTypes($xml->ActivityTypes
, FALSE, FALSE, 'edit');
568 0 => $activityTypeValues['Change Case Status'],
569 1 => $activityTypeValues['Change Case Start Date'],
576 $xml = $p->retrieve('housing_support');
577 $activityTypes = $p->activityTypes($xml->ActivityTypes
, FALSE, TRUE, FALSE);
581 55 => 'Medical evaluation changed',
582 56 => 'Mental health evaluation',
583 57 => 'Secure temporary housing',
584 60 => 'Income and benefits stabilization',
585 58 => 'Long-term housing plan',
587 15 => 'Change Case Type',
588 16 => 'Change Case Status',
589 18 => 'Change Case Start Date',