defaultAssigneeOptionsValues = [];
$this->setupContacts();
$this->setupDefaultAssigneeOptions();
$this->setupRelationships();
$this->setupMoreRelationshipTypes();
$this->setupActivityDefinitions();
$this->process = new CRM_Case_XMLProcessor_Process();
}
public function tearDown() {
$this->deleteMoreRelationshipTypes();
parent::tearDown();
}
/**
* Creates sample contacts.
*/
protected function setUpContacts() {
$this->contacts = [
'ana' => $this->individualCreate(),
'beto' => $this->individualCreate(),
'carlos' => $this->individualCreate(),
];
}
/**
* Adds the default assignee group and options to the test database.
* It also stores the IDs of the options in an index.
*/
protected function setupDefaultAssigneeOptions() {
$options = [
'NONE', 'BY_RELATIONSHIP', 'SPECIFIC_CONTACT', 'USER_CREATING_THE_CASE',
];
CRM_Core_BAO_OptionGroup::ensureOptionGroupExists([
'name' => 'activity_default_assignee',
]);
foreach ($options as $option) {
$optionValue = CRM_Core_BAO_OptionValue::ensureOptionValueExists([
'option_group_id' => 'activity_default_assignee',
'name' => $option,
'label' => $option,
]);
$this->defaultAssigneeOptionsValues[$option] = $optionValue['value'];
}
}
/**
* Adds a relationship between the activity's target contact and default assignee.
*/
protected function setupRelationships() {
$this->relationships = [
'ana_is_pupil_of_beto' => [
'type_id' => NULL,
'name_a_b' => 'Pupil of',
'name_b_a' => 'Instructor',
'contact_id_a' => $this->contacts['ana'],
'contact_id_b' => $this->contacts['beto'],
],
'ana_is_spouse_of_carlos' => [
'type_id' => NULL,
'name_a_b' => 'Spouse of',
'name_b_a' => 'Spouse of',
'contact_id_a' => $this->contacts['ana'],
'contact_id_b' => $this->contacts['carlos'],
],
'unassigned_employee' => [
'type_id' => NULL,
'name_a_b' => 'Employee of',
'name_b_a' => 'Employer',
],
];
foreach ($this->relationships as $name => &$relationship) {
$relationship['type_id'] = $this->relationshipTypeCreate([
'contact_type_a' => 'Individual',
'contact_type_b' => 'Individual',
'name_a_b' => $relationship['name_a_b'],
'label_a_b' => $relationship['name_a_b'],
'name_b_a' => $relationship['name_b_a'],
'label_b_a' => $relationship['name_b_a'],
]);
if (isset($relationship['contact_id_a'])) {
$this->callAPISuccess('Relationship', 'create', [
'contact_id_a' => $relationship['contact_id_a'],
'contact_id_b' => $relationship['contact_id_b'],
'relationship_type_id' => $relationship['type_id'],
]);
}
}
}
/**
* Set up some additional relationship types for some specific tests.
*/
protected function setupMoreRelationshipTypes() {
$this->moreRelationshipTypes = [
'unidirectional_name_label_different' => [
'type_id' => NULL,
'name_a_b' => 'jm7ab',
'label_a_b' => 'Jedi Master is',
'name_b_a' => 'jm7ba',
'label_b_a' => 'Jedi Master for',
'description' => 'Jedi Master',
],
'unidirectional_name_label_same' => [
'type_id' => NULL,
'name_a_b' => 'Quilt Maker is',
'label_a_b' => 'Quilt Maker is',
'name_b_a' => 'Quilt Maker for',
'label_b_a' => 'Quilt Maker for',
'description' => 'Quilt Maker',
],
'bidirectional_name_label_different' => [
'type_id' => NULL,
'name_a_b' => 'f12',
'label_a_b' => 'Friend of',
'name_b_a' => 'f12',
'label_b_a' => 'Friend of',
'description' => 'Friend',
],
'bidirectional_name_label_same' => [
'type_id' => NULL,
'name_a_b' => 'Enemy of',
'label_a_b' => 'Enemy of',
'name_b_a' => 'Enemy of',
'label_b_a' => 'Enemy of',
'description' => 'Enemy',
],
];
foreach ($this->moreRelationshipTypes as &$relationship) {
$relationship['type_id'] = $this->relationshipTypeCreate([
'contact_type_a' => 'Individual',
'contact_type_b' => 'Individual',
'name_a_b' => $relationship['name_a_b'],
'label_a_b' => $relationship['label_a_b'],
'name_b_a' => $relationship['name_b_a'],
'label_b_a' => $relationship['label_b_a'],
'description' => $relationship['description'],
]);
}
}
/**
* Clean up additional relationship types (tearDown).
*/
protected function deleteMoreRelationshipTypes() {
foreach ($this->moreRelationshipTypes as $relationship) {
$this->callAPISuccess('relationship_type', 'delete', ['id' => $relationship['type_id']]);
}
}
/**
* Defines the the activity parameters and XML definitions. These can be used
* to create the activity.
*/
protected function setupActivityDefinitions() {
$activityTypeXml = 'Open Case';
$this->activityTypeXml = new SimpleXMLElement($activityTypeXml);
$this->activityParams = [
'activity_date_time' => date('Ymd'),
'caseID' => $this->caseTypeId,
'clientID' => $this->contacts['ana'],
'creatorID' => $this->_loggedInUser,
];
}
/**
* Tests the creation of activities where the default assignee should be the
* target contact's instructor. Beto is the instructor for Ana.
*/
public function testCreateActivityWithDefaultContactByRelationship() {
$relationship = $this->relationships['ana_is_pupil_of_beto'];
$this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['BY_RELATIONSHIP'];
$this->activityTypeXml->default_assignee_relationship = "{$relationship['type_id']}_b_a";
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists($this->contacts['beto']);
}
/**
* Tests when the default assignee relationship exists, but in the other direction only.
* Ana is a pupil, but has no pupils related to her.
*/
public function testCreateActivityWithDefaultContactByRelationshipMissing() {
$relationship = $this->relationships['ana_is_pupil_of_beto'];
$this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['BY_RELATIONSHIP'];
$this->activityTypeXml->default_assignee_relationship = "{$relationship['type_id']}_a_b";
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists(NULL);
}
/**
* Tests when the the default assignee relationship exists and is a bidirectional
* relationship. Ana and Carlos are spouses.
*/
public function testCreateActivityWithDefaultContactByRelationshipBidirectional() {
$relationship = $this->relationships['ana_is_spouse_of_carlos'];
$this->activityParams['clientID'] = $this->contacts['carlos'];
$this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['BY_RELATIONSHIP'];
$this->activityTypeXml->default_assignee_relationship = "{$relationship['type_id']}_a_b";
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists($this->contacts['ana']);
}
/**
* Tests when the default assignee relationship does not exist. Ana is not an
* employee for anyone.
*/
public function testCreateActivityWithDefaultContactByRelationButTheresNoRelationship() {
$relationship = $this->relationships['unassigned_employee'];
$this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['BY_RELATIONSHIP'];
$this->activityTypeXml->default_assignee_relationship = "{$relationship['type_id']}_b_a";
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists(NULL);
}
/**
* Tests the creation of activities with default assignee set to a specific contact.
*/
public function testCreateActivityAssignedToSpecificContact() {
$this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['SPECIFIC_CONTACT'];
$this->activityTypeXml->default_assignee_contact = $this->contacts['carlos'];
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists($this->contacts['carlos']);
}
/**
* Tests the creation of activities with default assignee set to a specific contact,
* but the contact does not exist.
*/
public function testCreateActivityAssignedToNonExistantSpecificContact() {
$this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['SPECIFIC_CONTACT'];
$this->activityTypeXml->default_assignee_contact = 987456321;
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists(NULL);
}
/**
* Tests the creation of activities with the default assignee being the one
* creating the case's activity.
*/
public function testCreateActivityAssignedToUserCreatingTheCase() {
$this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['USER_CREATING_THE_CASE'];
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists($this->_loggedInUser);
}
/**
* Tests the creation of activities when the default assignee is set to NONE.
*/
public function testCreateActivityAssignedNoUser() {
$this->activityTypeXml->default_assignee_type = $this->defaultAssigneeOptionsValues['NONE'];
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists(NULL);
}
/**
* Tests the creation of activities when the default assignee is set to NONE.
*/
public function testCreateActivityWithNoDefaultAssigneeOption() {
$this->process->createActivity($this->activityTypeXml, $this->activityParams);
$this->assertActivityAssignedToContactExists(NULL);
}
/**
* Asserts that an activity was created where the assignee was the one related
* to the target contact.
*
* @param int|null $assigneeContactId the ID of the expected assigned contact or NULL if expected to be empty.
*/
protected function assertActivityAssignedToContactExists($assigneeContactId) {
$expectedContact = $assigneeContactId === NULL ? [] : [$assigneeContactId];
$result = $this->callAPISuccess('Activity', 'get', [
'target_contact_id' => $this->activityParams['clientID'],
'return' => ['assignee_contact_id'],
]);
$activity = CRM_Utils_Array::first($result['values']);
$this->assertNotNull($activity, 'Target contact has no activities assigned to them');
$this->assertEquals($expectedContact, $activity['assignee_contact_id'], 'Activity is not assigned to expected contact');
}
/**
* Test that caseRoles() doesn't have name and label mixed up.
*
* @param $key string The array key in the moreRelationshipTypes array that
* is the relationship type we're currently testing. So not necessarily
* unique for each entry in the dataprovider since want to test a given
* relationship type against multiple xml strings. It's not a test
* identifier, it's an array key to use to look up something.
* @param $xmlString string
* @param $expected array
* @param $dontcare array We're re-using the data provider for two tests and
* we don't care about those expected values.
*
* @dataProvider xmlCaseRoleDataProvider
*/
public function testCaseRoles($key, $xmlString, $expected, $dontcare) {
$xmlObj = new SimpleXMLElement($xmlString);
// element 0 is direction (a_b), 1 is the text we want
$expectedArray = empty($expected) ? [] : ["{$this->moreRelationshipTypes[$key]['type_id']}_{$expected[0]}" => $expected[1]];
$this->assertEquals($expectedArray, $this->process->caseRoles($xmlObj->CaseRoles, FALSE));
}
/**
* Test that locateNameOrLabel doesn't have name and label mixed up.
*
* @param $key string The array key in the moreRelationshipTypes array that
* is the relationship type we're currently testing. So not necessarily
* unique for each entry in the dataprovider since want to test a given
* relationship type against multiple xml strings. It's not a test
* identifier, it's an array key to use to look up something.
* @param $xmlString string
* @param $dontcare array We're re-using the data provider for two tests and
* we don't care about those expected values.
* @param $expected array
*
* @dataProvider xmlCaseRoleDataProvider
*/
public function testLocateNameOrLabel($key, $xmlString, $dontcare, $expected) {
$xmlObj = new SimpleXMLElement($xmlString);
// element 0 is direction (a_b), 1 is the text we want.
// In case of failure, the function is expected to return FALSE for the
// direction and then for the text it just gives us back the string we
// gave it.
$expectedArray = empty($expected[0])
? [FALSE, $expected[1]]
: ["{$this->moreRelationshipTypes[$key]['type_id']}_{$expected[0]}", $expected[1]];
$this->assertEquals($expectedArray, $this->process->locateNameOrLabel($xmlObj->CaseRoles->RelationshipType));
}
/**
* Data provider for testCaseRoles and testLocateNameOrLabel
* @return array
*/
public function xmlCaseRoleDataProvider() {
return [
// Simulate one that has been converted to the format it should be going
// forward, where name is the actual name, i.e. same as machineName.
[
// this is the array key in the $this->moreRelationshipTypes array
'unidirectional_name_label_different',
// some xml
'jm7ba11',
// this is the expected for testCaseRoles
['a_b', 'Jedi Master is'],
// this is the expected for testLocateNameOrLabel
['a_b', 'jm7ba'],
],
// Simulate one that is still in label format, i.e. one that is still in
// xml files that haven't been updated, or in the db but upgrade script
// not run yet.
[
'unidirectional_name_label_different',
'Jedi Master for11',
['a_b', 'Jedi Master is'],
['a_b', 'jm7ba'],
],
// Ditto but where we know name and label are the same in the db.
[
'unidirectional_name_label_same',
'Quilt Maker for11',
['a_b', 'Quilt Maker is'],
['a_b', 'Quilt Maker for'],
],
// Simulate one that is messed up and should fail, e.g. like a typo
// in an xml file. Here we've made a typo on purpose.
[
'unidirectional_name_label_different',
'Jedi Masterrrr for11',
NULL,
[FALSE, 'Jedi Masterrrr for'],
],
// Now some similar tests to above but for bidirectional relationships.
// Bidirectional relationship, name and label different, using machine name.
[
'bidirectional_name_label_different',
'f1211',
['b_a', 'Friend of'],
['b_a', 'f12'],
],
// Bidirectional relationship, name and label different, using display label.
[
'bidirectional_name_label_different',
'Friend of11',
['b_a', 'Friend of'],
['b_a', 'f12'],
],
// Bidirectional relationship, name and label same.
[
'bidirectional_name_label_same',
'Enemy of11',
['b_a', 'Enemy of'],
['b_a', 'Enemy of'],
],
];
}
/**
* Test XMLProcessor activityTypes()
*/
public function testXmlProcessorActivityTypes() {
// First change an activity's label since we also test getting the labels.
// @todo Having a brain freeze or something - can't do this in one step?
$activity_type_id = $this->callApiSuccess('OptionValue', 'get', [
'option_group_id' => 'activity_type',
'name' => 'Medical evaluation',
])['id'];
$this->callApiSuccess('OptionValue', 'create', [
'id' => $activity_type_id,
'label' => 'Medical evaluation changed',
]);
$p = new CRM_Case_XMLProcessor_Process();
$xml = $p->retrieve('housing_support');
// Test getting the `name`s
$activityTypes = $p->activityTypes($xml->ActivityTypes, FALSE, FALSE, FALSE);
$this->assertEquals(
[
13 => 'Open Case',
55 => 'Medical evaluation',
56 => 'Mental health evaluation',
57 => 'Secure temporary housing',
60 => 'Income and benefits stabilization',
58 => 'Long-term housing plan',
14 => 'Follow up',
15 => 'Change Case Type',
16 => 'Change Case Status',
18 => 'Change Case Start Date',
25 => 'Link Cases',
],
$activityTypes
);
// While we're here and have the `name`s check the editable types in
// Settings.xml which is something that gets called reasonably often
// thru CRM_Case_XMLProcessor_Process::activityTypes().
$activityTypeValues = array_flip($activityTypes);
$xml = $p->retrieve('Settings');
$settings = $p->activityTypes($xml->ActivityTypes, FALSE, FALSE, 'edit');
$this->assertEquals(
[
'edit' => [
0 => $activityTypeValues['Change Case Status'],
1 => $activityTypeValues['Change Case Start Date'],
],
],
$settings
);
// Now get `label`s
$xml = $p->retrieve('housing_support');
$activityTypes = $p->activityTypes($xml->ActivityTypes, FALSE, TRUE, FALSE);
$this->assertEquals(
[
13 => 'Open Case',
55 => 'Medical evaluation changed',
56 => 'Mental health evaluation',
57 => 'Secure temporary housing',
60 => 'Income and benefits stabilization',
58 => 'Long-term housing plan',
14 => 'Follow up',
15 => 'Change Case Type',
16 => 'Change Case Status',
18 => 'Change Case Start Date',
25 => 'Link Cases',
],
$activityTypes
);
}
}