Merge pull request #15541 from ixiam/dev_issue#1324
[civicrm-core.git] / tests / phpunit / CRM / Case / XMLProcessor / ProcessTest.php
1 <?php
2 require_once 'CiviTest/CiviCaseTestCase.php';
3
4 /**
5 * Class CRM_Case_PseudoConstantTest
6 * @group headless
7 */
8 class CRM_Case_XMLProcessor_ProcessTest extends CiviCaseTestCase {
9
10 public function setUp() {
11 parent::setUp();
12
13 $this->defaultAssigneeOptionsValues = [];
14
15 $this->setupContacts();
16 $this->setupDefaultAssigneeOptions();
17 $this->setupRelationships();
18 $this->setupMoreRelationshipTypes();
19 $this->setupActivityDefinitions();
20
21 $this->process = new CRM_Case_XMLProcessor_Process();
22 }
23
24 public function tearDown() {
25 $this->deleteMoreRelationshipTypes();
26
27 parent::tearDown();
28 }
29
30 /**
31 * Creates sample contacts.
32 */
33 protected function setUpContacts() {
34 $this->contacts = [
35 'ana' => $this->individualCreate(),
36 'beto' => $this->individualCreate(),
37 'carlos' => $this->individualCreate(),
38 ];
39 }
40
41 /**
42 * Adds the default assignee group and options to the test database.
43 * It also stores the IDs of the options in an index.
44 */
45 protected function setupDefaultAssigneeOptions() {
46 $options = [
47 'NONE', 'BY_RELATIONSHIP', 'SPECIFIC_CONTACT', 'USER_CREATING_THE_CASE',
48 ];
49
50 CRM_Core_BAO_OptionGroup::ensureOptionGroupExists([
51 'name' => 'activity_default_assignee',
52 ]);
53
54 foreach ($options as $option) {
55 $optionValue = CRM_Core_BAO_OptionValue::ensureOptionValueExists([
56 'option_group_id' => 'activity_default_assignee',
57 'name' => $option,
58 'label' => $option,
59 ]);
60
61 $this->defaultAssigneeOptionsValues[$option] = $optionValue['value'];
62 }
63 }
64
65 /**
66 * Adds a relationship between the activity's target contact and default assignee.
67 */
68 protected function setupRelationships() {
69 $this->relationships = [
70 'ana_is_pupil_of_beto' => [
71 'type_id' => NULL,
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'],
76 ],
77 'ana_is_spouse_of_carlos' => [
78 'type_id' => NULL,
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'],
83 ],
84 'unassigned_employee' => [
85 'type_id' => NULL,
86 'name_a_b' => 'Employee of',
87 'name_b_a' => 'Employer',
88 ],
89 ];
90
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'],
99 ]);
100
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'],
106 ]);
107 }
108 }
109 }
110
111 /**
112 * Set up some additional relationship types for some specific tests.
113 */
114 protected function setupMoreRelationshipTypes() {
115 $this->moreRelationshipTypes = [
116 'unidirectional_name_label_different' => [
117 'type_id' => NULL,
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',
123 ],
124 'unidirectional_name_label_same' => [
125 'type_id' => NULL,
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',
131 ],
132 'bidirectional_name_label_different' => [
133 'type_id' => NULL,
134 'name_a_b' => 'f12',
135 'label_a_b' => 'Friend of',
136 'name_b_a' => 'f12',
137 'label_b_a' => 'Friend of',
138 'description' => 'Friend',
139 ],
140 'bidirectional_name_label_same' => [
141 'type_id' => NULL,
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',
147 ],
148 ];
149
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'],
159 ]);
160 }
161 }
162
163 /**
164 * Clean up additional relationship types (tearDown).
165 */
166 protected function deleteMoreRelationshipTypes() {
167 foreach ($this->moreRelationshipTypes as $relationship) {
168 $this->callAPISuccess('relationship_type', 'delete', ['id' => $relationship['type_id']]);
169 }
170 }
171
172 /**
173 * Defines the the activity parameters and XML definitions. These can be used
174 * to create the activity.
175 */
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 'caseID' => $this->caseTypeId,
182 'clientID' => $this->contacts['ana'],
183 'creatorID' => $this->_loggedInUser,
184 ];
185 }
186
187 /**
188 * Tests the creation of activities where the default assignee should be the
189 * target contact's instructor. Beto is the instructor for Ana.
190 */
191 public function testCreateActivityWithDefaultContactByRelationship() {
192 $relationship = $this->relationships['ana_is_pupil_of_beto'];
193 $this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['BY_RELATIONSHIP'];
194 $this->activityTypeXml->default_assignee_relationship = "{$relationship['type_id']}_b_a";
195
196 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
197 $this->assertActivityAssignedToContactExists($this->contacts['beto']);
198 }
199
200 /**
201 * Tests when the default assignee relationship exists, but in the other direction only.
202 * Ana is a pupil, but has no pupils related to her.
203 */
204 public function testCreateActivityWithDefaultContactByRelationshipMissing() {
205 $relationship = $this->relationships['ana_is_pupil_of_beto'];
206 $this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['BY_RELATIONSHIP'];
207 $this->activityTypeXml->default_assignee_relationship = "{$relationship['type_id']}_a_b";
208
209 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
210 $this->assertActivityAssignedToContactExists(NULL);
211 }
212
213 /**
214 * Tests when the the default assignee relationship exists and is a bidirectional
215 * relationship. Ana and Carlos are spouses.
216 */
217 public function testCreateActivityWithDefaultContactByRelationshipBidirectional() {
218 $relationship = $this->relationships['ana_is_spouse_of_carlos'];
219 $this->activityParams['clientID'] = $this->contacts['carlos'];
220 $this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['BY_RELATIONSHIP'];
221 $this->activityTypeXml->default_assignee_relationship = "{$relationship['type_id']}_a_b";
222
223 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
224 $this->assertActivityAssignedToContactExists($this->contacts['ana']);
225 }
226
227 /**
228 * Tests when the default assignee relationship does not exist. Ana is not an
229 * employee for anyone.
230 */
231 public function testCreateActivityWithDefaultContactByRelationButTheresNoRelationship() {
232 $relationship = $this->relationships['unassigned_employee'];
233 $this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['BY_RELATIONSHIP'];
234 $this->activityTypeXml->default_assignee_relationship = "{$relationship['type_id']}_b_a";
235
236 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
237 $this->assertActivityAssignedToContactExists(NULL);
238 }
239
240 /**
241 * Tests the creation of activities with default assignee set to a specific contact.
242 */
243 public function testCreateActivityAssignedToSpecificContact() {
244 $this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['SPECIFIC_CONTACT'];
245 $this->activityTypeXml->default_assignee_contact = $this->contacts['carlos'];
246
247 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
248 $this->assertActivityAssignedToContactExists($this->contacts['carlos']);
249 }
250
251 /**
252 * Tests the creation of activities with default assignee set to a specific contact,
253 * but the contact does not exist.
254 */
255 public function testCreateActivityAssignedToNonExistantSpecificContact() {
256 $this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['SPECIFIC_CONTACT'];
257 $this->activityTypeXml->default_assignee_contact = 987456321;
258
259 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
260 $this->assertActivityAssignedToContactExists(NULL);
261 }
262
263 /**
264 * Tests the creation of activities with the default assignee being the one
265 * creating the case's activity.
266 */
267 public function testCreateActivityAssignedToUserCreatingTheCase() {
268 $this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['USER_CREATING_THE_CASE'];
269
270 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
271 $this->assertActivityAssignedToContactExists($this->_loggedInUser);
272 }
273
274 /**
275 * Tests the creation of activities when the default assignee is set to NONE.
276 */
277 public function testCreateActivityAssignedNoUser() {
278 $this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['NONE'];
279
280 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
281 $this->assertActivityAssignedToContactExists(NULL);
282 }
283
284 /**
285 * Tests the creation of activities when the default assignee is set to NONE.
286 */
287 public function testCreateActivityWithNoDefaultAssigneeOption() {
288 $this->process->createActivity($this->activityTypeXml, $this->activityParams);
289 $this->assertActivityAssignedToContactExists(NULL);
290 }
291
292 /**
293 * Asserts that an activity was created where the assignee was the one related
294 * to the target contact.
295 *
296 * @param int|null $assigneeContactId the ID of the expected assigned contact or NULL if expected to be empty.
297 */
298 protected function assertActivityAssignedToContactExists($assigneeContactId) {
299 $expectedContact = $assigneeContactId === NULL ? [] : [$assigneeContactId];
300 $result = $this->callAPISuccess('Activity', 'get', [
301 'target_contact_id' => $this->activityParams['clientID'],
302 'return' => ['assignee_contact_id'],
303 ]);
304 $activity = CRM_Utils_Array::first($result['values']);
305
306 $this->assertNotNull($activity, 'Target contact has no activities assigned to them');
307 $this->assertEquals($expectedContact, $activity['assignee_contact_id'], 'Activity is not assigned to expected contact');
308 }
309
310 /**
311 * Test that caseRoles() doesn't have name and label mixed up.
312 *
313 * @param $key string The array key in the moreRelationshipTypes array that
314 * is the relationship type we're currently testing. So not necessarily
315 * unique for each entry in the dataprovider since want to test a given
316 * relationship type against multiple xml strings. It's not a test
317 * identifier, it's an array key to use to look up something.
318 * @param $xmlString string
319 * @param $expected array
320 * @param $dontcare array We're re-using the data provider for two tests and
321 * we don't care about those expected values.
322 *
323 * @dataProvider xmlCaseRoleDataProvider
324 */
325 public function testCaseRoles($key, $xmlString, $expected, $dontcare) {
326 $xmlObj = new SimpleXMLElement($xmlString);
327
328 // element 0 is direction (a_b), 1 is the text we want
329 $expectedArray = empty($expected) ? [] : ["{$this->moreRelationshipTypes[$key]['type_id']}_{$expected[0]}" => $expected[1]];
330
331 $this->assertEquals($expectedArray, $this->process->caseRoles($xmlObj->CaseRoles, FALSE));
332 }
333
334 /**
335 * Test that locateNameOrLabel doesn't have name and label mixed up.
336 *
337 * @param $key string The array key in the moreRelationshipTypes array that
338 * is the relationship type we're currently testing. So not necessarily
339 * unique for each entry in the dataprovider since want to test a given
340 * relationship type against multiple xml strings. It's not a test
341 * identifier, it's an array key to use to look up something.
342 * @param $xmlString string
343 * @param $dontcare array We're re-using the data provider for two tests and
344 * we don't care about those expected values.
345 * @param $expected array
346 *
347 * @dataProvider xmlCaseRoleDataProvider
348 */
349 public function testLocateNameOrLabel($key, $xmlString, $dontcare, $expected) {
350 $xmlObj = new SimpleXMLElement($xmlString);
351
352 // element 0 is direction (a_b), 1 is the text we want.
353 // In case of failure, the function is expected to return FALSE for the
354 // direction and then for the text it just gives us back the string we
355 // gave it.
356 $expectedArray = empty($expected[0])
357 ? [FALSE, $expected[1]]
358 : ["{$this->moreRelationshipTypes[$key]['type_id']}_{$expected[0]}", $expected[1]];
359
360 $this->assertEquals($expectedArray, $this->process->locateNameOrLabel($xmlObj->CaseRoles->RelationshipType));
361 }
362
363 /**
364 * Data provider for testCaseRoles and testLocateNameOrLabel
365 * @return array
366 */
367 public function xmlCaseRoleDataProvider() {
368 return [
369 // Simulate one that has been converted to the format it should be going
370 // forward, where name is the actual name, i.e. same as machineName.
371 [
372 // this is the array key in the $this->moreRelationshipTypes array
373 'unidirectional_name_label_different',
374 // some xml
375 '<CaseType><CaseRoles><RelationshipType><name>jm7ba</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
376 // this is the expected for testCaseRoles
377 ['a_b', 'Jedi Master is'],
378 // this is the expected for testLocateNameOrLabel
379 ['a_b', 'jm7ba'],
380 ],
381 // Simulate one that is still in label format, i.e. one that is still in
382 // xml files that haven't been updated, or in the db but upgrade script
383 // not run yet.
384 [
385 'unidirectional_name_label_different',
386 '<CaseType><CaseRoles><RelationshipType><name>Jedi Master for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
387 ['a_b', 'Jedi Master is'],
388 ['a_b', 'jm7ba'],
389 ],
390 // Ditto but where we know name and label are the same in the db.
391 [
392 'unidirectional_name_label_same',
393 '<CaseType><CaseRoles><RelationshipType><name>Quilt Maker for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
394 ['a_b', 'Quilt Maker is'],
395 ['a_b', 'Quilt Maker for'],
396 ],
397 // Simulate one that is messed up and should fail, e.g. like a typo
398 // in an xml file. Here we've made a typo on purpose.
399 [
400 'unidirectional_name_label_different',
401 '<CaseType><CaseRoles><RelationshipType><name>Jedi Masterrrr for</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
402 NULL,
403 [FALSE, 'Jedi Masterrrr for'],
404 ],
405 // Now some similar tests to above but for bidirectional relationships.
406 // Bidirectional relationship, name and label different, using machine name.
407 [
408 'bidirectional_name_label_different',
409 '<CaseType><CaseRoles><RelationshipType><name>f12</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
410 ['b_a', 'Friend of'],
411 ['b_a', 'f12'],
412 ],
413 // Bidirectional relationship, name and label different, using display label.
414 [
415 'bidirectional_name_label_different',
416 '<CaseType><CaseRoles><RelationshipType><name>Friend of</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
417 ['b_a', 'Friend of'],
418 ['b_a', 'f12'],
419 ],
420 // Bidirectional relationship, name and label same.
421 [
422 'bidirectional_name_label_same',
423 '<CaseType><CaseRoles><RelationshipType><name>Enemy of</name><creator>1</creator><manager>1</manager></RelationshipType></CaseRoles></CaseType>',
424 ['b_a', 'Enemy of'],
425 ['b_a', 'Enemy of'],
426 ],
427 ];
428 }
429
430 }