f90a5da77d4a75039e3e77fd3a16cd4e4647533e
[civicrm-core.git] / tests / phpunit / CRM / Case / BAO / CaseTest.php
1 <?php
2
3 /**
4 * Class CRM_Case_BAO_CaseTest
5 * @group headless
6 */
7 class CRM_Case_BAO_CaseTest extends CiviUnitTestCase {
8
9 public function setUp() {
10 parent::setUp();
11
12 $this->tablesToTruncate = [
13 'civicrm_activity',
14 'civicrm_contact',
15 'civicrm_custom_group',
16 'civicrm_custom_field',
17 'civicrm_case',
18 'civicrm_case_contact',
19 'civicrm_case_activity',
20 'civicrm_case_type',
21 'civicrm_activity_contact',
22 'civicrm_managed',
23 'civicrm_relationship',
24 'civicrm_relationship_type',
25 ];
26
27 $this->quickCleanup($this->tablesToTruncate);
28
29 $this->loadAllFixtures();
30
31 CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase');
32 }
33
34 /**
35 * Make sure that the latest case activity works accurately.
36 */
37 public function testCaseActivity() {
38 $userID = $this->createLoggedInUser();
39
40 $addTimeline = civicrm_api3('Case', 'addtimeline', [
41 'case_id' => 1,
42 'timeline' => "standard_timeline",
43 ]);
44
45 $query = CRM_Case_BAO_Case::getCaseActivityQuery('recent', $userID, ' civicrm_case.id IN( 1 )');
46 $res = CRM_Core_DAO::executeQuery($query);
47 $openCaseType = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Open Case');
48 while ($res->fetch()) {
49 $message = 'Failed asserting that the case activity query has a activity_type_id property:';
50 $this->assertObjectHasAttribute('activity_type_id', $res, $message . PHP_EOL . print_r($res, TRUE));
51 $message = 'Failed asserting that the latest activity from Case ID 1 was "Open Case":';
52 $this->assertEquals($openCaseType, $res->activity_type_id, $message . PHP_EOL . print_r($res, TRUE));
53 }
54 }
55
56 protected function tearDown() {
57 parent::tearDown();
58 $this->quickCleanup($this->tablesToTruncate, TRUE);
59 }
60
61 public function testAddCaseToContact() {
62 $params = [
63 'case_id' => 1,
64 'contact_id' => 17,
65 ];
66 CRM_Case_BAO_CaseContact::create($params);
67
68 $recent = CRM_Utils_Recent::get();
69 $this->assertEquals('Test Contact - Housing Support', $recent[0]['title']);
70 }
71
72 /**
73 * Create and return case object of given Client ID.
74 * @param $clientId
75 * @param $loggedInUser
76 * @return CRM_Case_BAO_Case
77 */
78 private function createCase($clientId, $loggedInUser = NULL) {
79 if (empty($loggedInUser)) {
80 // backwards compatibility - but it's more typical that the creator is a different person than the client
81 $loggedInUser = $clientId;
82 }
83 $caseParams = [
84 'activity_subject' => 'Case Subject',
85 'client_id' => $clientId,
86 'case_type_id' => 1,
87 'status_id' => 1,
88 'case_type' => 'housing_support',
89 'subject' => 'Case Subject',
90 'start_date' => date("Y-m-d"),
91 'start_date_time' => date("YmdHis"),
92 'medium_id' => 2,
93 'activity_details' => '',
94 ];
95 $form = new CRM_Case_Form_Case();
96 $caseObj = $form->testSubmit($caseParams, "OpenCase", $loggedInUser, "standalone");
97 return $caseObj;
98 }
99
100 /**
101 * Create case role relationship between given contacts for provided case ID.
102 *
103 * @param $contactIdA
104 * @param $contactIdB
105 * @param $caseId
106 * @param bool $isActive
107 */
108 private function createCaseRoleRelationship($contactIdA, $contactIdB, $caseId, $isActive = TRUE) {
109 $relationshipType = $this->relationshipTypeCreate([
110 'contact_type_b' => 'Individual',
111 ]);
112
113 $this->callAPISuccess('Relationship', 'create', [
114 'contact_id_a' => $contactIdA,
115 'contact_id_b' => $contactIdB,
116 'relationship_type_id' => $relationshipType,
117 'case_id' => $caseId,
118 'is_active' => $isActive,
119 ]);
120 }
121
122 /**
123 * Asserts number of cases for given logged in user.
124 *
125 * @param $loggedInUser
126 * @param $caseId
127 * @param $caseCount
128 */
129 private function assertCasesOfUser($loggedInUser, $caseId, $caseCount) {
130 $summary = CRM_Case_BAO_Case::getCasesSummary(FALSE);
131 $upcomingCases = CRM_Case_BAO_Case::getCases(FALSE, [], 'dashboard', TRUE);
132 $caseRoles = CRM_Case_BAO_Case::getCaseRoles($loggedInUser, $caseId);
133
134 $this->assertEquals($caseCount, $upcomingCases, 'Upcoming case count must be ' . $caseCount);
135 $this->assertEquals($caseCount, $summary['rows']['Housing Support']['Ongoing']['count'], 'Housing Support Ongoing case summary must be ' . $caseCount);
136 $this->assertEquals($caseCount, count($caseRoles), 'Total case roles for logged in users must be ' . $caseCount);
137 }
138
139 /**
140 * Test that Case count is exactly one for logged in user for user's active role.
141 *
142 * @throws \CRM_Core_Exception
143 */
144 public function testActiveCaseRole() {
145 $individual = $this->individualCreate();
146 $caseObj = $this->createCase($individual);
147 $caseId = $caseObj->id;
148 $loggedInUser = $this->createLoggedInUser();
149 $this->createCaseRoleRelationship($individual, $loggedInUser, $caseId);
150 $this->assertCasesOfUser($loggedInUser, $caseId, 1);
151 }
152
153 /**
154 * Test that case count is zero for logged in user for user's inactive role.
155 */
156 public function testInactiveCaseRole() {
157 $individual = $this->individualCreate();
158 $caseObj = $this->createCase($individual);
159 $caseId = $caseObj->id;
160 $loggedInUser = $this->createLoggedInUser();
161 $this->createCaseRoleRelationship($individual, $loggedInUser, $caseId, FALSE);
162 $this->assertCasesOfUser($loggedInUser, $caseId, 0);
163 }
164
165 public function testGetCaseType() {
166 $caseTypeLabel = CRM_Case_BAO_Case::getCaseType(1);
167 $this->assertEquals('Housing Support', $caseTypeLabel);
168 }
169
170 public function testRetrieveCaseIdsByContactId() {
171 $caseIds = CRM_Case_BAO_Case::retrieveCaseIdsByContactId(3, FALSE, 'housing_support');
172 $this->assertEquals([1], $caseIds);
173 }
174
175 /**
176 * Test that all custom files are migrated to new case when case is assigned to new client.
177 */
178 public function testCaseReassignForCustomFiles() {
179 $individual = $this->individualCreate();
180 $customGroup = $this->customGroupCreate(array(
181 'extends' => 'Case',
182 ));
183 $customGroup = $customGroup['values'][$customGroup['id']];
184
185 $customFileFieldA = $this->customFieldCreate(array(
186 'custom_group_id' => $customGroup['id'],
187 'html_type' => 'File',
188 'is_active' => 1,
189 'default_value' => 'null',
190 'label' => 'Custom File A',
191 'data_type' => 'File',
192 ));
193
194 $customFileFieldB = $this->customFieldCreate(array(
195 'custom_group_id' => $customGroup['id'],
196 'html_type' => 'File',
197 'is_active' => 1,
198 'default_value' => 'null',
199 'label' => 'Custom File B',
200 'data_type' => 'File',
201 ));
202
203 // Create two files to attach to the new case
204 $filepath = Civi::paths()->getPath('[civicrm.files]/custom');
205
206 CRM_Utils_File::createFakeFile($filepath, 'Bananas do not bend themselves without a little help.', 'i_bend_bananas.txt');
207 $fileA = $this->callAPISuccess('File', 'create', ['uri' => "$filepath/i_bend_bananas.txt"]);
208
209 CRM_Utils_File::createFakeFile($filepath, 'Wombats will bite your ankles if you run from them.', 'wombats_bite_your_ankles.txt');
210 $fileB = $this->callAPISuccess('File', 'create', ['uri' => "$filepath/wombats_bite_your_ankles.txt"]);
211
212 $caseObj = $this->createCase($individual);
213
214 $this->callAPISuccess('Case', 'create', array(
215 'id' => $caseObj->id,
216 'custom_' . $customFileFieldA['id'] => $fileA['id'],
217 'custom_' . $customFileFieldB['id'] => $fileB['id'],
218 ));
219
220 $reassignIndividual = $this->individualCreate();
221 $this->createLoggedInUser();
222 $newCase = CRM_Case_BAO_Case::mergeCases($reassignIndividual, $caseObj->id, $individual, NULL, TRUE);
223
224 $entityFiles = new CRM_Core_DAO_EntityFile();
225 $entityFiles->entity_id = $newCase[0];
226 $entityFiles->entity_table = $customGroup['table_name'];
227 $entityFiles->find();
228
229 $totalEntityFiles = 0;
230 while ($entityFiles->fetch()) {
231 $totalEntityFiles++;
232 }
233
234 $this->assertEquals(2, $totalEntityFiles, 'Two files should be attached with new case.');
235 }
236
237 /**
238 * FIXME: need to create an activity to run this test
239 * function testGetCases() {
240 * $cases = CRM_Case_BAO_Case::getCases(TRUE, 3);
241 * $this->assertEquals('Housing Support', $cases[1]['case_type']);
242 * $this->assertEquals(1, $cases[1]['case_type_id']);
243 * }
244 */
245 public function testGetCasesSummary() {
246 $cases = CRM_Case_BAO_Case::getCasesSummary();
247 $this->assertEquals(1, $cases['rows']['Housing Support']['Ongoing']['count']);
248 }
249
250 /* FIXME: requires activities
251 * function testGetRelatedCases() {
252 * }
253 */
254
255 /**
256 * Test various things after a case is closed.
257 *
258 * This annotation is not ideal, but without it there is some kind of
259 * messup that happens to quickform that persists between tests, e.g.
260 * it can't add maxfilesize validation rules.
261 * @runInSeparateProcess
262 * @preserveGlobalState disabled
263 */
264 public function testCaseClosure() {
265 $loggedInUser = $this->createLoggedInUser();
266 $client_id = $this->individualCreate();
267 $caseObj = $this->createCase($client_id, $loggedInUser);
268 $case_id = $caseObj->id;
269
270 // Get the case status option value for "Resolved" (name="Closed").
271 $closed_status = $this->callAPISuccess('OptionValue', 'getValue', [
272 'return' => 'value',
273 'option_group_id' => 'case_status',
274 'name' => 'Closed',
275 ]);
276 $this->assertNotEmpty($closed_status);
277
278 // Get the activity status option value for "Completed"
279 $completed_status = $this->callAPISuccess('OptionValue', 'getValue', [
280 'return' => 'value',
281 'option_group_id' => 'activity_status',
282 'name' => 'Completed',
283 ]);
284 $this->assertNotEmpty($completed_status);
285
286 // Get the value for the activity type id we need to create
287 $atype = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Case Status');
288
289 // Now it gets weird. There doesn't seem to be a good way to test this, so we simulate a form and the various bits that go with it.
290
291 // HTTP vars needed because that's how the form determines stuff
292 $oldMETHOD = empty($_SERVER['REQUEST_METHOD']) ? NULL : $_SERVER['REQUEST_METHOD'];
293 $oldGET = empty($_GET) ? [] : $_GET;
294 $oldREQUEST = empty($_REQUEST) ? [] : $_REQUEST;
295 $_SERVER['REQUEST_METHOD'] = 'GET';
296 $_GET['caseid'] = $case_id;
297 $_REQUEST['caseid'] = $case_id;
298 $_GET['cid'] = $client_id;
299 $_REQUEST['cid'] = $client_id;
300 $_GET['action'] = 'add';
301 $_REQUEST['action'] = 'add';
302 $_GET['reset'] = 1;
303 $_REQUEST['reset'] = 1;
304 $_GET['atype'] = $atype;
305 $_REQUEST['atype'] = $atype;
306
307 $form = new CRM_Case_Form_Activity();
308 $form->controller = new CRM_Core_Controller_Simple('CRM_Case_Form_Activity', 'Case Activity');
309 $form->_activityTypeId = $atype;
310 $form->_activityTypeName = 'Change Case Status';
311 $form->_activityTypeFile = 'ChangeCaseStatus';
312
313 $form->preProcess();
314 $form->buildQuickForm();
315 $form->setDefaultValues();
316
317 // Now submit the form. Store the date used so we can check it later.
318
319 $t = time();
320 $now_date = date('Y-m-d H:i:s', $t);
321 $now_date_date_only = date('Y-m-d', $t);
322 $actParams = [
323 'is_unittest' => TRUE,
324 'case_status_id' => $closed_status,
325 'activity_date_time' => $now_date,
326 'target_contact_id' => $client_id,
327 'source_contact_id' => $loggedInUser,
328 // yeah this is extra weird, but without it you get the wrong subject
329 'subject' => 'null',
330 ];
331
332 $form->postProcess($actParams);
333
334 // Ok now let's check some things
335
336 $result = $this->callAPISuccess('Case', 'get', [
337 'sequential' => 1,
338 'id' => $case_id,
339 ]);
340 $caseData = array_shift($result['values']);
341
342 $this->assertEquals($caseData['end_date'], $now_date_date_only);
343 $this->assertEquals($caseData['status_id'], $closed_status);
344
345 // now get the latest activity and check some things for it
346
347 $actId = max($caseData['activities']);
348 $this->assertNotEmpty($actId);
349
350 $result = $this->callAPISuccess('Activity', 'get', [
351 'sequential' => 1,
352 'id' => $actId,
353 ]);
354 $activity = array_shift($result['values']);
355
356 $this->assertEquals($activity['subject'], 'Case status changed from Ongoing to Resolved');
357 $this->assertEquals($activity['activity_date_time'], $now_date);
358 $this->assertEquals($activity['status_id'], $completed_status);
359
360 // Now replace old globals
361 if (is_null($oldMETHOD)) {
362 unset($_SERVER['REQUEST_METHOD']);
363 }
364 else {
365 $_SERVER['REQUEST_METHOD'] = $oldMETHOD;
366 }
367 $_GET = $oldGET;
368 $_REQUEST = $oldREQUEST;
369 }
370
371 /**
372 * Test getGlobalContacts
373 */
374 public function testGetGlobalContacts() {
375 //Add contact to case resource.
376 $caseResourceContactID = $this->individualCreate();
377 $this->callAPISuccess('GroupContact', 'create', [
378 'group_id' => "Case_Resources",
379 'contact_id' => $caseResourceContactID,
380 ]);
381
382 //No contact should be returned.
383 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
384 $groupInfo = [];
385 $groupContacts = CRM_Case_BAO_Case::getGlobalContacts($groupInfo);
386 $this->assertEquals(0, count($groupContacts));
387
388 //Verify if contact is returned correctly.
389 CRM_Core_Config::singleton()->userPermissionClass->permissions = [
390 'access CiviCRM',
391 'view all contacts',
392 ];
393 $groupInfo = [];
394 $groupContacts = CRM_Case_BAO_Case::getGlobalContacts($groupInfo);
395 $this->assertEquals(1, count($groupContacts));
396 $this->assertEquals($caseResourceContactID, key($groupContacts));
397 }
398
399 /**
400 * Test max_instances
401 */
402 public function testMaxInstances() {
403 $loggedInUser = $this->createLoggedInUser();
404 $client_id = $this->individualCreate();
405 $caseObj = $this->createCase($client_id, $loggedInUser);
406 $case_id = $caseObj->id;
407
408 // Sanity check to make sure we'll be testing what we think we're testing.
409 $this->assertEquals($caseObj->case_type_id, 1);
410
411 // Get the case type
412 $result = $this->callAPISuccess('CaseType', 'get', [
413 'sequential' => 1,
414 'id' => 1,
415 ]);
416 $caseType = array_shift($result['values']);
417 $activityTypeName = $caseType['definition']['activityTypes'][1]['name'];
418 // Sanity check to make sure we'll be testing what we think we're testing.
419 $this->assertEquals($activityTypeName, "Medical evaluation");
420
421 // Look up the activity type label - we need it later
422 $result = $this->callAPISuccess('OptionValue', 'get', [
423 'sequential' => 1,
424 'option_group_id' => 'activity_type',
425 'name' => $activityTypeName,
426 ]);
427 $optionValue = array_shift($result['values']);
428 $activityTypeLabel = $optionValue['label'];
429 $this->assertNotEmpty($activityTypeLabel);
430
431 // Locate the existing activity independently so we can check it
432 $result = $this->callAPISuccess('Activity', 'get', [
433 'sequential' => 1,
434 // this sometimes confuses me - pass in the name for the id
435 'activity_type_id' => $activityTypeName,
436 ]);
437 // There should be only one in the database at this point so this should be the id.
438 $activity_id = $result['id'];
439 $this->assertNotEmpty($activity_id);
440 $this->assertGreaterThan(0, $activity_id);
441 $activityArr = array_shift($result['values']);
442
443 // At the moment everything should be happy, although there's nothing to test because if max_instances has no value then nothing gets called, which is correct since it means unlimited. But we don't have a way to test that right now. For fun we could test max_instances=0 but that isn't the same as "not set". 0 would actually mean 0 are allowed, which is pointless, since then why would you even add the activity type to the config.
444
445 // Update max instances for the activity type
446 // We're not really checking that the tested code has retrieved the new case type definition, just that given some numbers as input it returns the right thing as output, so these lines are mostly symbolic at the moment.
447 $caseType['definition']['activityTypes'][1]['max_instances'] = 1;
448 $this->callAPISuccess('CaseType', 'create', $caseType);
449
450 // Now we should get a link back
451 $editUrl = CRM_Case_Form_Activity::checkMaxInstances(
452 $case_id,
453 $activityArr['activity_type_id'],
454 // max instances
455 1,
456 $loggedInUser,
457 $client_id,
458 // existing activity count
459 1
460 );
461 $this->assertNotNull($editUrl);
462
463 $expectedUrl = CRM_Utils_System::url(
464 'civicrm/case/activity',
465 "reset=1&cid={$client_id}&caseid={$case_id}&action=update&id={$activity_id}"
466 );
467 $this->assertEquals($editUrl, $expectedUrl);
468
469 // And also a bounce message is expected
470 $bounceMessage = CRM_Case_Form_Activity::getMaxInstancesBounceMessage(
471 $editUrl,
472 $activityTypeLabel,
473 // max instances,
474 1,
475 // existing activity count
476 1
477 );
478 $this->assertNotEmpty($bounceMessage);
479
480 // Now check with max_instances = 2
481 $caseType['definition']['activityTypes'][1]['max_instances'] = 2;
482 $this->callAPISuccess('CaseType', 'create', $caseType);
483
484 // So it should now be back to being happy
485 $editUrl = CRM_Case_Form_Activity::checkMaxInstances(
486 $case_id,
487 $activityArr['activity_type_id'],
488 // max instances
489 2,
490 $loggedInUser,
491 $client_id,
492 // existing activity count
493 1
494 );
495 $this->assertNull($editUrl);
496 $bounceMessage = CRM_Case_Form_Activity::getMaxInstancesBounceMessage(
497 $editUrl,
498 $activityTypeLabel,
499 // max instances,
500 2,
501 // existing activity count
502 1
503 );
504 $this->assertEmpty($bounceMessage);
505
506 // Add new activity check again
507 $newActivity = [
508 'case_id' => $case_id,
509 'activity_type_id' => $activityArr['activity_type_id'],
510 'status_id' => $activityArr['status_id'],
511 'subject' => "A different subject",
512 'activity_date_time' => date('Y-m-d H:i:s'),
513 'source_contact_id' => $loggedInUser,
514 'target_id' => $client_id,
515 ];
516 $this->callAPISuccess('Activity', 'create', $newActivity);
517
518 $editUrl = CRM_Case_Form_Activity::checkMaxInstances(
519 $case_id,
520 $activityArr['activity_type_id'],
521 // max instances
522 2,
523 $loggedInUser,
524 $client_id,
525 // existing activity count
526 2
527 );
528 // There should be no url here.
529 $this->assertNull($editUrl);
530
531 // But there should be a warning message still.
532 $bounceMessage = CRM_Case_Form_Activity::getMaxInstancesBounceMessage(
533 $editUrl,
534 $activityTypeLabel,
535 // max instances,
536 2,
537 // existing activity count
538 2
539 );
540 $this->assertNotEmpty($bounceMessage);
541 }
542
543 /**
544 * Test changing the label for the case manager role and then creating
545 * a case.
546 * At the time this test was written this test would fail, demonstrating
547 * one problem with name vs label.
548 */
549 public function testCreateCaseWithChangedManagerLabel() {
550 // We could just assume the relationship that gets created has
551 // relationship_type_id = 1, but let's create a case, see what the
552 // id is, then do our actual test.
553 $loggedInUser = $this->createLoggedInUser();
554 $client_id = $this->individualCreate();
555 $caseObj = $this->createCase($client_id, $loggedInUser);
556 $case_id = $caseObj->id;
557
558 // Going to assume the stock case type has what it currently has at the
559 // time of writing, which is the autocreated case manager relationship for
560 // the logged in user.
561 $getParams = [
562 'contact_id_b' => $loggedInUser,
563 'case_id' => $case_id,
564 ];
565 $result = $this->callAPISuccess('Relationship', 'get', $getParams);
566 // as noted above assume this is the only one
567 $relationship_type_id = $result['values'][$result['id']]['relationship_type_id'];
568
569 // Save the old labels first so we can put back at end of test.
570 $oldParams = [
571 'id' => $relationship_type_id,
572 ];
573 $oldValues = $this->callAPISuccess('RelationshipType', 'get', $oldParams);
574 // Now change the label of the relationship type.
575 $changeParams = [
576 'id' => $relationship_type_id,
577 'label_a_b' => 'Best ' . $oldValues['values'][$relationship_type_id]['label_a_b'],
578 'label_b_a' => 'Best ' . $oldValues['values'][$relationship_type_id]['label_b_a'],
579 ];
580 $this->callAPISuccess('RelationshipType', 'create', $changeParams);
581
582 // Now try creating another case.
583 $caseObj2 = $this->createCase($client_id, $loggedInUser);
584 $case_id2 = $caseObj2->id;
585
586 $checkParams = [
587 'contact_id_b' => $loggedInUser,
588 'case_id' => $case_id2,
589 ];
590 $result = $this->callAPISuccess('Relationship', 'get', $checkParams);
591 // Main thing is the above createCase call doesn't fail, but let's check
592 // the relationship type id is what we expect too while we're here.
593 // See note above about assuming this is the only relationship autocreated.
594 $this->assertEquals($relationship_type_id, $result['values'][$result['id']]['relationship_type_id']);
595
596 // Now put relationship type back to the way it was.
597 $changeParams = [
598 'id' => $relationship_type_id,
599 'label_a_b' => $oldValues['values'][$relationship_type_id]['label_a_b'],
600 'label_b_a' => $oldValues['values'][$relationship_type_id]['label_b_a'],
601 ];
602 $this->callAPISuccess('RelationshipType', 'create', $changeParams);
603 }
604
605 }