diff options
Diffstat (limited to 'www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit')
55 files changed, 4378 insertions, 0 deletions
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BaseCustomValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BaseCustomValueTest.php new file mode 100644 index 00000000..41fe7281 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BaseCustomValueTest.php @@ -0,0 +1,31 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Test\Api4\UnitTestCase; +use Civi\Test\Api4\Traits\TableDropperTrait; + +abstract class BaseCustomValueTest extends UnitTestCase { + + use \Civi\Test\Api4\Traits\OptionCleanupTrait { + setUp as setUpOptionCleanup; + } + use TableDropperTrait; + + /** + * Set up baseline for testing + */ + public function setUp() { + $this->setUpOptionCleanup(); + $cleanup_params = [ + 'tablesToTruncate' => [ + 'civicrm_custom_group', + 'civicrm_custom_field', + ], + ]; + + $this->dropByPrefix('civicrm_value_mycontact'); + $this->cleanup($cleanup_params); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicActionsTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicActionsTest.php new file mode 100644 index 00000000..29523389 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicActionsTest.php @@ -0,0 +1,154 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Test\Api4\UnitTestCase; +use Civi\Api4\MockBasicEntity; + +/** + * @group headless + */ +class BasicActionsTest extends UnitTestCase { + + public function testCrud() { + MockBasicEntity::delete()->addWhere('id', '>', 0)->execute(); + + $id1 = MockBasicEntity::create()->addValue('foo', 'one')->execute()->first()['id']; + + $result = MockBasicEntity::get()->execute(); + $this->assertCount(1, $result); + + $id2 = MockBasicEntity::create()->addValue('foo', 'two')->execute()->first()['id']; + + $result = MockBasicEntity::get()->execute(); + $this->assertCount(2, $result); + + MockBasicEntity::update()->addWhere('id', '=', $id2)->addValue('foo', 'new')->execute(); + + $result = MockBasicEntity::get()->addOrderBy('id', 'DESC')->setLimit(1)->execute(); + $this->assertCount(1, $result); + $this->assertEquals('new', $result->first()['foo']); + + MockBasicEntity::delete()->addWhere('id', '=', $id2); + $result = MockBasicEntity::get()->execute(); + $this->assertEquals('one', $result->first()['foo']); + } + + public function testReplace() { + MockBasicEntity::delete()->addWhere('id', '>', 0)->execute(); + + $objects = [ + ['group' => 'one', 'color' => 'red'], + ['group' => 'one', 'color' => 'blue'], + ['group' => 'one', 'color' => 'green'], + ['group' => 'two', 'color' => 'orange'], + ]; + + foreach ($objects as &$object) { + $object['id'] = MockBasicEntity::create()->setValues($object)->execute()->first()['id']; + } + + // Keep red, change blue, delete green, and add yellow + $replacements = [ + ['color' => 'red', 'id' => $objects[0]['id']], + ['color' => 'not blue', 'id' => $objects[1]['id']], + ['color' => 'yellow'] + ]; + + MockBasicEntity::replace()->addWhere('group', '=', 'one')->setRecords($replacements)->execute(); + + $newObjects = MockBasicEntity::get()->addOrderBy('id', 'DESC')->execute()->indexBy('id'); + + $this->assertCount(4, $newObjects); + + $this->assertEquals('yellow', $newObjects->first()['color']); + + $this->assertEquals('not blue', $newObjects[$objects[1]['id']]['color']); + + // Ensure group two hasn't been altered + $this->assertEquals('orange', $newObjects[$objects[3]['id']]['color']); + $this->assertEquals('two', $newObjects[$objects[3]['id']]['group']); + } + + public function testBatchFrobnicate() { + MockBasicEntity::delete()->addWhere('id', '>', 0)->execute(); + + $objects = [ + ['group' => 'one', 'color' => 'red', 'number' => 10], + ['group' => 'one', 'color' => 'blue', 'number' => 20], + ['group' => 'one', 'color' => 'green', 'number' => 30], + ['group' => 'two', 'color' => 'blue', 'number' => 40], + ]; + foreach ($objects as &$object) { + $object['id'] = MockBasicEntity::create()->setValues($object)->execute()->first()['id']; + } + + $result = MockBasicEntity::batchFrobnicate()->addWhere('color', '=', 'blue')->execute(); + $this->assertEquals(2, count($result)); + $this->assertEquals([400, 1600], \CRM_Utils_Array::collect('frobnication', (array) $result)); + } + + public function testGetFields() { + $getFields = MockBasicEntity::getFields()->execute()->indexBy('name'); + + $this->assertCount(6, $getFields); + $this->assertEquals('Id', $getFields['id']['title']); + // Ensure default data type is "String" when not specified + $this->assertEquals('String', $getFields['color']['data_type']); + + // Getfields should default to loadOptions = false and reduce them to bool + $this->assertTrue($getFields['group']['options']); + $this->assertFalse($getFields['id']['options']); + + // Now load options + $getFields = MockBasicEntity::getFields() + ->addWhere('name', '=', 'group') + ->setLoadOptions(TRUE) + ->execute()->indexBy('name'); + + $this->assertCount(1, $getFields); + $this->assertArrayHasKey('one', $getFields['group']['options']); + } + + public function testItemsToGet() { + $get = MockBasicEntity::get() + ->addWhere('color', 'NOT IN', ['yellow']) + ->addWhere('color', 'IN', ['red', 'blue']) + ->addWhere('color', '!=', 'green') + ->addWhere('group', '=', 'one'); + + $this->assertEquals(['red', 'blue'], $get->_itemsToGet('color')); + $this->assertEquals(['one'], $get->_itemsToGet('group')); + } + + public function testFieldsToGet() { + $get = MockBasicEntity::get() + ->addWhere('color', '!=', 'green'); + + // If no "select" is set, should always return true + $this->assertTrue($get->_isFieldSelected('color')); + $this->assertTrue($get->_isFieldSelected('shape')); + $this->assertTrue($get->_isFieldSelected('size')); + + // With a non-empty "select" fieldsToSelect() will return fields needed to evaluate each clause. + $get->addSelect('id'); + $this->assertTrue($get->_isFieldSelected('color')); + $this->assertTrue($get->_isFieldSelected('id')); + $this->assertFalse($get->_isFieldSelected('shape')); + $this->assertFalse($get->_isFieldSelected('size')); + $this->assertFalse($get->_isFieldSelected('weight')); + $this->assertFalse($get->_isFieldSelected('group')); + + $get->addClause('OR', ['shape', '=', 'round'], ['AND', [['size', '=', 'big'], ['weight', '!=', 'small']]]); + $this->assertTrue($get->_isFieldSelected('color')); + $this->assertTrue($get->_isFieldSelected('id')); + $this->assertTrue($get->_isFieldSelected('shape')); + $this->assertTrue($get->_isFieldSelected('size')); + $this->assertTrue($get->_isFieldSelected('weight')); + $this->assertFalse($get->_isFieldSelected('group')); + + $get->addOrderBy('group'); + $this->assertTrue($get->_isFieldSelected('group')); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicCustomFieldTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicCustomFieldTest.php new file mode 100644 index 00000000..85687a08 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicCustomFieldTest.php @@ -0,0 +1,187 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; + +/** + * @group headless + */ +class BasicCustomFieldTest extends BaseCustomValueTest { + + public function testWithSingleField() { + + $customGroup = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'MyContactFields') + ->addValue('extends', 'Contact') + ->execute() + ->first(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavColor') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + $contactId = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Johann') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->addValue('MyContactFields.FavColor', 'Red') + ->execute() + ->first()['id']; + + $contact = Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('first_name') + ->addSelect('MyContactFields.FavColor') + ->addWhere('id', '=', $contactId) + ->addWhere('MyContactFields.FavColor', '=', 'Red') + ->execute() + ->first(); + + $this->assertArrayHasKey('MyContactFields', $contact); + $contactFields = $contact['MyContactFields']; + $this->assertArrayHasKey('FavColor', $contactFields); + $this->assertEquals('Red', $contactFields['FavColor']); + + Contact::update() + ->addWhere('id', '=', $contactId) + ->addValue('MyContactFields.FavColor', 'Blue') + ->execute(); + + $contact = Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('MyContactFields.FavColor') + ->addWhere('id', '=', $contactId) + ->execute() + ->first(); + + $contactFields = $contact['MyContactFields']; + $this->assertEquals('Blue', $contactFields['FavColor']); + } + + public function testWithTwoFields() { + + $customGroup = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'MyContactFields') + ->addValue('extends', 'Contact') + ->execute() + ->first(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavColor') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavFood') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + $contactId1 = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Johann') + ->addValue('last_name', 'Tester') + ->addValue('MyContactFields.FavColor', 'Red') + ->addValue('MyContactFields.FavFood', 'Cherry') + ->execute() + ->first()['id']; + + $contactId2 = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'MaryLou') + ->addValue('last_name', 'Tester') + ->addValue('MyContactFields.FavColor', 'Purple') + ->addValue('MyContactFields.FavFood', 'Grapes') + ->execute() + ->first()['id']; + + $contact = Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('first_name') + ->addSelect('MyContactFields.FavColor') + ->addSelect('MyContactFields.FavFood') + ->addWhere('id', '=', $contactId1) + ->addWhere('MyContactFields.FavColor', '=', 'Red') + ->addWhere('MyContactFields.FavFood', '=', 'Cherry') + ->execute() + ->first(); + + $this->assertArrayHasKey('MyContactFields', $contact); + $contactFields = $contact['MyContactFields']; + $this->assertArrayHasKey('FavColor', $contactFields); + $this->assertEquals('Red', $contactFields['FavColor']); + + Contact::update() + ->addWhere('id', '=', $contactId1) + ->addValue('MyContactFields.FavColor', 'Blue') + ->execute(); + + $contact = Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('MyContactFields.FavColor') + ->addWhere('id', '=', $contactId1) + ->execute() + ->first(); + + $contactFields = $contact['MyContactFields']; + $this->assertEquals('Blue', $contactFields['FavColor']); + + $search = Contact::get() + ->setCheckPermissions(FALSE) + ->addClause('OR', ['MyContactFields.FavColor', '=', 'Blue'], ['MyContactFields.FavFood', '=', 'Grapes']) + ->addSelect('id') + ->addOrderBy('id') + ->execute() + ->indexBy('id'); + + $this->assertEquals([$contactId1, $contactId2], array_keys((array) $search)); + + $search = Contact::get() + ->setCheckPermissions(FALSE) + ->addClause('NOT', ['MyContactFields.FavColor', '=', 'Purple'], ['MyContactFields.FavFood', '=', 'Grapes']) + ->addSelect('id') + ->addOrderBy('id') + ->execute() + ->indexBy('id'); + + $this->assertNotContains($contactId2, array_keys((array) $search)); + + $search = Contact::get() + ->setCheckPermissions(FALSE) + ->addClause('NOT', ['MyContactFields.FavColor', '=', 'Purple'], ['MyContactFields.FavFood', '=', 'Grapes']) + ->addSelect('id') + ->addOrderBy('id') + ->execute() + ->indexBy('id'); + + $this->assertContains($contactId1, array_keys((array) $search)); + $this->assertNotContains($contactId2, array_keys((array) $search)); + + $search = Contact::get() + ->setCheckPermissions(FALSE) + ->setWhere([['NOT', ['OR', [['MyContactFields.FavColor', '=', 'Blue'], ['MyContactFields.FavFood', '=', 'Grapes']]]]]) + ->addSelect('id') + ->addOrderBy('id') + ->execute() + ->indexBy('id'); + + $this->assertNotContains($contactId1, array_keys((array) $search)); + $this->assertNotContains($contactId2, array_keys((array) $search)); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ChainTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ChainTest.php new file mode 100644 index 00000000..bbd6a092 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ChainTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class ChainTest extends UnitTestCase { + + public function testGetActionsWithFields() { + $actions = \Civi\Api4\Activity::getActions() + ->addChain('fields', \Civi\Api4\Activity::getFields()->setAction('$name'), 'name') + ->execute() + ->indexBy('name'); + + $this->assertEquals('Array', $actions['getActions']['fields']['params']['data_type']); + } + + public function testGetEntityWithActions() { + $entities = \Civi\Api4\Entity::get() + ->addSelect('name') + ->setChain([ + 'actions' => ['$name', 'getActions', ['select' => ['name']], 'name'] + ]) + ->execute() + ->indexBy('name'); + + $this->assertArrayHasKey('replace', $entities['Contact']['actions']); + $this->assertArrayHasKey('getLinks', $entities['Entity']['actions']); + $this->assertArrayNotHasKey('replace', $entities['Entity']['actions']); + } + + public function testContactCreateWithGroup() { + $firstName = uniqid('cwtf'); + $lastName = uniqid('cwtl'); + + $contact = \Civi\Api4\Contact::create() + ->addValue('first_name', $firstName) + ->addValue('last_name', $lastName) + ->addChain('group', \Civi\Api4\Group::create()->addValue('title', '$display_name'), 0) + ->addChain('add_to_group', \Civi\Api4\GroupContact::create()->addValue('contact_id', '$id')->addValue('group_id', '$group.id'), 0) + ->addChain('check_group', \Civi\Api4\GroupContact::get()->addWhere('group_id', '=', '$group.id')) + ->execute() + ->first(); + + $this->assertCount(1, $contact['check_group']); + $this->assertEquals($contact['id'], $contact['check_group'][0]['contact_id']); + $this->assertEquals($contact['group']['id'], $contact['check_group'][0]['group_id']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ComplexQueryTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ComplexQueryTest.php new file mode 100644 index 00000000..0a320100 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ComplexQueryTest.php @@ -0,0 +1,87 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Test\Api4\UnitTestCase; +use Civi\Api4\Activity; + +/** + * @group headless + * + * This class tests a series of complex query situations described in the + * initial APIv4 specification + */ +class ComplexQueryTest extends UnitTestCase { + + public function setUpHeadless() { + $relatedTables = [ + 'civicrm_activity', + 'civicrm_activity_contact', + ]; + $this->cleanup(['tablesToTruncate' => $relatedTables]); + $this->loadDataSet('DefaultDataSet'); + + return parent::setUpHeadless(); + } + + /** + * Fetch all phone call activities + * Expects at least one activity loaded from the data set. + */ + public function testGetAllHousingSupportActivities() { + $results = Activity::get() + ->setCheckPermissions(FALSE) + ->addWhere('activity_type.name', '=', 'Phone Call') + ->execute(); + + $this->assertGreaterThan(0, count($results)); + } + + /** + * Fetch all activities with a blue tag; and return all tags on the activities + */ + public function testGetAllTagsForBlueTaggedActivities() { + + } + + /** + * Fetch contacts named 'Bob' and all of their blue activities + */ + public function testGetAllBlueActivitiesForBobs() { + + } + + /** + * Get all contacts in a zipcode and return their Home or Work email addresses + */ + public function testGetHomeOrWorkEmailsForContactsWithZipcode() { + + } + + /** + * Fetch all activities where Bob is the assignee or source + */ + public function testGetActivitiesWithBobAsAssigneeOrSource() { + + } + + /** + * Get all contacts which + * (a) have address in zipcode 94117 or 94118 or in city "San Francisco","LA" + * and + * (b) are not deceased and + * (c) have a custom-field "most_important_issue=Environment". + */ + public function testAWholeLotOfConditions() { + + } + + /** + * Get participants who attended CiviCon 2012 but not CiviCon 2013. + * Return their name and email. + */ + public function testGettingNameAndEmailOfAttendeesOfCiviCon2012Only() { + + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php new file mode 100644 index 00000000..63d85d55 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php @@ -0,0 +1,170 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; + +/** + * @group headless + */ +class ContactApiKeyTest extends \Civi\Test\Api4\UnitTestCase { + + public function testGetApiKey() { + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM']; + $key = uniqid(); + + $contact = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Api') + ->addValue('last_name', 'Key0') + ->addValue('api_key', $key) + ->execute() + ->first(); + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + $this->assertEquals($result['api_key'], $key); + + $result = Contact::get() + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + $this->assertTrue(empty($result['api_key'])); + } + + public function testCreateWithApiKey() { + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'add contacts']; + $key = uniqid(); + + $error = ''; + try { + Contact::create() + ->addValue('first_name', 'Api') + ->addValue('last_name', 'Key1') + ->addValue('api_key', $key) + ->execute() + ->first(); + } + catch (\Exception $e) { + $error = $e->getMessage(); + } + $this->assertContains('key', $error); + } + + public function testUpdateApiKey() { + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM']; + $key = uniqid(); + + $contact = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Api') + ->addValue('last_name', 'Key2') + ->addValue('api_key', $key) + ->execute() + ->first(); + + $error = ''; + try { + // Try to update the key without permissions; nothing should happen + Contact::update() + ->addWhere('id', '=', $contact['id']) + ->addValue('api_key', "NotAllowed") + ->execute(); + } + catch (\Exception $e) { + $error = $e->getMessage(); + } + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + $this->assertContains('key', $error); + + // Assert key is still the same + $this->assertEquals($result['api_key'], $key); + + // Now we can update the key + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'administer CiviCRM', 'edit all contacts']; + + Contact::update() + ->addWhere('id', '=', $contact['id']) + ->addValue('api_key', "IGotThePower!") + ->execute(); + + $result = Contact::get() + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + // Assert key was updated + $this->assertEquals($result['api_key'], "IGotThePower!"); + } + + public function testUpdateOwnApiKey() { + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'edit own api keys', 'edit my contact']; + $key = uniqid(); + + $contact = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Api') + ->addValue('last_name', 'Key3') + ->addValue('api_key', $key) + ->execute() + ->first(); + + $error = ''; + try { + // Try to update the key without permissions; nothing should happen + Contact::update() + ->addWhere('id', '=', $contact['id']) + ->addValue('api_key', "NotAllowed") + ->execute(); + } + catch (\Exception $e) { + $error = $e->getMessage(); + } + + $this->assertContains('key', $error); + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + // Assert key is still the same + $this->assertEquals($result['api_key'], $key); + + // Now we can update the key + \CRM_Core_Session::singleton()->set('userID', $contact['id']); + + Contact::update() + ->addWhere('id', '=', $contact['id']) + ->addValue('api_key', "MyId!") + ->execute(); + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + // Assert key was updated + $this->assertEquals($result['api_key'], "MyId!"); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateCustomValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateCustomValueTest.php new file mode 100644 index 00000000..762b4617 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateCustomValueTest.php @@ -0,0 +1,64 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; +use Civi\Api4\OptionGroup; +use Civi\Api4\OptionValue; + +/** + * @group headless + */ +class CreateCustomValueTest extends BaseCustomValueTest { + + public function testGetWithCustomData() { + $optionValues = ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue']; + + $customGroup = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'MyContactFields') + ->addValue('extends', 'Contact') + ->execute() + ->first(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'Color') + ->addValue('options', $optionValues) + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Select') + ->addValue('data_type', 'String') + ->execute(); + + $customField = CustomField::get() + ->setCheckPermissions(FALSE) + ->addWhere('label', '=', 'Color') + ->execute() + ->first(); + + $this->assertNotNull($customField['option_group_id']); + $optionGroupId = $customField['option_group_id']; + + $optionGroup = OptionGroup::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $optionGroupId) + ->execute() + ->first(); + + $this->assertEquals('Color', $optionGroup['title']); + + $createdOptionValues = OptionValue::get() + ->setCheckPermissions(FALSE) + ->addWhere('option_group_id', '=', $optionGroupId) + ->execute() + ->getArrayCopy(); + + $values = array_column($createdOptionValues, 'value'); + $labels = array_column($createdOptionValues, 'label'); + $createdOptionValues = array_combine($values, $labels); + + $this->assertEquals($optionValues, $createdOptionValues); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateWithOptionGroupTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateWithOptionGroupTest.php new file mode 100644 index 00000000..b4d0af85 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateWithOptionGroupTest.php @@ -0,0 +1,190 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; +use Civi\Api4\Contact; + +/** + * @group headless + */ +class CreateWithOptionGroupTest extends BaseCustomValueTest { + + /** + * Remove the custom tables + */ + public function setUp() { + $this->dropByPrefix('civicrm_value_financial'); + $this->dropByPrefix('civicrm_value_favorite'); + parent::setUp(); + } + + public function testGetWithCustomData() { + $group = uniqid('fava'); + $colorField = uniqid('colora'); + $foodField = uniqid('fooda'); + + $customGroupId = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', $group) + ->addValue('extends', 'Contact') + ->execute() + ->first()['id']; + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', $colorField) + ->addValue('name', $colorField) + ->addValue('options', ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue']) + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Select') + ->addValue('data_type', 'String') + ->execute(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', $foodField) + ->addValue('name', $foodField) + ->addValue('options', ['1' => 'Corn', '2' => 'Potatoes', '3' => 'Cheese']) + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Select') + ->addValue('data_type', 'String') + ->execute(); + + $customGroupId = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'FinancialStuff') + ->addValue('extends', 'Contact') + ->execute() + ->first()['id']; + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'Salary') + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Number') + ->addValue('data_type', 'Money') + ->execute(); + + Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Jerome') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->addValue("$group.$colorField", 'r') + ->addValue("$group.$foodField", '1') + ->addValue('FinancialStuff.Salary', 50000) + ->execute(); + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('first_name') + ->addSelect("$group.$colorField.label") + ->addSelect("$group.$foodField.label") + ->addSelect('FinancialStuff.Salary') + ->addWhere("$group.$foodField.label", 'IN', ['Corn', 'Potatoes']) + ->addWhere('FinancialStuff.Salary', '>', '10000') + ->execute() + ->first(); + + $this->assertArrayHasKey($group, $result); + $favoriteThings = $result[$group]; + $favoriteFood = $favoriteThings[$foodField]; + $favoriteColor = $favoriteThings[$colorField]; + $financialStuff = $result['FinancialStuff']; + $this->assertEquals('Red', $favoriteColor['label']); + $this->assertEquals('Corn', $favoriteFood['label']); + $this->assertEquals(50000, $financialStuff['Salary']); + } + + public function testWithCustomDataForMultipleContacts() { + $group = uniqid('favb'); + $colorField = uniqid('colorb'); + $foodField = uniqid('foodb'); + + $customGroupId = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', $group) + ->addValue('extends', 'Contact') + ->execute() + ->first()['id']; + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', $colorField) + ->addValue('name', $colorField) + ->addValue('options', ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue']) + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Select') + ->addValue('data_type', 'String') + ->execute(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', $foodField) + ->addValue('name', $foodField) + ->addValue('options', ['1' => 'Corn', '2' => 'Potatoes', '3' => 'Cheese']) + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Select') + ->addValue('data_type', 'String') + ->execute(); + + $customGroupId = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'FinancialStuff') + ->addValue('extends', 'Contact') + ->execute() + ->first()['id']; + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'Salary') + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Number') + ->addValue('data_type', 'Money') + ->execute(); + + Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Red') + ->addValue('last_name', 'Corn') + ->addValue('contact_type', 'Individual') + ->addValue("$group.$colorField", 'r') + ->addValue("$group.$foodField", '1') + ->addValue('FinancialStuff.Salary', 10000) + ->execute(); + + Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Blue') + ->addValue('last_name', 'Cheese') + ->addValue('contact_type', 'Individual') + ->addValue("$group.$colorField", 'b') + ->addValue("$group.$foodField", '3') + ->addValue('FinancialStuff.Salary', 500000) + ->execute(); + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('first_name') + ->addSelect('last_name') + ->addSelect("$group.$colorField.label") + ->addSelect("$group.$foodField.label") + ->addSelect('FinancialStuff.Salary') + ->addWhere("$group.$foodField.label", 'IN', ['Corn', 'Cheese']) + ->execute(); + + $blueCheese = NULL; + foreach ($result as $contact) { + if ($contact['first_name'] === 'Blue') { + $blueCheese = $contact; + } + } + + $this->assertEquals('Blue', $blueCheese[$group][$colorField]['label']); + $this->assertEquals('Cheese', $blueCheese[$group][$foodField]['label']); + $this->assertEquals(500000, $blueCheese['FinancialStuff']['Salary']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValuePerformanceTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValuePerformanceTest.php new file mode 100644 index 00000000..3fa59ef4 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValuePerformanceTest.php @@ -0,0 +1,95 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; +use Civi\Test\Api4\Traits\QueryCounterTrait; + +/** + * @group headless + */ +class CustomValuePerformanceTest extends BaseCustomValueTest { + + use QueryCounterTrait; + + public function testQueryCount() { + + $this->markTestIncomplete(); + + $customGroupId = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'MyContactFields') + ->addValue('title', 'MyContactFields') + ->addValue('extends', 'Contact') + ->execute() + ->first()['id']; + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavColor') + ->addValue('custom_group_id', $customGroupId) + ->addValue('options', ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue']) + ->addValue('html_type', 'Select') + ->addValue('data_type', 'String') + ->execute(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavAnimal') + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavLetter') + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavFood') + ->addValue('custom_group_id', $customGroupId) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + $this->beginQueryCount(); + + Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Red') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->addValue('MyContactFields.FavColor', 'r') + ->addValue('MyContactFields.FavAnimal', 'Sheep') + ->addValue('MyContactFields.FavLetter', 'z') + ->addValue('MyContactFields.FavFood', 'Coconuts') + ->execute(); + + Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('display_name') + ->addSelect('MyContactFields.FavColor.label') + ->addSelect('MyContactFields.FavColor.weight') + ->addSelect('MyContactFields.FavColor.is_default') + ->addSelect('MyContactFields.FavAnimal') + ->addSelect('MyContactFields.FavLetter') + ->addWhere('MyContactFields.FavColor', '=', 'r') + ->addWhere('MyContactFields.FavFood', '=', 'Coconuts') + ->addWhere('MyContactFields.FavAnimal', '=', 'Sheep') + ->addWhere('MyContactFields.FavLetter', '=', 'z') + ->execute() + ->first(); + + // FIXME: This count is artificially high due to the line + // $this->entity = Tables::getBriefName(Tables::getClassForTable($targetTable)); + // In class Joinable. TODO: Investigate why. + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValueTest.php new file mode 100644 index 00000000..da954bc7 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValueTest.php @@ -0,0 +1,174 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; +use Civi\Api4\CustomValue; +use Civi\Api4\Contact; + +/** + * @group headless + */ +class CustomValueTest extends BaseCustomValueTest { + + protected $contactID; + + /** + * Test CustomValue::GetFields/Get/Create/Update/Replace/Delete + */ + public function testCRUD() { + $optionValues = ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue']; + + $group = uniqid('groupc'); + $colorField = uniqid('colorc'); + + $customGroup = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', $group) + ->addValue('extends', 'Contact') + ->addValue('is_multiple', TRUE) + ->execute() + ->first(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', $colorField) + ->addValue('options', $optionValues) + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Select') + ->addValue('data_type', 'String') + ->execute(); + + $this->contactID = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Johann') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->execute() + ->first()['id']; + + // Retrieve and check the fields of CustomValue = Custom_$group + $fields = CustomValue::getFields($group)->execute(); + $expectedResult = [ + [ + 'custom_field_id' => 1, + 'custom_group' => $group, + 'name' => $colorField, + 'title' => ts($colorField), + 'entity' => "Custom_$group", + 'data_type' => 'String', + 'fk_entity' => NULL, + ], + [ + 'name' => 'id', + 'title' => ts('Custom Value ID'), + 'entity' => "Custom_$group", + 'data_type' => 'Integer', + 'fk_entity' => NULL, + ], + [ + 'name' => 'entity_id', + 'title' => ts('Entity ID'), + 'entity' => "Custom_$group", + 'data_type' => 'Integer', + 'fk_entity' => 'Contact', + ], + ]; + + foreach ($expectedResult as $key => $field) { + foreach ($field as $attr => $value) { + $this->assertEquals($expectedResult[$key][$attr], $fields[$key][$attr]); + } + } + + // CASE 1: Test CustomValue::create + // Create two records for a single contact and using CustomValue::get ensure that two records are created + CustomValue::create($group) + ->addValue($colorField, 'Green') + ->addValue("entity_id", $this->contactID) + ->execute(); + CustomValue::create($group) + ->addValue($colorField, 'Red') + ->addValue("entity_id", $this->contactID) + ->execute(); + // fetch custom values using API4 CustomValue::get + $result = CustomValue::get($group)->execute(); + + // check if two custom values are created + $this->assertEquals(2, count($result)); + $expectedResult = [ + [ + 'id' => 1, + $colorField => 'Green', + 'entity_id' => $this->contactID, + ], + [ + 'id' => 2, + $colorField => 'Red', + 'entity_id' => $this->contactID, + ], + ]; + // match the data + foreach ($expectedResult as $key => $field) { + foreach ($field as $attr => $value) { + $this->assertEquals($expectedResult[$key][$attr], $result[$key][$attr]); + } + } + + // CASE 2: Test CustomValue::update + // Update a records whose id is 1 and change the custom field (name = Color) value to 'White' from 'Green' + CustomValue::update($group) + ->addWhere("id", "=", 1) + ->addValue($colorField, 'White') + ->execute(); + + // ensure that the value is changed for id = 1 + $color = CustomValue::get($group) + ->addWhere("id", "=", 1) + ->execute() + ->first()[$colorField]; + $this->assertEquals('White', $color); + + // CASE 3: Test CustomValue::replace + // create a second contact which will be used to replace the custom values, created earlier + $secondContactID = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Adam') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->execute() + ->first()['id']; + // Replace all the records which was created earlier with entity_id = first contact + // with custom record [$colorField => 'Rainbow', 'entity_id' => $secondContactID] + CustomValue::replace($group) + ->setRecords([[$colorField => 'Rainbow', 'entity_id' => $secondContactID]]) + ->addWhere('entity_id', '=', $this->contactID) + ->execute(); + + // Check the two records created earlier is replaced by new contact + $result = CustomValue::get($group)->execute(); + $this->assertEquals(1, count($result)); + + $expectedResult = [ + [ + 'id' => 3, + $colorField => 'Rainbow', + 'entity_id' => $secondContactID, + ], + ]; + foreach ($expectedResult as $key => $field) { + foreach ($field as $attr => $value) { + $this->assertEquals($expectedResult[$key][$attr], $result[$key][$attr]); + } + } + + // CASE 4: Test CustomValue::delete + // There is only record left whose id = 3, delete that record on basis of criteria id = 3 + CustomValue::delete($group)->addWhere("id", "=", 3)->execute(); + $result = CustomValue::get($group)->execute(); + // check that there are no custom values present + $this->assertEquals(0, count($result)); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/DateTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/DateTest.php new file mode 100644 index 00000000..8cdbfc7c --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/DateTest.php @@ -0,0 +1,46 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; +use Civi\Api4\Relationship; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class DateTest extends UnitTestCase { + + public function testRelationshipDate() { + $c1 = Contact::create() + ->addValue('first_name', 'c') + ->addValue('last_name', 'one') + ->execute() + ->first()['id']; + $c2 = Contact::create() + ->addValue('first_name', 'c') + ->addValue('last_name', 'two') + ->execute() + ->first()['id']; + $r = Relationship::create() + ->addValue('contact_id_a', $c1) + ->addValue('contact_id_b', $c2) + ->addValue('relationship_type_id', 1) + ->addValue('start_date', 'now') + ->addValue('end_date', 'now + 1 week') + ->execute() + ->first()['id']; + $result = Relationship::get() + ->addWhere('start_date', '=', 'now') + ->addWhere('end_date', '>', 'now + 1 day') + ->execute() + ->indexBy('id'); + $this->assertArrayHasKey($r, $result); + $result = Relationship::get() + ->addWhere('start_date', '<', 'now') + ->execute() + ->indexBy('id'); + $this->assertArrayNotHasKey($r, $result); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/EvaluateConditionTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/EvaluateConditionTest.php new file mode 100644 index 00000000..3726042f --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/EvaluateConditionTest.php @@ -0,0 +1,38 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\MockBasicEntity; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class EvaluateConditionTest extends UnitTestCase { + + public function testEvaluateCondition() { + $action = MockBasicEntity::get(); + $reflection = new \ReflectionClass($action); + $method = $reflection->getMethod('evaluateCondition'); + $method->setAccessible(TRUE); + + $data = [ + 'nada' => 0, + 'uno' => 1, + 'dos' => 2, + 'apple' => 'red', + 'banana' => 'yellow', + 'values' => ['one' => 1, 'two' => 2, 'three' => 3], + ]; + + $this->assertFalse($method->invoke($action, '$uno > $dos', $data)); + $this->assertTrue($method->invoke($action, '$uno < $dos', $data)); + $this->assertTrue($method->invoke($action, '$apple == "red" && $banana != "red"', $data)); + $this->assertFalse($method->invoke($action, '$apple == "red" && $banana != "yellow"', $data)); + $this->assertTrue($method->invoke($action, '$values.one == $uno', $data)); + $this->assertTrue($method->invoke($action, '$values.one + $dos == $values.three', $data)); + $this->assertTrue($method->invoke($action, 'empty($nada)', $data)); + $this->assertFalse($method->invoke($action, 'empty($values)', $data)); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ExtendFromIndividualTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ExtendFromIndividualTest.php new file mode 100644 index 00000000..2d3be50c --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ExtendFromIndividualTest.php @@ -0,0 +1,54 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; + +/** + * @group headless + */ +class ExtendFromIndividualTest extends BaseCustomValueTest { + + public function testGetWithNonStandardExtends() { + + $customGroup = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'MyContactFields') + ->addValue('extends', 'Individual') // not Contact + ->execute() + ->first(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavColor') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + $contactId = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Johann') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->addValue('MyContactFields.FavColor', 'Red') + ->execute() + ->first()['id']; + + $contact = Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('display_name') + ->addSelect('MyContactFields.FavColor') + ->addWhere('id', '=', $contactId) + ->execute() + ->first(); + + $this->assertArrayHasKey('MyContactFields', $contact); + $contactFields = $contact['MyContactFields']; + $favColor = $contactFields['FavColor']; + $this->assertEquals('Red', $favColor); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/FkJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/FkJoinTest.php new file mode 100644 index 00000000..c2b044e5 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/FkJoinTest.php @@ -0,0 +1,75 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Test\Api4\UnitTestCase; +use Civi\Api4\Activity; +use Civi\Api4\Contact; + +/** + * @group headless + */ +class FkJoinTest extends UnitTestCase { + + public function setUpHeadless() { + $relatedTables = [ + 'civicrm_activity', + 'civicrm_phone', + 'civicrm_activity_contact', + ]; + $this->cleanup(['tablesToTruncate' => $relatedTables]); + $this->loadDataSet('DefaultDataSet'); + + return parent::setUpHeadless(); + } + + /** + * Fetch all phone call activities. Expects a single activity + * loaded from the data set. + */ + public function testThreeLevelJoin() { + $results = Activity::get() + ->setCheckPermissions(FALSE) + ->addWhere('activity_type.name', '=', 'Phone Call') + ->execute(); + + $this->assertCount(1, $results); + } + + public function testActivityContactJoin() { + $results = Activity::get() + ->setCheckPermissions(FALSE) + ->addSelect('assignees.id') + ->addSelect('assignees.first_name') + ->addSelect('assignees.display_name') + ->addWhere('assignees.first_name', '=', 'Phoney') + ->execute(); + + $firstResult = $results->first(); + + $this->assertCount(1, $results); + $this->assertTrue(is_array($firstResult['assignees'])); + + $firstAssignee = array_shift($firstResult['assignees']); + $this->assertEquals($firstAssignee['first_name'], 'Phoney'); + } + + public function testContactPhonesJoin() { + $testContact = $this->getReference('test_contact_1'); + $testPhone = $this->getReference('test_phone_1'); + + $results = Contact::get() + ->setCheckPermissions(FALSE) + ->addSelect('phones.phone') + ->addWhere('id', '=', $testContact['id']) + ->addWhere('phones.location_type.name', '=', 'Home') + ->execute() + ->first(); + + $this->assertArrayHasKey('phones', $results); + $this->assertCount(1, $results['phones']); + $firstPhone = array_shift($results['phones']); + $this->assertEquals($testPhone['phone'], $firstPhone['phone']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetExtraFieldsTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetExtraFieldsTest.php new file mode 100644 index 00000000..bc9b10e7 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetExtraFieldsTest.php @@ -0,0 +1,26 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Test\Api4\UnitTestCase; +use Civi\Api4\Contact; + +/** + * @group headless + */ +class GetExtraFieldsTest extends UnitTestCase { + + public function testBAOFieldsWillBeReturned() { + $returnedFields = Contact::getFields() + ->execute() + ->getArrayCopy(); + + $baseFields = \CRM_Contact_BAO_Contact::fields(); + $baseFieldNames = array_column($baseFields, 'name'); + $returnedFieldNames = array_column($returnedFields, 'name'); + $notReturned = array_diff($baseFieldNames, $returnedFieldNames); + + $this->assertEmpty($notReturned); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetFromArrayTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetFromArrayTest.php new file mode 100644 index 00000000..bee6fbf3 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetFromArrayTest.php @@ -0,0 +1,163 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Test\Api4\UnitTestCase; +use Civi\Api4\MockArrayEntity; + +/** + * @group headless + */ +class GetFromArrayTest extends UnitTestCase { + + public function testArrayGetWithLimit() { + $result = MockArrayEntity::get() + ->setOffset(2) + ->setLimit(2) + ->execute(); + $this->assertEquals(3, $result[0]['field1']); + $this->assertEquals(4, $result[1]['field1']); + $this->assertEquals(2, count($result)); + } + + public function testArrayGetWithSort() { + $result = MockArrayEntity::get() + ->addOrderBy('field1', 'DESC') + ->execute(); + $this->assertEquals([5, 4, 3, 2, 1], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addOrderBy('field5', 'DESC') + ->addOrderBy('field2', 'ASC') + ->execute(); + $this->assertEquals([3, 2, 5, 4, 1], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addOrderBy('field3', 'ASC') + ->addOrderBy('field2', 'ASC') + ->execute(); + $this->assertEquals([3, 1, 2, 5, 4], array_column((array) $result, 'field1')); + } + + public function testArrayGetWithSelect() { + $result = MockArrayEntity::get() + ->addSelect('field1') + ->addSelect('field3') + ->setLimit(4) + ->execute(); + $this->assertEquals([ + [ + 'field1' => 1, + 'field3' => NULL, + ], + [ + 'field1' => 2, + 'field3' => 0, + ], + [ + 'field1' => 3, + ], + [ + 'field1' => 4, + 'field3' => 1, + ], + ], (array) $result); + } + + public function testArrayGetWithWhere() { + $result = MockArrayEntity::get() + ->addWhere('field2', '=', 'yack') + ->execute(); + $this->assertEquals([2], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field5', '!=', 'banana') + ->addWhere('field3', 'IS NOT NULL') + ->execute(); + $this->assertEquals([4, 5], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field1', '>=', '4') + ->execute(); + $this->assertEquals([4, 5], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field1', '<', '2') + ->execute(); + $this->assertEquals([1], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field2', 'LIKE', '%ra%') + ->execute(); + $this->assertEquals([1, 3], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field3', 'IS NULL') + ->execute(); + $this->assertEquals([1, 3], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field3', '=', '0') + ->execute(); + $this->assertEquals([2], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field2', 'LIKE', '%ra') + ->execute(); + $this->assertEquals([1], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field2', 'LIKE', 'ra') + ->execute(); + $this->assertEquals(0, count($result)); + + $result = MockArrayEntity::get() + ->addWhere('field2', 'NOT LIKE', '%ra%') + ->execute(); + $this->assertEquals([2, 4, 5], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field6', '=', '0') + ->execute(); + $this->assertEquals([3, 4, 5], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field6', '=', 0) + ->execute(); + $this->assertEquals([3, 4, 5], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field1', 'BETWEEN', [3, 5]) + ->execute(); + $this->assertEquals([3, 4, 5], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addWhere('field1', 'NOT BETWEEN', [3, 4]) + ->execute(); + $this->assertEquals([1, 2, 5], array_column((array) $result, 'field1')); + } + + public function testArrayGetWithNestedWhereClauses() { + $result = MockArrayEntity::get() + ->addClause('OR', ['field2', 'LIKE', '%ra'], ['field2', 'LIKE', 'x ray']) + ->execute(); + $this->assertEquals([1, 3], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addClause('OR', ['field2', '=', 'zebra'], ['field2', '=', 'yack']) + ->addClause('OR', ['field5', '!=', 'apple'], ['field3', 'IS NULL']) + ->execute(); + $this->assertEquals([1, 2], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addClause('NOT', ['field2', '!=', 'yack']) + ->execute(); + $this->assertEquals([2], array_column((array) $result, 'field1')); + + $result = MockArrayEntity::get() + ->addClause('OR', ['field1', '=', 2], ['AND', [['field5', '=', 'apple'], ['field3', '=', 1]]]) + ->execute(); + $this->assertEquals([2, 4, 5], array_column((array) $result, 'field1')); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/IndexTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/IndexTest.php new file mode 100644 index 00000000..17356c92 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/IndexTest.php @@ -0,0 +1,48 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class IndexTest extends UnitTestCase { + + public function testIndex() { + // Results indexed by name + $resultByName = civicrm_api4('Activity', 'getActions', [], 'name'); + $this->assertInstanceOf('Civi\Api4\Generic\Result', $resultByName); + $this->assertEquals('get', $resultByName['get']['name']); + + // Get result at index 0 + $firstResult = civicrm_api4('Activity', 'getActions', [], 0); + $this->assertInstanceOf('Civi\Api4\Generic\Result', $firstResult); + $this->assertArrayHasKey('name', $firstResult); + + $this->assertEquals($resultByName->first(), (array) $firstResult); + } + + public function testBadIndexInt() { + $error = ''; + try { + civicrm_api4('Activity', 'getActions', [], 99); + } + catch (\API_Exception $e) { + $error = $e->getMessage(); + } + $this->assertContains('not found', $error); + } + + public function testBadIndexString() { + $error = ''; + try { + civicrm_api4('Activity', 'getActions', [], 'xyz'); + } + catch (\API_Exception $e) { + $error = $e->getMessage(); + } + $this->assertContains('not found', $error); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/NullValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/NullValueTest.php new file mode 100644 index 00000000..dc4f656a --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/NullValueTest.php @@ -0,0 +1,55 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class NullValueTest extends UnitTestCase { + + public function setUpHeadless() { + $format = '{contact.first_name}{ }{contact.last_name}'; + \Civi::settings()->set('display_name_format', $format); + return parent::setUpHeadless(); + } + + public function testStringNull() { + $contact = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Joseph') + ->addValue('last_name', 'null') + ->addValue('contact_type', 'Individual') + ->execute() + ->first(); + + $this->assertSame('Null', $contact['last_name']); + $this->assertSame('Joseph Null', $contact['display_name']); + } + + public function testSettingToNull() { + $contact = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'ILoveMy') + ->addValue('last_name', 'LastName') + ->addValue('contact_type', 'Individual') + ->execute() + ->first(); + + $this->assertSame('ILoveMy LastName', $contact['display_name']); + $contactId = $contact['id']; + + $contact = Contact::update() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contactId) + ->addValue('last_name', NULL) + ->execute() + ->first(); + + $this->assertSame(NULL, $contact['last_name']); + $this->assertSame('ILoveMy', $contact['display_name']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ReplaceTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ReplaceTest.php new file mode 100644 index 00000000..097e12b0 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ReplaceTest.php @@ -0,0 +1,171 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; +use Civi\Api4\CustomValue; +use Civi\Api4\Email; +use Civi\Test\Api4\Traits\TableDropperTrait; +use Civi\Test\Api4\UnitTestCase; +use Civi\Api4\Contact; + +/** + * @group headless + */ +class ReplaceTest extends UnitTestCase { + use TableDropperTrait; + + /** + * Set up baseline for testing + */ + public function setUp() { + $tablesToTruncate = [ + 'civicrm_custom_group', + 'civicrm_custom_field', + 'civicrm_email', + ]; + $this->dropByPrefix('civicrm_value_replacetest'); + $this->cleanup(['tablesToTruncate' => $tablesToTruncate]); + parent::setUp(); + } + + public function testEmailReplace() { + $cid1 = Contact::create() + ->addValue('first_name', 'Lotsa') + ->addValue('last_name', 'Emails') + ->execute() + ->first()['id']; + $cid2 = Contact::create() + ->addValue('first_name', 'Notso') + ->addValue('last_name', 'Many') + ->execute() + ->first()['id']; + $e0 = Email::create() + ->setValues(['contact_id' => $cid2, 'email' => 'nosomany@example.com', 'location_type_id' => 1]) + ->execute() + ->first()['id']; + $e1 = Email::create() + ->setValues(['contact_id' => $cid1, 'email' => 'first@example.com', 'location_type_id' => 1]) + ->execute() + ->first()['id']; + $e2 = Email::create() + ->setValues(['contact_id' => $cid1, 'email' => 'second@example.com', 'location_type_id' => 1]) + ->execute() + ->first()['id']; + $replacement = [ + ['email' => 'firstedited@example.com', 'id' => $e1], + ['contact_id' => $cid1, 'email' => 'third@example.com', 'location_type_id' => 1] + ]; + $replaced = Email::replace() + ->setRecords($replacement) + ->addWhere('contact_id', '=', $cid1) + ->execute(); + // Should have saved 2 records + $this->assertEquals(2, $replaced->count()); + // Should have deleted email2 + $this->assertEquals([$e2], $replaced->deleted); + // Verify contact now has the new email records + $results = Email::get() + ->addWhere('contact_id', '=', $cid1) + ->execute() + ->indexBy('id'); + $this->assertEquals('firstedited@example.com', $results[$e1]['email']); + $this->assertEquals(2, $results->count()); + $this->assertArrayNotHasKey($e2, (array) $results); + $this->assertArrayNotHasKey($e0, (array) $results); + unset($results[$e1]); + foreach ($results as $result) { + $this->assertEquals('third@example.com', $result['email']); + } + // Validate our other contact's email did not get deleted + $c2email = Email::get() + ->addWhere('contact_id', '=', $cid2) + ->execute() + ->first(); + $this->assertEquals('nosomany@example.com', $c2email['email']); + } + + public function testCustomValueReplace() { + $customGroup = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'replaceTest') + ->addValue('extends', 'Contact') + ->addValue('is_multiple', TRUE) + ->execute() + ->first(); + + CustomField::create() + ->addValue('label', 'Custom1') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'String') + ->addValue('data_type', 'String') + ->execute(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'Custom2') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'String') + ->addValue('data_type', 'String') + ->execute(); + + $cid1 = Contact::create() + ->addValue('first_name', 'Lotsa') + ->addValue('last_name', 'Data') + ->execute() + ->first()['id']; + $cid2 = Contact::create() + ->addValue('first_name', 'Notso') + ->addValue('last_name', 'Much') + ->execute() + ->first()['id']; + + // Contact 2 gets one row + CustomValue::create('replaceTest') + ->setCheckPermissions(FALSE) + ->addValue('Custom1', "2 1") + ->addValue('Custom2', "2 1") + ->addValue('entity_id', $cid2) + ->execute(); + + // Create 3 rows for contact 1 + foreach ([1, 2, 3] as $i) { + CustomValue::create('replaceTest') + ->setCheckPermissions(FALSE) + ->addValue('Custom1', "1 $i") + ->addValue('Custom2', "1 $i") + ->addValue('entity_id', $cid1) + ->execute(); + } + + $cid1Records = CustomValue::get('replaceTest') + ->setCheckPermissions(FALSE) + ->addWhere('entity_id', '=', $cid1) + ->execute(); + + $this->assertCount(3, $cid1Records); + $this->assertCount(1, CustomValue::get('replaceTest')->setCheckPermissions(FALSE)->addWhere('entity_id', '=', $cid2)->execute()); + + $result = CustomValue::replace('replaceTest') + ->addWhere('entity_id', '=', $cid1) + ->addRecord(['Custom1' => 'new one', 'Custom2' => 'new two']) + ->addRecord(['id' => $cid1Records[0]['id'], 'Custom1' => 'changed one', 'Custom2' => 'changed two']) + ->execute(); + + $this->assertCount(2, $result); + $this->assertCount(2, $result->deleted); + + $newRecords = CustomValue::get('replaceTest') + ->setCheckPermissions(FALSE) + ->addWhere('entity_id', '=', $cid1) + ->execute() + ->indexBy('id'); + + $this->assertEquals('new one', $newRecords->last()['Custom1']); + $this->assertEquals('new two', $newRecords->last()['Custom2']); + $this->assertEquals('changed one', $newRecords[$cid1Records[0]['id']]['Custom1']); + $this->assertEquals('changed two', $newRecords[$cid1Records[0]['id']]['Custom2']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateContactTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateContactTest.php new file mode 100644 index 00000000..71265598 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateContactTest.php @@ -0,0 +1,34 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; +use Civi\Test\Api4\UnitTestCase; + +/** + * Class UpdateContactTest + * @package Civi\Test\Api4\Action + * @group headless + */ +class UpdateContactTest extends UnitTestCase { + + public function testUpdateWillWork() { + $contactId = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Johann') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->execute() + ->first()['id']; + + $contact = Contact::update() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contactId) + ->addValue('first_name', 'Testy') + ->execute() + ->first(); + $this->assertEquals('Testy', $contact['first_name']); + $this->assertEquals('Tester', $contact['last_name']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateCustomValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateCustomValueTest.php new file mode 100644 index 00000000..99a9f011 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateCustomValueTest.php @@ -0,0 +1,56 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; +use \CRM_Core_BAO_CustomValueTable as CustomValueTable; + +/** + * @group headless + */ +class UpdateCustomValueTest extends BaseCustomValueTest { + + public function testGetWithCustomData() { + + $customGroup = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'MyContactFields') + ->addValue('extends', 'Contact') + ->execute() + ->first(); + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavColor') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + $contactId = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Red') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->addValue('MyContactFields.FavColor', 'Red') + ->execute() + ->first()['id']; + + Contact::update() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contactId) + ->addValue('first_name', 'Red') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->addValue('MyContactFields.FavColor', 'Blue') + ->execute(); + + $result = CustomValueTable::getEntityValues($contactId, 'Contact'); + + $this->assertEquals(1, count($result)); + $this->assertContains('Blue', $result); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/ConformanceTest.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/ConformanceTest.json new file mode 100644 index 00000000..fcaf8966 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/ConformanceTest.json @@ -0,0 +1,28 @@ +{ + "Contact": [ + { + "first_name": "Janice", + "last_name": "Voss", + "contact_type": "Individual" + } + ], + "CustomGroup": [ + { + "name": "MyFavoriteThings", + "extends": "Contact" + } + ], + "Event": [ + { + "start_date": "20401010000000", + "title": "The Singularity", + "event_type_id": "major_historical_event" + } + ], + "Group": [ + { + "name": "the_group", + "title": "The Group" + } + ] +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/DefaultDataSet.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/DefaultDataSet.json new file mode 100644 index 00000000..7d4a91bc --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/DefaultDataSet.json @@ -0,0 +1,45 @@ +{ + "Contact": [ + { + "first_name": "Phoney", + "last_name": "Contact", + "contact_type": "Individual", + "@ref": "test_contact_1" + }, + { + "first_name": "Second", + "last_name": "Test", + "contact_type": "Individual", + "@ref": "test_contact_2" + } + ], + "Activity": [ + { + "subject": "Test Phone Activity", + "activity_type": "Phone Call", + "source_contact_id": "@ref test_contact_1.id" + }, + { + "subject": "Another Activity", + "activity_type": "Meeting", + "source_contact_id": "@ref test_contact_1.id", + "assignee_contact_id": [ + "@ref test_contact_1.id", + "@ref test_contact_2.id" + ] + } + ], + "Phone": [ + { + "contact_id": "@ref test_contact_1.id", + "phone": "+35355439483", + "location_type_id": "1", + "@ref": "test_phone_1" + }, + { + "contact_id": "@ref test_contact_1.id", + "phone": "+3538733439483", + "location_type_id": "2" + } + ] +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/MultiContactMultiEmail.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/MultiContactMultiEmail.json new file mode 100644 index 00000000..ce3fbcaf --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/MultiContactMultiEmail.json @@ -0,0 +1,42 @@ +{ + "Contact": [ + { + "first_name": "First", + "last_name": "Contact", + "contact_type": "Individual", + "@ref": "test_contact_1" + }, + { + "first_name": "Second", + "last_name": "Contact", + "contact_type": "Individual", + "@ref": "test_contact_2" + } + ], + "Email": [ + { + "contact_id": "@ref test_contact_1.id", + "email": "test_contact_one_home@fakedomain.com", + "location_type_id": 1, + "@ref": "test_email_1" + }, + { + "contact_id": "@ref test_contact_1.id", + "email": "test_contact_one_work@fakedomain.com", + "location_type_id": 2, + "@ref": "test_email_2" + }, + { + "contact_id": "@ref test_contact_2.id", + "email": "test_contact_two_home@fakedomain.com", + "location_type_id": 1, + "@ref": "test_email_3" + }, + { + "contact_id": "@ref test_contact_2.id", + "email": "test_contact_two_work@fakedomain.com", + "location_type_id": 2, + "@ref": "test_email_4" + } + ] +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/SingleContact.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/SingleContact.json new file mode 100644 index 00000000..73e7369e --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/SingleContact.json @@ -0,0 +1,81 @@ +{ + "Contact": [ + { + "first_name": "Single", + "last_name": "Contact", + "contact_type": "Individual", + "preferred_communication_method": "1", + "@ref": "test_contact_1" + } + ], + "Activity": [ + { + "subject": "Won A Nobel Prize", + "activity_type": "Meeting", + "source_contact_id": "@ref test_contact_1.id", + "@ref": "test_activity_1" + }, + { + "subject": "Cleaned The House", + "activity_type": "Meeting", + "source_contact_id": "@ref test_contact_1.id", + "assignee_contact_id": [ + "@ref test_contact_1.id" + ], + "@ref": "test_activity_2" + } + ], + "Phone": [ + { + "contact_id": "@ref test_contact_1.id", + "phone": "+1111111111111", + "location_type_id": 1 + }, + { + "contact_id": "@ref test_contact_1.id", + "phone": "+2222222222222", + "location_type_id": 2 + } + ], + "Email": [ + { + "contact_id": "@ref test_contact_1.id", + "email": "test_contact_home@fakedomain.com", + "location_type_id": 1 + }, + { + "contact_id": "@ref test_contact_1.id", + "email": "test_contact_work@fakedomain.com", + "location_type_id": 2 + } + ], + "Address": [ + { + "contact_id": "@ref test_contact_1.id", + "street_address": "123 Sesame St.", + "location_type_id": 1 + } + ], + "Website": [ + { + "contact_id": "@ref test_contact_1.id", + "url": "http://test.com", + "website_id": 1 + } + ], + "OpenID": [ + { + "contact_id": "@ref test_contact_1.id", + "openid": "123", + "allowed_to_login": 1, + "location_type_id": 1 + } + ], + "IM": [ + { + "contact_id": "@ref test_contact_1.id", + "name": "123", + "location_type_id": 1 + } + ] +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ConformanceTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ConformanceTest.php new file mode 100644 index 00000000..79fe5b30 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ConformanceTest.php @@ -0,0 +1,226 @@ +<?php + +namespace Civi\Test\Api4\Entity; + +use Civi\Api4\Generic\AbstractEntity; +use Civi\Api4\Entity; +use Civi\Test\Api4\Service\TestCreationParameterProvider; +use Civi\Test\Api4\Traits\TableDropperTrait; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class ConformanceTest extends UnitTestCase { + + use TableDropperTrait; + use \Civi\Test\Api4\Traits\OptionCleanupTrait { + setUp as setUpOptionCleanup; + } + + /** + * @var TestCreationParameterProvider + */ + protected $creationParamProvider; + + /** + * Set up baseline for testing + */ + public function setUp() { + $tablesToTruncate = [ + 'civicrm_custom_group', + 'civicrm_custom_field', + 'civicrm_group', + 'civicrm_event', + 'civicrm_participant', + ]; + $this->dropByPrefix('civicrm_value_myfavorite'); + $this->cleanup(['tablesToTruncate' => $tablesToTruncate]); + $this->setUpOptionCleanup(); + $this->loadDataSet('ConformanceTest'); + $this->creationParamProvider = \Civi::container()->get('test.param_provider'); + parent::setUp(); + // calculateTaxAmount() for contribution triggers a deprecation notice + \PHPUnit_Framework_Error_Deprecated::$enabled = FALSE; + } + + public function getEntities() { + $result = []; + $entities = Entity::get()->setCheckPermissions(FALSE)->execute(); + foreach ($entities as $entity) { + if ($entity['name'] != 'Entity') { + $result[] = [$entity['name']]; + } + } + return $result; + } + + /** + * Fixme: This should use getEntities as a dataProvider but that fails for some reason + */ + public function testConformance() { + $entities = $this->getEntities(); + $this->assertNotEmpty($entities); + + foreach ($entities as $data) { + $entity = $data[0]; + $entityClass = 'Civi\Api4\\' . $entity; + + $this->checkActions($entityClass); + $this->checkFields($entityClass, $entity); + $id = $this->checkCreation($entity, $entityClass); + $this->checkGet($entityClass, $id, $entity); + $this->checkUpdateFailsFromCreate($entityClass, $id); + $this->checkWrongParamType($entityClass); + $this->checkDeleteWithNoId($entityClass); + $this->checkDeletion($entityClass, $id); + $this->checkPostDelete($entityClass, $id, $entity); + } + } + + /** + * @param string $entityClass + * @param $entity + */ + protected function checkFields($entityClass, $entity) { + $fields = $entityClass::getFields() + ->setCheckPermissions(FALSE) + ->setIncludeCustom(FALSE) + ->execute() + ->indexBy('name'); + + $errMsg = sprintf('%s is missing required ID field', $entity); + $subset = ['data_type' => 'Integer']; + + $this->assertArraySubset($subset, $fields['id'], $errMsg); + } + + /** + * @param string $entityClass + */ + protected function checkActions($entityClass) { + $actions = $entityClass::getActions() + ->setCheckPermissions(FALSE) + ->execute() + ->indexBy('name'); + + $this->assertNotEmpty($actions->getArrayCopy()); + } + + /** + * @param string $entity + * @param AbstractEntity|string $entityClass + * + * @return mixed + */ + protected function checkCreation($entity, $entityClass) { + $requiredParams = $this->creationParamProvider->getRequired($entity); + $createResult = $entityClass::create() + ->setValues($requiredParams) + ->setCheckPermissions(FALSE) + ->execute() + ->first(); + + $this->assertArrayHasKey('id', $createResult, "create missing ID"); + $id = $createResult['id']; + + $this->assertGreaterThanOrEqual(1, $id, "$entity ID not positive"); + + return $id; + } + + /** + * @param AbstractEntity|string $entityClass + * @param int $id + */ + protected function checkUpdateFailsFromCreate($entityClass, $id) { + $exceptionThrown = ''; + try { + $entityClass::create() + ->setCheckPermissions(FALSE) + ->addValue('id', $id) + ->execute(); + } + catch (\API_Exception $e) { + $exceptionThrown = $e->getMessage(); + } + $this->assertContains('id', $exceptionThrown); + } + + /** + * @param AbstractEntity|string $entityClass + * @param int $id + * @param string $entity + */ + protected function checkGet($entityClass, $id, $entity) { + $getResult = $entityClass::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $id) + ->execute(); + + $errMsg = sprintf('Failed to fetch a %s after creation', $entity); + $this->assertEquals(1, count($getResult), $errMsg); + } + + /** + * @param AbstractEntity|string $entityClass + */ + protected function checkDeleteWithNoId($entityClass) { + $exceptionThrown = ''; + try { + $entityClass::delete() + ->execute(); + } + catch (\API_Exception $e) { + $exceptionThrown = $e->getMessage(); + } + $this->assertContains('required', $exceptionThrown); + } + + /** + * @param AbstractEntity|string $entityClass + */ + protected function checkWrongParamType($entityClass) { + $exceptionThrown = ''; + try { + $entityClass::get() + ->setCheckPermissions('nada') + ->execute(); + } + catch (\API_Exception $e) { + $exceptionThrown = $e->getMessage(); + } + $this->assertContains('checkPermissions', $exceptionThrown); + $this->assertContains('type', $exceptionThrown); + } + + /** + * @param AbstractEntity|string $entityClass + * @param int $id + */ + protected function checkDeletion($entityClass, $id) { + $deleteResult = $entityClass::delete() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $id) + ->execute(); + + // should get back an array of deleted id + $this->assertEquals([$id], (array) $deleteResult); + } + + /** + * @param AbstractEntity|string $entityClass + * @param int $id + * @param string $entity + */ + protected function checkPostDelete($entityClass, $id, $entity) { + $getDeletedResult = $entityClass::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $id) + ->execute(); + + $errMsg = sprintf('Entity "%s" was not deleted', $entity); + $this->assertEquals(0, count($getDeletedResult), $errMsg); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ContactJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ContactJoinTest.php new file mode 100644 index 00000000..392e0466 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ContactJoinTest.php @@ -0,0 +1,103 @@ +<?php + +namespace Civi\Test\Api4\Entity; + +use Civi\Api4\Contact; +use Civi\Api4\OptionValue; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class ContactJoinTest extends UnitTestCase { + + public function setUpHeadless() { + $relatedTables = [ + 'civicrm_address', + 'civicrm_email', + 'civicrm_phone', + 'civicrm_openid', + 'civicrm_im', + 'civicrm_website', + 'civicrm_activity', + 'civicrm_activity_contact', + ]; + + $this->cleanup(['tablesToTruncate' => $relatedTables]); + $this->loadDataSet('SingleContact'); + + return parent::setUpHeadless(); + } + + public function testContactJoin() { + + $contact = $this->getReference('test_contact_1'); + $entitiesToTest = ['Address', 'OpenID', 'IM', 'Website', 'Email', 'Phone']; + + foreach ($entitiesToTest as $entity) { + $results = civicrm_api4($entity, 'get', [ + 'where' => [['contact_id', '=', $contact['id']]], + 'select' => ['contact.display_name', 'contact.id'], + ]); + foreach ($results as $result) { + $this->assertEquals($contact['id'], $result['contact']['id']); + $this->assertEquals($contact['display_name'], $result['contact']['display_name']); + } + } + } + + public function testJoinToPCMWillReturnArray() { + $contact = Contact::create()->setValues([ + 'preferred_communication_method' => [1, 2, 3], + 'contact_type' => 'Individual', + 'first_name' => 'Test', + 'last_name' => 'PCM', + ])->execute()->first(); + + $fetchedContact = Contact::get() + ->addWhere('id', '=', $contact['id']) + ->addSelect('preferred_communication_method') + ->execute() + ->first(); + + $this->assertCount(3, $fetchedContact["preferred_communication_method"]); + } + + public function testJoinToPCMOptionValueWillShowLabel() { + $options = OptionValue::get() + ->addWhere('option_group.name', '=', 'preferred_communication_method') + ->execute() + ->getArrayCopy(); + + $optionValues = array_column($options, 'value'); + $labels = array_column($options, 'label'); + + $contact = Contact::create()->setValues([ + 'preferred_communication_method' => $optionValues, + 'contact_type' => 'Individual', + 'first_name' => 'Test', + 'last_name' => 'PCM', + ])->execute()->first(); + + $contact2 = Contact::create()->setValues([ + 'preferred_communication_method' => $optionValues, + 'contact_type' => 'Individual', + 'first_name' => 'Test', + 'last_name' => 'PCM2', + ])->execute()->first(); + + $contactIds = array_column([$contact, $contact2], 'id'); + + $fetchedContact = Contact::get() + ->addWhere('id', 'IN', $contactIds) + ->addSelect('preferred_communication_method.label') + ->execute() + ->first(); + + $preferredMethod = $fetchedContact['preferred_communication_method']; + $returnedLabels = array_column($preferredMethod, 'label'); + + $this->assertEquals($labels, $returnedLabels); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/EntityTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/EntityTest.php new file mode 100644 index 00000000..8f7ecd00 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/EntityTest.php @@ -0,0 +1,35 @@ +<?php + +namespace Civi\Test\Api4\Entity; + +use Civi\Api4\Entity; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class EntityTest extends UnitTestCase { + + public function testEntityGet() { + $result = Entity::get() + ->setCheckPermissions(FALSE) + ->execute() + ->indexBy('name'); + $this->assertArrayHasKey('Entity', $result, + "Entity::get missing itself"); + $this->assertArrayHasKey('Participant', $result, + "Entity::get missing Participant"); + } + + public function testEntity() { + $result = Entity::getActions() + ->setCheckPermissions(FALSE) + ->execute() + ->indexBy('name'); + $this->assertNotContains( + 'create', + array_keys((array) $result), + "Entity entity has more than basic actions"); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ParticipantTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ParticipantTest.php new file mode 100644 index 00000000..8236efb0 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ParticipantTest.php @@ -0,0 +1,200 @@ +<?php + +namespace Civi\Test\Api4\Entity; + +use Civi\Api4\Participant; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class ParticipantTest extends UnitTestCase { + + public function setUp() { + parent::setUp(); + $cleanup_params = [ + 'tablesToTruncate' => [ + 'civicrm_event', + 'civicrm_participant', + ], + ]; + $this->cleanup($cleanup_params); + } + + public function testGetActions() { + $result = Participant::getActions() + ->setCheckPermissions(FALSE) + ->execute() + ->indexBy('name'); + + $getParams = $result['get']['params']; + $whereDescription = 'Criteria for selecting items.'; + + $this->assertEquals(TRUE, $getParams['checkPermissions']['default']); + $this->assertEquals($whereDescription, $getParams['where']['description']); + } + + public function testGet() { + $rows = $this->getRowCount('civicrm_participant'); + if ($rows > 0) { + $this->markTestSkipped('Participant table must be empty'); + } + + // With no records: + $result = Participant::get()->setCheckPermissions(FALSE)->execute(); + $this->assertEquals(0, $result->count(), "count of empty get is not 0"); + + // Check that the $result knows what the inputs were + $this->assertEquals('Participant', $result->entity); + $this->assertEquals('get', $result->action); + $this->assertEquals(4, $result->version); + + // Create some test related records before proceeding + $participantCount = 20; + $contactCount = 7; + $eventCount = 5; + + // All events will either have this number or one less because of the + // rotating participation creation method. + $expectedFirstEventCount = ceil($participantCount / $eventCount); + + $dummy = [ + 'contacts' => $this->createEntity([ + 'type' => 'Individual', + 'count' => $contactCount, + 'seq' => 1]), + 'events' => $this->createEntity([ + 'type' => 'Event', + 'count' => $eventCount, + 'seq' => 1]), + 'sources' => ['Paddington', 'Springfield', 'Central'], + ]; + + // - create dummy participants record + for ($i = 0; $i < $participantCount; $i++) { + $dummy['participants'][$i] = $this->sample([ + 'type' => 'Participant', + 'overrides' => [ + 'event_id' => $dummy['events'][$i % $eventCount]['id'], + 'contact_id' => $dummy['contacts'][$i % $contactCount]['id'], + 'source' => $dummy['sources'][$i % 3], // 3 = number of sources + ]])['sample_params']; + + Participant::create() + ->setValues($dummy['participants'][$i]) + ->setCheckPermissions(FALSE) + ->execute(); + } + $sqlCount = $this->getRowCount('civicrm_participant'); + $this->assertEquals($participantCount, $sqlCount, "Unexpected count"); + + $firstEventId = $dummy['events'][0]['id']; + $secondEventId = $dummy['events'][1]['id']; + $firstContactId = $dummy['contacts'][0]['id']; + + $firstOnlyResult = Participant::get() + ->setCheckPermissions(FALSE) + ->addClause('AND', ['event_id', '=', $firstEventId]) + ->execute(); + + $this->assertEquals($expectedFirstEventCount, count($firstOnlyResult), + "count of first event is not $expectedFirstEventCount"); + + // get first two events using different methods + $firstTwo = Participant::get() + ->setCheckPermissions(FALSE) + ->addWhere('event_id', 'IN', [$firstEventId, $secondEventId]) + ->execute(); + + $firstResult = $result->first(); + + // verify counts + // count should either twice the first event count or one less + $this->assertLessThanOrEqual( + $expectedFirstEventCount * 2, + count($firstTwo), + "count is too high" + ); + + $this->assertGreaterThanOrEqual( + $expectedFirstEventCount * 2 - 1, + count($firstTwo), + "count is too low" + ); + + $firstParticipantResult = Participant::get() + ->setCheckPermissions(FALSE) + ->addWhere('event_id', '=', $firstEventId) + ->addWhere('contact_id', '=', $firstContactId) + ->execute(); + + $this->assertEquals(1, count($firstParticipantResult), "more than one registration"); + + $firstParticipantId = $firstParticipantResult->first()['id']; + + // get a result which excludes $first_participant + $otherParticipantResult = Participant::get() + ->setCheckPermissions(FALSE) + ->setSelect(['id']) + ->addClause('NOT', [ + ['event_id', '=', $firstEventId], + ['contact_id', '=', $firstContactId], + ] + ) + ->execute() + ->indexBy('id'); + + // check alternate syntax for NOT + $otherParticipantResult2 = Participant::get() + ->setCheckPermissions(FALSE) + ->setSelect(['id']) + ->addClause('NOT', 'AND', [ + ['event_id', '=', $firstEventId], + ['contact_id', '=', $firstContactId], + ] + ) + ->execute() + ->indexBy('id'); + + $this->assertEquals($otherParticipantResult, $otherParticipantResult2); + + $this->assertEquals($participantCount - 1, + count($otherParticipantResult), + "failed to exclude a single record on complex criteria"); + // check the record we have excluded is the right one: + + $this->assertFalse( + $otherParticipantResult->offsetExists($firstParticipantId), + 'excluded wrong record'); + + // retrieve a participant record and update some records + $patchRecord = [ + 'source' => "not " . $firstResult['source'], + ]; + + Participant::update() + ->addWhere('event_id', '=', $firstEventId) + ->setCheckPermissions(FALSE) + ->setLimit(20) + ->setValues($patchRecord) + ->setCheckPermissions(FALSE) + ->execute(); + + // - delete some records + $secondEventId = $dummy['events'][1]['id']; + $deleteResult = Participant::delete() + ->addWhere('event_id', '=', $secondEventId) + ->setCheckPermissions(FALSE) + ->execute(); + $expectedDeletes = [2, 7, 12, 17]; + $this->assertEquals($expectedDeletes, (array) $deleteResult, + "didn't delete every second record as expected"); + + $sqlCount = $this->getRowCount('civicrm_participant'); + $this->assertEquals( + $participantCount - count($expectedDeletes), + $sqlCount, + "records not gone from database after delete"); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/Action/MockArrayEntity/Get.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/Action/MockArrayEntity/Get.php new file mode 100644 index 00000000..f276baf6 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/Action/MockArrayEntity/Get.php @@ -0,0 +1,53 @@ +<?php + +namespace Civi\Api4\Action\MockArrayEntity; + +/** + * This class demonstrates how the getRecords method of Basic\Get can be overridden. + */ +class Get extends \Civi\Api4\Generic\BasicGetAction { + + public function getRecords() { + return [ + [ + 'field1' => 1, + 'field2' => 'zebra', + 'field3' => NULL, + 'field4' => [1, 2, 3], + 'field5' => 'apple', + ], + [ + 'field1' => 2, + 'field2' => 'yack', + 'field3' => 0, + 'field4' => [2, 3, 4], + 'field5' => 'banana', + 'field6' => '', + ], + [ + 'field1' => 3, + 'field2' => 'x ray', + 'field4' => [3, 4, 5], + 'field5' => 'banana', + 'field6' => 0, + ], + [ + 'field1' => 4, + 'field2' => 'wildebeest', + 'field3' => 1, + 'field4' => [4, 5, 6], + 'field5' => 'apple', + 'field6' => '0', + ], + [ + 'field1' => 5, + 'field2' => 'vole', + 'field3' => 1, + 'field4' => [4, 5, 6], + 'field5' => 'apple', + 'field6' => 0, + ], + ]; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockArrayEntity.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockArrayEntity.php new file mode 100644 index 00000000..371df50e --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockArrayEntity.php @@ -0,0 +1,21 @@ +<?php + +namespace Civi\Api4; +use Civi\Api4\Generic\BasicGetFieldsAction; + +/** + * MockArrayEntity entity. + * + * @method Generic\BasicGetAction get() + * + * @package Civi\Api4 + */ +class MockArrayEntity extends Generic\AbstractEntity { + + public static function getFields() { + return new BasicGetFieldsAction(static::class, __FUNCTION__, function() { + return []; + }); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockBasicEntity.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockBasicEntity.php new file mode 100644 index 00000000..aed11e45 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockBasicEntity.php @@ -0,0 +1,94 @@ +<?php + +namespace Civi\Api4; + +/** + * MockBasicEntity entity. + * + * @package Civi\Api4 + */ +class MockBasicEntity extends Generic\AbstractEntity { + + const STORAGE_CLASS = '\\Civi\\Test\\Api4\\Mock\\MockEntityDataStorage'; + + /** + * @return Generic\BasicGetFieldsAction + */ + public static function getFields() { + return new Generic\BasicGetFieldsAction(static::class, __FUNCTION__, function() { + return [ + [ + 'name' => 'id', + 'type' => 'Integer', + ], + [ + 'name' => 'group', + 'options' => [ + 'one' => 'One', + 'two' => 'Two', + ] + ], + [ + 'name' => 'color', + ], + [ + 'name' => 'shape', + ], + [ + 'name' => 'size', + ], + [ + 'name' => 'weight', + ], + ]; + }); + } + + /** + * @return Generic\BasicGetAction + */ + public static function get() { + return new Generic\BasicGetAction('MockBasicEntity', __FUNCTION__, [self::STORAGE_CLASS, 'get']); + } + + /** + * @return Generic\BasicCreateAction + */ + public static function create() { + return new Generic\BasicCreateAction(static::class, __FUNCTION__, [self::STORAGE_CLASS, 'write']); + } + + /** + * @return Generic\BasicUpdateAction + */ + public static function update() { + return new Generic\BasicUpdateAction(self::getEntityName(), __FUNCTION__, 'id', [self::STORAGE_CLASS, 'write']); + } + + /** + * @return Generic\BasicBatchAction + */ + public static function delete() { + return new Generic\BasicBatchAction('MockBasicEntity', __FUNCTION__, 'id', [self::STORAGE_CLASS, 'delete']); + } + + /** + * @return Generic\BasicBatchAction + */ + public static function batchFrobnicate() { + return new Generic\BasicBatchAction('MockBasicEntity', __FUNCTION__, ['id', 'number'], function ($item) { + return [ + 'id' => $item['id'], + 'frobnication' => $item['number'] * $item['number'], + ]; + }); + } + + /** + * @return Generic\BasicReplaceAction + */ + public static function replace() { + return new Generic\BasicReplaceAction('MockBasicEntity', __FUNCTION__); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockEntityDataStorage.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockEntityDataStorage.php new file mode 100644 index 00000000..fbb10465 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockEntityDataStorage.php @@ -0,0 +1,31 @@ +<?php + +namespace Civi\Test\Api4\Mock; + +/** + * Simple data backend for mock basic api. + */ +class MockEntityDataStorage { + + private static $data = []; + + private static $nextId = 1; + + public static function get() { + return self::$data; + } + + public static function write($record) { + if (empty($record['id'])) { + $record['id'] = self::$nextId++; + } + self::$data[$record['id']] = $record; + return $record; + } + + public static function delete($record) { + unset(self::$data[$record['id']]); + return $record; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionBase.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionBase.php new file mode 100644 index 00000000..e46272d2 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionBase.php @@ -0,0 +1,22 @@ +<?php + +namespace Civi\Test\Api4\Mock; + +/** + * Class TestV4ReflectionBase + * + * This is the base class. + * + * @internal + */ +class MockV4ReflectionBase { + /** + * This is the foo property. + * + * In general, you can do nothing with it. + * + * @var array + */ + public $foo = []; + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionChild.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionChild.php new file mode 100644 index 00000000..83966b5c --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionChild.php @@ -0,0 +1,16 @@ +<?php + +namespace Civi\Test\Api4\Mock; + +/** + * @inheritDoc + */ +class MockV4ReflectionChild extends MockV4ReflectionBase { + /** + * @inheritDoc + * + * In the child class, foo has been barred. + */ + public $foo = ['bar' => 1]; + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionGrandchild.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionGrandchild.php new file mode 100644 index 00000000..a2a93a4f --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionGrandchild.php @@ -0,0 +1,17 @@ +<?php + +namespace Civi\Test\Api4\Mock; + + +/** + * Grandchild class + * + * This is an extended description. + * + * There is a line break in this description. + * + * @inheritdoc + */ +class MockV4ReflectionGrandchild extends MockV4ReflectionChild { + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryComplexJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryComplexJoinTest.php new file mode 100644 index 00000000..0e68843a --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryComplexJoinTest.php @@ -0,0 +1,85 @@ +<?php + +namespace Civi\Test\Api4\Query; + +use Civi\Api4\Query\Api4SelectQuery; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class Api4SelectQueryComplexJoinTest extends UnitTestCase { + + public function setUpHeadless() { + $relatedTables = [ + 'civicrm_address', + 'civicrm_email', + 'civicrm_phone', + 'civicrm_openid', + 'civicrm_im', + 'civicrm_website', + 'civicrm_activity', + 'civicrm_activity_contact', + ]; + $this->cleanup(['tablesToTruncate' => $relatedTables]); + $this->loadDataSet('SingleContact'); + return parent::setUpHeadless(); + } + + public function testWithComplexRelatedEntitySelect() { + $query = new Api4SelectQuery('Contact', FALSE); + $query->select[] = 'id'; + $query->select[] = 'display_name'; + $query->select[] = 'phones.phone'; + $query->select[] = 'emails.email'; + $query->select[] = 'emails.location_type.name'; + $query->select[] = 'created_activities.contact_id'; + $query->select[] = 'created_activities.activity.subject'; + $query->select[] = 'created_activities.activity.activity_type.name'; + $query->where[] = ['first_name', '=', 'Single']; + $query->where[] = ['id', '=', $this->getReference('test_contact_1')['id']]; + $results = $query->run(); + + $testActivities = [ + $this->getReference('test_activity_1'), + $this->getReference('test_activity_2'), + ]; + $activitySubjects = array_column($testActivities, 'subject'); + + $this->assertCount(1, $results); + $firstResult = array_shift($results); + $this->assertArrayHasKey('created_activities', $firstResult); + $firstCreatedActivity = array_shift($firstResult['created_activities']); + $this->assertArrayHasKey('activity', $firstCreatedActivity); + $firstActivity = $firstCreatedActivity['activity']; + $this->assertContains($firstActivity['subject'], $activitySubjects); + $this->assertArrayHasKey('activity_type', $firstActivity); + $activityType = $firstActivity['activity_type']; + $this->assertArrayHasKey('name', $activityType); + } + + public function testWithSelectOfOrphanDeepValues() { + $query = new Api4SelectQuery('Contact', FALSE); + $query->select[] = 'id'; + $query->select[] = 'first_name'; + $query->select[] = 'emails.location_type.name'; // emails not selected + $results = $query->run(); + $firstResult = array_shift($results); + + $this->assertEmpty($firstResult['emails']); + } + + public function testOrderDoesNotMatter() { + $query = new Api4SelectQuery('Contact', FALSE); + $query->select[] = 'id'; + $query->select[] = 'first_name'; + $query->select[] = 'emails.location_type.name'; // before emails selection + $query->select[] = 'emails.email'; + $query->where[] = ['emails.email', 'IS NOT NULL']; + $results = $query->run(); + $firstResult = array_shift($results); + + $this->assertNotEmpty($firstResult['emails'][0]['location_type']['name']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryTest.php new file mode 100644 index 00000000..ca9a5851 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryTest.php @@ -0,0 +1,91 @@ +<?php + +namespace Civi\Test\Api4\Query; + +use Civi\Api4\Query\Api4SelectQuery; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class Api4SelectQueryTest extends UnitTestCase { + + public function setUpHeadless() { + $relatedTables = [ + 'civicrm_address', + 'civicrm_email', + 'civicrm_phone', + 'civicrm_openid', + 'civicrm_im', + 'civicrm_website', + 'civicrm_activity', + 'civicrm_activity_contact', + ]; + $this->cleanup(['tablesToTruncate' => $relatedTables]); + $this->loadDataSet('DefaultDataSet'); + $displayNameFormat = '{contact.first_name}{ }{contact.last_name}'; + \Civi::settings()->set('display_name_format', $displayNameFormat); + + return parent::setUpHeadless(); + } + + public function testWithSingleWhereJoin() { + $phoneNum = $this->getReference('test_phone_1')['phone']; + + $query = new Api4SelectQuery('Contact', FALSE); + $query->where[] = ['phones.phone', '=', $phoneNum]; + $results = $query->run(); + + $this->assertCount(1, $results); + } + + public function testOneToManyJoin() { + $phoneNum = $this->getReference('test_phone_1')['phone']; + + $query = new Api4SelectQuery('Contact', FALSE); + $query->select[] = 'id'; + $query->select[] = 'first_name'; + $query->select[] = 'phones.phone'; + $query->where[] = ['phones.phone', '=', $phoneNum]; + $results = $query->run(); + + $this->assertCount(1, $results); + $firstResult = array_shift($results); + $this->assertArrayHasKey('phones', $firstResult); + $firstPhone = array_shift($firstResult['phones']); + $this->assertEquals($phoneNum, $firstPhone['phone']); + } + + public function testManyToOneJoin() { + $phoneNum = $this->getReference('test_phone_1')['phone']; + $contact = $this->getReference('test_contact_1'); + + $query = new Api4SelectQuery('Phone', FALSE); + $query->select[] = 'id'; + $query->select[] = 'phone'; + $query->select[] = 'contact.display_name'; + $query->select[] = 'contact.first_name'; + $query->where[] = ['phone', '=', $phoneNum]; + $results = $query->run(); + + $this->assertCount(1, $results); + $firstResult = array_shift($results); + $this->assertArrayHasKey('contact', $firstResult); + $resultContact = $firstResult['contact']; + $this->assertEquals($contact['display_name'], $resultContact['display_name']); + } + + public function testOneToManyMultipleJoin() { + $query = new Api4SelectQuery('Contact', FALSE); + $query->select[] = 'id'; + $query->select[] = 'first_name'; + $query->select[] = 'phones.phone'; + $query->where[] = ['first_name', '=', 'Phoney']; + $results = $query->run(); + $result = array_pop($results); + + $this->assertEquals('Phoney', $result['first_name']); + $this->assertCount(2, $result['phones']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OneToOneJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OneToOneJoinTest.php new file mode 100644 index 00000000..ef05f657 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OneToOneJoinTest.php @@ -0,0 +1,46 @@ +<?php + +namespace Civi\Test\Api4\Query; + +use Civi\Api4\Contact; +use Civi\Api4\OptionGroup; +use Civi\Api4\OptionValue; +use Civi\Test\Api4\UnitTestCase; + +/** + * Class OneToOneJoinTest + * @package Civi\Test\Api4\Query + * @group headless + */ +class OneToOneJoinTest extends UnitTestCase { + + public function testOneToOneJoin() { + $armenianContact = Contact::create() + ->addValue('first_name', 'Contact') + ->addValue('last_name', 'One') + ->addValue('contact_type', 'Individual') + ->addValue('preferred_language', 'hy_AM') + ->execute() + ->first(); + + $basqueContact = Contact::create() + ->addValue('first_name', 'Contact') + ->addValue('last_name', 'Two') + ->addValue('contact_type', 'Individual') + ->addValue('preferred_language', 'eu_ES') + ->execute() + ->first(); + + $contacts = Contact::get() + ->addWhere('id', 'IN', [$armenianContact['id'], $basqueContact['id']]) + ->addSelect('preferred_language.label') + ->addSelect('last_name') + ->execute() + ->indexBy('last_name') + ->getArrayCopy(); + + $this->assertEquals($contacts['One']['preferred_language']['label'], 'Armenian'); + $this->assertEquals($contacts['Two']['preferred_language']['label'], 'Basque'); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OptionValueJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OptionValueJoinTest.php new file mode 100644 index 00000000..75d4b977 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OptionValueJoinTest.php @@ -0,0 +1,46 @@ +<?php + +namespace Civi\Test\Api4\Query; + +use Civi\Api4\Query\Api4SelectQuery; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class OptionValueJoinTest extends UnitTestCase { + + public function setUpHeadless() { + $relatedTables = [ + 'civicrm_address', + 'civicrm_email', + 'civicrm_phone', + 'civicrm_openid', + 'civicrm_im', + 'civicrm_website', + 'civicrm_activity', + 'civicrm_activity_contact', + ]; + + $this->cleanup(['tablesToTruncate' => $relatedTables]); + $this->loadDataSet('SingleContact'); + + return parent::setUpHeadless(); + } + + public function testCommunicationMethodJoin() { + $query = new Api4SelectQuery('Contact', FALSE); + $query->select[] = 'first_name'; + $query->select[] = 'preferred_communication_method.label'; + $query->where[] = ['preferred_communication_method', 'IS NOT NULL']; + $results = $query->run(); + $first = array_shift($results); + $firstPreferredMethod = array_shift($first['preferred_communication_method']); + + $this->assertEquals( + 'Phone', + $firstPreferredMethod['label'] + ); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/SelectQueryMultiJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/SelectQueryMultiJoinTest.php new file mode 100644 index 00000000..860bd785 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/SelectQueryMultiJoinTest.php @@ -0,0 +1,74 @@ +<?php + +namespace Civi\Test\Api4\Query; + +use Civi\Api4\Contact; +use Civi\Api4\Email; +use Civi\Test\Api4\UnitTestCase; + +/** + * Class SelectQueryMultiJoinTest + * @package Civi\Test\Api4\Query + * @group headless + */ +class SelectQueryMultiJoinTest extends UnitTestCase { + public function setUpHeadless() { + $this->cleanup(['tablesToTruncate' => ['civicrm_contact', 'civicrm_email']]); + $this->loadDataSet('MultiContactMultiEmail'); + return parent::setUpHeadless(); + } + + public function testOneToManySelect() { + $results = Contact::get() + ->addSelect('emails.email') + ->execute() + ->indexBy('id') + ->getArrayCopy(); + + $firstContactId = $this->getReference('test_contact_1')['id']; + $secondContactId = $this->getReference('test_contact_2')['id']; + + $firstContact = $results[$firstContactId]; + $secondContact = $results[$secondContactId]; + $firstContactEmails = array_column($firstContact['emails'], 'email'); + $secondContactEmails = array_column($secondContact['emails'], 'email'); + + $expectedFirstEmails = [ + 'test_contact_one_home@fakedomain.com', + 'test_contact_one_work@fakedomain.com', + ]; + $expectedSecondEmails = [ + 'test_contact_two_home@fakedomain.com', + 'test_contact_two_work@fakedomain.com', + ]; + + $this->assertEquals($expectedFirstEmails, $firstContactEmails); + $this->assertEquals($expectedSecondEmails, $secondContactEmails); + } + + public function testManyToOneSelect() { + $results = Email::get() + ->addSelect('contact.display_name') + ->execute() + ->indexBy('id') + ->getArrayCopy(); + + $firstEmail = $this->getReference('test_email_1'); + $secondEmail = $this->getReference('test_email_2'); + $thirdEmail = $this->getReference('test_email_3'); + $fourthEmail = $this->getReference('test_email_4'); + $firstContactEmailIds = [$firstEmail['id'], $secondEmail['id']]; + $secondContactEmailIds = [$thirdEmail['id'], $fourthEmail['id']]; + + foreach ($results as $id => $email) { + $displayName = $email['contact']['display_name']; + if (in_array($id, $firstContactEmailIds)) { + $this->assertEquals('First Contact', $displayName); + } + elseif (in_array($id, $secondContactEmailIds)) { + $this->assertEquals('Second Contact', $displayName); + } + } + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapRealTableTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapRealTableTest.php new file mode 100644 index 00000000..183d34dc --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapRealTableTest.php @@ -0,0 +1,22 @@ +<?php + +namespace Civi\Test\Api4\Service\Schema; + +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class SchemaMapRealTableTest extends UnitTestCase { + public function testAutoloadWillPopulateTablesByDefault() { + $map = \Civi::container()->get('schema_map'); + $this->assertNotEmpty($map->getTables()); + } + + public function testSimplePathWillExist() { + $map = \Civi::container()->get('schema_map'); + $path = $map->getPath('civicrm_contact', 'emails'); + $this->assertCount(1, $path); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapperTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapperTest.php new file mode 100644 index 00000000..04952f7b --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapperTest.php @@ -0,0 +1,90 @@ +<?php + +namespace Civi\Test\Api4\Service\Schema; + +use Civi\Api4\Service\Schema\Joinable\Joinable; +use Civi\Api4\Service\Schema\SchemaMap; +use Civi\Api4\Service\Schema\Table; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class SchemaMapperTest extends UnitTestCase { + + public function testWillHaveNoPathWithNoTables() { + $map = new SchemaMap(); + $this->assertEmpty($map->getPath('foo', 'bar')); + } + + public function testWillHavePathWithSingleJump() { + $phoneTable = new Table('civicrm_phone'); + $locationTable = new Table('civicrm_location_type'); + $link = new Joinable('civicrm_location_type', 'id', 'location'); + $phoneTable->addTableLink('location_type_id', $link); + + $map = new SchemaMap(); + $map->addTables([$phoneTable, $locationTable]); + + $this->assertNotEmpty($map->getPath('civicrm_phone', 'location')); + } + + public function testWillHavePathWithDoubleJump() { + $activity = new Table('activity'); + $activityContact = new Table('activity_contact'); + $middleLink = new Joinable('activity_contact', 'activity_id'); + $contactLink = new Joinable('contact', 'id'); + $activity->addTableLink('id', $middleLink); + $activityContact->addTableLink('contact_id', $contactLink); + + $map = new SchemaMap(); + $map->addTables([$activity, $activityContact]); + + $this->assertNotEmpty($map->getPath('activity', 'contact')); + } + + public function testPathWithTripleJoin() { + $first = new Table('first'); + $second = new Table('second'); + $third = new Table('third'); + $first->addTableLink('id', new Joinable('second', 'id')); + $second->addTableLink('id', new Joinable('third', 'id')); + $third->addTableLink('id', new Joinable('fourth', 'id')); + + $map = new SchemaMap(); + $map->addTables([$first, $second, $third]); + + $this->assertNotEmpty($map->getPath('first', 'fourth')); + } + + public function testCircularReferenceWillNotBreakIt() { + $contactTable = new Table('contact'); + $carTable = new Table('car'); + $carLink = new Joinable('car', 'id'); + $ownerLink = new Joinable('contact', 'id'); + $contactTable->addTableLink('car_id', $carLink); + $carTable->addTableLink('owner_id', $ownerLink); + + $map = new SchemaMap(); + $map->addTables([$contactTable, $carTable]); + + $this->assertEmpty($map->getPath('contact', 'foo')); + } + + public function testCannotGoOverJoinLimit() { + $first = new Table('first'); + $second = new Table('second'); + $third = new Table('third'); + $fourth = new Table('fourth'); + $first->addTableLink('id', new Joinable('second', 'id')); + $second->addTableLink('id', new Joinable('third', 'id')); + $third->addTableLink('id', new Joinable('fourth', 'id')); + $fourth->addTableLink('id', new Joinable('fifth', 'id')); + + $map = new SchemaMap(); + $map->addTables([$first, $second, $third, $fourth]); + + $this->assertEmpty($map->getPath('first', 'fifth')); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/TestCreationParameterProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/TestCreationParameterProvider.php new file mode 100644 index 00000000..ef10cea9 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/TestCreationParameterProvider.php @@ -0,0 +1,148 @@ +<?php + +namespace Civi\Test\Api4\Service; + +use Civi\Api4\Service\Spec\FieldSpec; +use Civi\Api4\Service\Spec\SpecGatherer; +use \CRM_Utils_String as StringHelper; + +class TestCreationParameterProvider { + + /** + * @var SpecGatherer + */ + protected $gatherer; + + /** + * @param SpecGatherer $gatherer + */ + public function __construct(SpecGatherer $gatherer) { + $this->gatherer = $gatherer; + } + + /** + * @param $entity + * + * @return array + */ + public function getRequired($entity) { + $createSpec = $this->gatherer->getSpec($entity, 'create', FALSE); + $requiredFields = array_merge($createSpec->getRequiredFields(), $createSpec->getConditionalRequiredFields()); + + if ($entity === 'Contact') { + $requiredFields[] = $createSpec->getFieldByName('first_name'); + $requiredFields[] = $createSpec->getFieldByName('last_name'); + } + + $requiredParams = []; + foreach ($requiredFields as $requiredField) { + $value = $this->getRequiredValue($requiredField); + $requiredParams[$requiredField->getName()] = $value; + } + + unset($requiredParams['id']); + + return $requiredParams; + } + + /** + * Attempt to get a value using field option, defaults, FKEntity, or a random + * value based on the data type. + * + * @param FieldSpec $field + * + * @return mixed + * @throws \Exception + */ + private function getRequiredValue(FieldSpec $field) { + + if ($field->getOptions()) { + return $this->getOption($field); + } + elseif ($field->getDefaultValue()) { + return $field->getDefaultValue(); + } + elseif ($field->getFkEntity()) { + return $this->getFkID($field); + } + + $randomValue = $this->getRandomValue($field->getDataType()); + + if ($randomValue) { + return $randomValue; + } + + throw new \Exception('Could not provide default value'); + } + + /** + * @param FieldSpec $field + * + * @return mixed + */ + private function getOption(FieldSpec $field) { + $options = $field->getOptions(); + $useKeyNames = ['data_type', 'html_type']; + $shouldUseKey = in_array($field->getName(), $useKeyNames); + $isIdField = substr($field->getName(), -3) === '_id'; + + if ($isIdField || $shouldUseKey) { + return array_rand($options); // return key (ID) + } + else { + return $options[array_rand($options)]; + } + } + + /** + * @param FieldSpec $field + * + * @return mixed + * @throws \Exception + */ + private function getFkID(FieldSpec $field) { + $fkEntity = $field->getFkEntity(); + $params = ['checkPermissions' => FALSE]; + // Be predictable about what type of contact we select + if ($fkEntity === 'Contact') { + $params['where'] = [['contact_type', '=', 'Individual']]; + } + $entityList = civicrm_api4($fkEntity, 'get', $params); + if ($entityList->count() < 1) { + $msg = sprintf('At least one %s is required in test', $fkEntity); + throw new \Exception($msg); + } + + return $entityList->last()['id']; + } + + /** + * @param $dataType + * + * @return int|null|string + */ + private function getRandomValue($dataType) { + switch ($dataType) { + case 'Boolean': + return TRUE; + + case 'Integer': + return rand(1, 2000); + + case 'String': + return StringHelper::createRandom(10, implode('', range('a', 'z'))); + + case 'Text': + return StringHelper::createRandom(100, implode('', range('a', 'z'))); + + case 'Money': + return sprintf('%d.%2d', rand(0, 2000), rand(1, 99)); + + case 'Date': + return '20100102'; + } + + return NULL; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/RequestSpecTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/RequestSpecTest.php new file mode 100644 index 00000000..45aa41b2 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/RequestSpecTest.php @@ -0,0 +1,42 @@ +<?php + +namespace Civi\Test\Api4\Spec; + +use Civi\Api4\Service\Spec\FieldSpec; +use Civi\Api4\Service\Spec\RequestSpec; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class RequestSpecTest extends UnitTestCase { + + public function testRequiredFieldFetching() { + $spec = new RequestSpec('Contact', 'get'); + $requiredField = new FieldSpec('name', 'Contact'); + $requiredField->setRequired(TRUE); + $nonRequiredField = new FieldSpec('age', 'Contact', 'Integer'); + $nonRequiredField->setRequired(FALSE); + $spec->addFieldSpec($requiredField); + $spec->addFieldSpec($nonRequiredField); + + $requiredFields = $spec->getRequiredFields(); + + $this->assertCount(1, $requiredFields); + $this->assertEquals('name', array_shift($requiredFields)->getName()); + } + + public function testGettingFieldNames() { + $spec = new RequestSpec('Contact', 'get'); + $nameField = new FieldSpec('name', 'Contact'); + $ageField = new FieldSpec('age', 'Contact', 'Integer'); + $spec->addFieldSpec($nameField); + $spec->addFieldSpec($ageField); + + $fieldNames = $spec->getFieldNames(); + + $this->assertCount(2, $fieldNames); + $this->assertEquals(['name', 'age'], $fieldNames); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecFormatterTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecFormatterTest.php new file mode 100644 index 00000000..bdb1c459 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecFormatterTest.php @@ -0,0 +1,90 @@ +<?php + +namespace Civi\Test\Api4\Spec; + +use Civi\Api4\Service\Spec\CustomFieldSpec; +use Civi\Api4\Service\Spec\FieldSpec; +use Civi\Api4\Service\Spec\RequestSpec; +use Civi\Api4\Service\Spec\SpecFormatter; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class SpecFormatterTest extends UnitTestCase { + + public function testSpecToArray() { + $spec = new RequestSpec('Contact', 'get'); + $fieldName = 'last_name'; + $field = new FieldSpec($fieldName, 'Contact'); + $spec->addFieldSpec($field); + $arraySpec = SpecFormatter::specToArray($spec->getFields()); + + $this->assertEquals('String', $arraySpec[$fieldName]['data_type']); + } + + /** + * @dataProvider arrayFieldSpecProvider + * + * @param array $fieldData + * @param string $expectedName + * @param string $expectedType + */ + public function testArrayToField($fieldData, $expectedName, $expectedType) { + $field = SpecFormatter::arrayToField($fieldData, 'TestEntity'); + + $this->assertEquals($expectedName, $field->getName()); + $this->assertEquals($expectedType, $field->getDataType()); + } + + public function testCustomFieldWillBeReturned() { + $customGroupId = 1432; + $customFieldId = 3333; + $name = 'MyFancyField'; + + $data = [ + 'custom_group_id' => $customGroupId, + 'custom_group' => ['name' => 'my_group'], + 'id' => $customFieldId, + 'name' => $name, + 'data_type' => 'String', + 'html_type' => 'MultiSelect', + ]; + + /** @var CustomFieldSpec $field */ + $field = SpecFormatter::arrayToField($data, 'TestEntity'); + + $this->assertInstanceOf(CustomFieldSpec::class, $field); + $this->assertEquals('my_group', $field->getCustomGroupName()); + $this->assertEquals($customFieldId, $field->getCustomFieldId()); + $this->assertEquals(\CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND, $field->getSerialize()); + } + + /** + * @return array + */ + public function arrayFieldSpecProvider() { + return [ + [ + [ + 'name' => 'Foo', + 'title' => 'Bar', + 'type' => \CRM_Utils_Type::T_STRING + ], + 'Foo', + 'String' + ], + [ + [ + 'name' => 'MyField', + 'title' => 'Bar', + 'type' => \CRM_Utils_Type::T_STRING, + 'data_type' => 'Boolean' // this should take precedence + ], + 'MyField', + 'Boolean' + ], + ]; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecGathererTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecGathererTest.php new file mode 100644 index 00000000..bf5b92b9 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecGathererTest.php @@ -0,0 +1,96 @@ +<?php + +namespace Civi\Test\Api4\Spec; + +use Civi\Api4\Service\Spec\FieldSpec; +use Civi\Api4\Service\Spec\Provider\SpecProviderInterface; +use Civi\Api4\Service\Spec\RequestSpec; +use Civi\Api4\Service\Spec\SpecGatherer; +use Civi\Test\Api4\Traits\OptionCleanupTrait; +use Civi\Test\Api4\UnitTestCase; +use Civi\Api4\CustomField; +use Civi\Api4\CustomGroup; +use Civi\Test\Api4\Traits\TableDropperTrait; +use Prophecy\Argument; + +/** + * @group headless + */ +class SpecGathererTest extends UnitTestCase { + + use TableDropperTrait; + use OptionCleanupTrait; + + public function setUpHeadless() { + $this->dropByPrefix('civicrm_value_favorite'); + $this->cleanup([ + 'tablesToTruncate' => [ + 'civicrm_custom_group', + 'civicrm_custom_field' + ], + ]); + return parent::setUpHeadless(); + } + + public function testBasicFieldsGathering() { + $gatherer = new SpecGatherer(); + $specs = $gatherer->getSpec('Contact', 'get', FALSE); + $contactDAO = _civicrm_api3_get_DAO('Contact'); + $contactFields = $contactDAO::fields(); + $specFieldNames = $specs->getFieldNames(); + $contactFieldNames = array_column($contactFields, 'name'); + + $this->assertEmpty(array_diff_key($contactFieldNames, $specFieldNames)); + } + + public function testWithSpecProvider() { + $gather = new SpecGatherer(); + + $provider = $this->prophesize(SpecProviderInterface::class); + $provider->applies('Contact', 'create')->willReturn(TRUE); + $provider->modifySpec(Argument::any())->will(function ($args) { + /** @var RequestSpec $spec */ + $spec = $args[0]; + $spec->addFieldSpec(new FieldSpec('foo', 'Contact')); + }); + $gather->addSpecProvider($provider->reveal()); + + $spec = $gather->getSpec('Contact', 'create', FALSE); + $fieldNames = $spec->getFieldNames(); + + $this->assertContains('foo', $fieldNames); + } + + public function testPseudoConstantOptionsWillBeAdded() { + $customGroupId = CustomGroup::create() + ->setCheckPermissions(FALSE) + ->addValue('name', 'FavoriteThings') + ->addValue('extends', 'Contact') + ->execute() + ->first()['id']; + + $options = ['r' => 'Red', 'g' => 'Green', 'p' => 'Pink']; + + CustomField::create() + ->setCheckPermissions(FALSE) + ->addValue('label', 'FavColor') + ->addValue('custom_group_id', $customGroupId) + ->addValue('options', $options) + ->addValue('html_type', 'Select') + ->addValue('data_type', 'String') + ->execute(); + + $gatherer = new SpecGatherer(); + $spec = $gatherer->getSpec('Contact', 'get', TRUE); + + $regularField = $spec->getFieldByName('contact_type'); + $this->assertNotEmpty($regularField->getOptions()); + $this->assertContains('Individual', $regularField->getOptions()); + + $customField = $spec->getFieldByName('FavoriteThings.FavColor'); + $this->assertNotEmpty($customField->getOptions()); + $this->assertContains('Green', $customField->getOptions()); + $this->assertEquals('Pink', $customField->getOptions()['p']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/OptionCleanupTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/OptionCleanupTrait.php new file mode 100644 index 00000000..06f43235 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/OptionCleanupTrait.php @@ -0,0 +1,24 @@ +<?php + +namespace Civi\Test\Api4\Traits; + +trait OptionCleanupTrait { + + protected $optionGroupMaxId; + protected $optionValueMaxId; + + public function setUp() { + $this->optionGroupMaxId = \CRM_Core_DAO::singleValueQuery('SELECT MAX(id) FROM civicrm_option_group'); + $this->optionValueMaxId = \CRM_Core_DAO::singleValueQuery('SELECT MAX(id) FROM civicrm_option_value'); + } + + public function tearDown() { + if ($this->optionValueMaxId) { + \CRM_Core_DAO::executeQuery('DELETE FROM civicrm_option_value WHERE id > ' . $this->optionValueMaxId); + } + if ($this->optionGroupMaxId) { + \CRM_Core_DAO::executeQuery('DELETE FROM civicrm_option_group WHERE id > ' . $this->optionGroupMaxId); + } + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/QueryCounterTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/QueryCounterTrait.php new file mode 100644 index 00000000..c7e10f1b --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/QueryCounterTrait.php @@ -0,0 +1,43 @@ +<?php + +namespace Civi\Test\Api4\Traits; + +use \CRM_Utils_Array as ArrayHelper; + +trait QueryCounterTrait { + + /** + * @var int + */ + protected $startCount = 0; + + /** + * Start the query counter + */ + protected function beginQueryCount() { + $this->startCount = $this->getCurrentGlobalQueryCount(); + } + + /** + * @return int + * The number of queries since the counter was started + */ + protected function getQueryCount() { + return $this->getCurrentGlobalQueryCount() - $this->startCount; + } + + /** + * @return int + * @throws \Exception + */ + private function getCurrentGlobalQueryCount() { + global $_DB_DATAOBJECT; + + if (!$_DB_DATAOBJECT) { + throw new \Exception('Database object not set so cannot count queries'); + } + + return ArrayHelper::value('RESULTSEQ', $_DB_DATAOBJECT, 0); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TableDropperTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TableDropperTrait.php new file mode 100644 index 00000000..6e543473 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TableDropperTrait.php @@ -0,0 +1,23 @@ +<?php + +namespace Civi\Test\Api4\Traits; + +trait TableDropperTrait { + /** + * @param $prefix + */ + protected function dropByPrefix($prefix) { + $sql = "SELECT CONCAT( 'DROP TABLE ', GROUP_CONCAT(table_name) , ';' ) " . + "AS statement FROM information_schema.tables " . + "WHERE table_name LIKE '%s%%' AND table_schema = DATABASE();"; + $sql = sprintf($sql, $prefix); + $dropTableQuery = \CRM_Core_DAO::executeQuery($sql); + $dropTableQuery->fetch(); + $dropTableQuery = $dropTableQuery->statement; + + if ($dropTableQuery) { + \CRM_Core_DAO::executeQuery($dropTableQuery); + } + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TestDataLoaderTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TestDataLoaderTrait.php new file mode 100644 index 00000000..1db22090 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TestDataLoaderTrait.php @@ -0,0 +1,69 @@ +<?php + +namespace Civi\Test\Api4\Traits; + +/** + * This probably should be a separate class + */ +trait TestDataLoaderTrait { + + /** + * @var array + * References to entities used for loading test data + */ + protected $references; + + /** + * Creates entities from a JSON data set + * + * @param $path + */ + protected function loadDataSet($path) { + if (!file_exists($path)) { + $path = __DIR__ . '/../DataSets/' . $path . '.json'; + } + + $dataSet = json_decode(file_get_contents($path), TRUE); + foreach ($dataSet as $entityName => $entities) { + foreach ($entities as $entityValues) { + + $entityValues = $this->replaceReferences($entityValues); + + $params = ['values' => $entityValues, 'checkPermissions' => FALSE]; + $result = civicrm_api4($entityName, 'create', $params); + if (isset($entityValues['@ref'])) { + $this->references[$entityValues['@ref']] = $result->first(); + } + } + } + } + + /** + * @param $name + * + * @return null|mixed + */ + protected function getReference($name) { + return isset($this->references[$name]) ? $this->references[$name] : NULL; + } + + /** + * @param array $entityValues + * + * @return array + */ + private function replaceReferences($entityValues) { + foreach ($entityValues as $name => $value) { + if (is_array($value)) { + $entityValues[$name] = $this->replaceReferences($value); + } + elseif (substr($value, 0, 4) === '@ref') { + $referenceName = substr($value, 5); + list ($reference, $property) = explode('.', $referenceName); + $entityValues[$name] = $this->references[$reference][$property]; + } + } + return $entityValues; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/UnitTestCase.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/UnitTestCase.php new file mode 100644 index 00000000..4f872d12 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/UnitTestCase.php @@ -0,0 +1,235 @@ +<?php + +namespace Civi\Test\Api4; + +use Civi\Test\Api4\Traits\TestDataLoaderTrait; +use Civi\Test\HeadlessInterface; +use Civi\Test\TransactionalInterface; + +/** + * @group headless + */ +class UnitTestCase extends \PHPUnit_Framework_TestCase implements HeadlessInterface, TransactionalInterface { + + use TestDataLoaderTrait; + + /** + * @see CiviUnitTestCase + * + * @param string $name + * @param array $data + * @param string $dataName + */ + public function __construct($name = NULL, array $data = [], $dataName = '') { + parent::__construct($name, $data, $dataName); + error_reporting(E_ALL & ~E_NOTICE); + } + + public function setUpHeadless() { + return \Civi\Test::headless()->installMe(__DIR__)->apply(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * + * This method is called after a test is executed. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Quick clean by emptying tables created for the test. + * + * @param array $params + */ + public function cleanup($params) { + $params += [ + 'tablesToTruncate' => [], + ]; + \CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 0;"); + foreach ($params['tablesToTruncate'] as $table) { + \Civi::log()->info('truncating: ' . $table); + $sql = "TRUNCATE TABLE $table"; + \CRM_Core_DAO::executeQuery($sql); + } + \CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 1;"); + } + + /** + * Quick record counter + * + * @param string $table_name + * @returns int record count + */ + public function getRowCount($table_name) { + $sql = "SELECT count(id) FROM $table_name"; + return (int) \CRM_Core_DAO::singleValueQuery($sql); + } + + /** + * Create sample entities (using V3 for now). + * + * @param array $params (type, seq, overrides, count) + * @return array (either single, or array of array if count >1) + */ + public static function createEntity($params) { + $params += [ + 'count' => 1, + 'seq' => 0, + ]; + $entities = []; + $entity = NULL; + for ($i = 0; $i < $params['count']; $i++) { + $params['seq']++; + $data = self::sample($params); + $api_params = ['sequential' => 1] + $data['sample_params']; + $result = civicrm_api3($data['entity'], 'create', $api_params); + if ($result['is_error']) { + throw new \Exception("creating $data[entity] failed"); + } + $entity = $result['values'][0]; + if (!($entity['id'] > 0)) { + throw new \Exception("created entity is malformed"); + } + $entities[] = $entity; + } + return $params['count'] == 1 ? $entity : $entities; + } + + /** + * Helper function for creating sample entities. + * + * Depending on the supplied sequence integer, plucks values from the dummy data. + * Constructs a foreign entity when an ID is required but isn't supplied in the overrides. + * + * Inspired by CiviUnitTestCase:: + * @todo - extract this function to own class and share with CiviUnitTestCase? + * @param array $params + * - type: string roughly matching entity type + * - seq: (optional) int sequence number for the values of this type + * - overrides: (optional) array of fill in parameters + * + * @return array + * - entity: string API entity type (usually the type supplied except for contact subtypes) + * - sample_params: array API sample_params properties of sample entity + */ + public static function sample($params) { + $params += [ + 'seq' => 0, + 'overrides' => [], + ]; + $type = $params['type']; + // sample data - if field is array then chosed based on `seq` + $sample_params = []; + if (in_array($type, ['Individual', 'Organization', 'Household'])) { + $sample_params['contact_type'] = $type; + $entity = 'Contact'; + } + else { + $entity = $type; + } + // use the seq to pluck a set of params out + foreach (self::sampleData($type) as $key => $value) { + if (is_array($value)) { + $sample_params[$key] = $value[$params['seq'] % count($value)]; + } + else { + $sample_params[$key] = $value; + } + } + if ($type == 'Individual') { + $sample_params['email'] = strtolower( + $sample_params['first_name'] . '_' . $sample_params['last_name'] . '@civicrm.org' + ); + $sample_params['prefix_id'] = 3; + $sample_params['suffix_id'] = 3; + } + if (!count($sample_params)) { + throw new \Exception("unknown sample type: $type"); + } + $sample_params = $params['overrides'] + $sample_params; + // make foreign enitiies if they haven't been supplied + foreach ($sample_params as $key => $value) { + if (substr($value, 0, 6) === 'dummy.') { + $foreign_entity = self::createEntity([ + 'type' => substr($value, 6), + 'seq' => $params['seq']]); + $sample_params[$key] = $foreign_entity['id']; + } + } + return compact("entity", "sample_params"); + } + + /** + * Provider of sample data. + * + * @return array + * Array values represent a set of allowable items. + * Strings in the form "dummy.Entity" require creating a foreign entity first. + */ + public static function sampleData($type) { + $data = [ + 'Individual' => [ + // The number of values in each list need to be coprime numbers to not have duplicates + 'first_name' => ['Anthony', 'Joe', 'Terrence', 'Lucie', 'Albert', 'Bill', 'Kim'], + 'middle_name' => ['J.', 'M.', 'P', 'L.', 'K.', 'A.', 'B.', 'C.', 'D', 'E.', 'Z.'], + 'last_name' => ['Anderson', 'Miller', 'Smith', 'Collins', 'Peterson'], + 'contact_type' => 'Individual', + ], + 'Organization' => [ + 'organization_name' => [ + 'Unit Test Organization', + 'Acme', + 'Roberts and Sons', + 'Cryo Space Labs', + 'Sharper Pens', + ], + ], + 'Household' => [ + 'household_name' => ['Unit Test household'], + ], + 'Event' => [ + 'title' => 'Annual CiviCRM meet', + 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now', + 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues', + 'event_type_id' => 1, + 'is_public' => 1, + 'start_date' => 20081021, + 'end_date' => 20081023, + 'is_online_registration' => 1, + 'registration_start_date' => 20080601, + 'registration_end_date' => 20081015, + 'max_participants' => 100, + 'event_full_text' => 'Sorry! We are already full', + 'is_monetary' => 0, + 'is_active' => 1, + 'is_show_location' => 0, + ], + 'Participant' => [ + 'event_id' => 'dummy.Event', + 'contact_id' => 'dummy.Individual', + 'status_id' => 2, + 'role_id' => 1, + 'register_date' => 20070219, + 'source' => 'Wimbeldon', + 'event_level' => 'Payment', + ], + 'Contribution' => [ + 'contact_id' => 'dummy.Individual', + 'financial_type_id' => 1, // donation, 2 = member, 3 = campaign contribution, 4=event + 'total_amount' => 7.3, + ], + 'Activity' => [ + //'activity_type_id' => 1, + 'subject' => 'unit testing', + 'source_contact_id' => 'dummy.Individual', + ], + ]; + if ($type == 'Contact') { + $type = 'Individual'; + } + return $data[$type]; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ArrayInsertionServiceTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ArrayInsertionServiceTest.php new file mode 100644 index 00000000..462f07cc --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ArrayInsertionServiceTest.php @@ -0,0 +1,67 @@ +<?php + +namespace Civi\Test\Api4\Utils; + +use Civi\Api4\Utils\ArrayInsertionUtil; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class ArrayInsertionServiceTest extends UnitTestCase { + + public function testInsertWillWork() { + $arr = []; + $path = ['foo' => FALSE, 'bar' => FALSE]; + $inserter = new ArrayInsertionUtil(); + $inserter::insert($arr, $path, ['LALA']); + + $expected = [ + 'foo' => [ + 'bar' => 'LALA' + ], + ]; + + $this->assertEquals($expected, $arr); + } + + public function testInsertionOfContactEmailLocation() { + $contacts = [ + [ + 'id' => 1, + 'first_name' => 'Jim' + ], + [ + 'id' => 2, + 'first_name' => 'Karen' + ], + ]; + $emails = [ + [ + 'email' => 'jim@jim.com', + 'id' => 2, + '_parent_id' => 1 + ], + ]; + $locationTypes = [ + [ + 'name' => 'Home', + 'id' => 3, + '_parent_id' => 2 + ], + ]; + + $emailPath = ['emails' => TRUE]; + $locationPath = ['emails' => TRUE, 'location' => FALSE]; + $inserter = new ArrayInsertionUtil(); + + foreach ($contacts as &$contact) { + $inserter::insert($contact, $emailPath, $emails); + $inserter::insert($contact, $locationPath, $locationTypes); + } + + $locationType = $contacts[0]['emails'][0]['location']['name']; + $this->assertEquals('Home', $locationType); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ReflectionUtilsTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ReflectionUtilsTest.php new file mode 100644 index 00000000..d94de337 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ReflectionUtilsTest.php @@ -0,0 +1,46 @@ +<?php + +namespace Civi\Test\Api4\Utils; + +use Civi\Api4\Utils\ReflectionUtils; +use Civi\Test\Api4\Mock\MockV4ReflectionGrandchild; +use Civi\Test\Api4\UnitTestCase; + +/** + * @group headless + */ +class ReflectionUtilsTest extends UnitTestCase { + + /** + * Test that class annotations are returned across @inheritDoc + */ + public function testGetDocBlockForClass() { + $grandChild = new MockV4ReflectionGrandchild(); + $reflection = new \ReflectionClass($grandChild); + $doc = ReflectionUtils::getCodeDocs($reflection); + + $this->assertEquals(TRUE, $doc['internal']); + $this->assertEquals('Grandchild class', $doc['description']); + + $expectedComment = 'This is an extended description. + +There is a line break in this description. + +This is the base class.'; + + $this->assertEquals($expectedComment, $doc['comment']); + } + + /** + * Test that property annotations are returned across @inheritDoc + */ + public function testGetDocBlockForProperty() { + $grandChild = new MockV4ReflectionGrandchild(); + $reflection = new \ReflectionClass($grandChild); + $doc = ReflectionUtils::getCodeDocs($reflection->getProperty('foo'), 'Property'); + + $this->assertEquals('This is the foo property.', $doc['description']); + $this->assertEquals("In the child class, foo has been barred.\n\nIn general, you can do nothing with it.", $doc['comment']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/bootstrap.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/bootstrap.php new file mode 100644 index 00000000..1c28e611 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/bootstrap.php @@ -0,0 +1,56 @@ +<?php + +define('CIVICRM_CONTAINER_CACHE', 'never'); +ini_set('memory_limit', '2G'); +ini_set('safe_mode', 0); +$bootCode = cv('php:boot --level=classloader', 'phpcode'); +eval($bootCode); + +preg_match('/require_once\s*\'(.*)\'/', $bootCode, $matches); +$loader = require sprintf('%s/vendor/autoload.php', $matches[1]); +$loader->addPsr4('Civi\\Test\\Api4\\', __DIR__); +$loader->addPsr4('Civi\\Api4\\', __DIR__ . '/Mock/Api4'); + +/** + * Call the "cv" command. + * + * @param string $cmd + * The rest of the command to send. + * @param string $decode + * Ex: 'json' or 'phpcode'. + * @return string + * Response output (if the command executed normally). + * @throws \RuntimeException + * If the command terminates abnormally. + */ +function cv($cmd, $decode = 'json') { + $cmd = 'cv ' . $cmd; + $descriptorSpec = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => STDERR]; + $oldOutput = getenv('CV_OUTPUT'); + putenv("CV_OUTPUT=json"); + $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__); + putenv("CV_OUTPUT=$oldOutput"); + fclose($pipes[0]); + $result = stream_get_contents($pipes[1]); + fclose($pipes[1]); + if (proc_close($process) !== 0) { + throw new RuntimeException("Command failed ($cmd):\n$result"); + } + switch ($decode) { + case 'raw': + return $result; + + case 'phpcode': + // If the last output is /*PHPCODE*/, then we managed to complete execution. + if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + return $result; + + case 'json': + return json_decode($result, 1); + + default: + throw new RuntimeException("Bad decoder format ($decode)"); + } +} |