summaryrefslogtreecommitdiff
path: root/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action
diff options
context:
space:
mode:
Diffstat (limited to 'www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action')
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BaseCustomValueTest.php31
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicActionsTest.php154
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicCustomFieldTest.php187
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ChainTest.php53
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ComplexQueryTest.php87
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php170
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateCustomValueTest.php64
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateWithOptionGroupTest.php190
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValuePerformanceTest.php95
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValueTest.php174
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/DateTest.php46
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/EvaluateConditionTest.php38
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ExtendFromIndividualTest.php54
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/FkJoinTest.php75
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetExtraFieldsTest.php26
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetFromArrayTest.php163
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/IndexTest.php48
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/NullValueTest.php55
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ReplaceTest.php171
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateContactTest.php34
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateCustomValueTest.php56
21 files changed, 1971 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);
+ }
+
+}