Merge pull request #14417 from civicrm/5.14
[civicrm-core.git] / tests / phpunit / api / v3 / ContactTest.php
index 32301bbb1bb6a5f071403dc5e80b39bbd073b829..4788abef523b6730bac7cb68737834e5b228c2e9 100644 (file)
@@ -37,6 +37,8 @@
  * @group headless
  */
 class api_v3_ContactTest extends CiviUnitTestCase {
+  use CRMTraits_Custom_CustomDataTrait;
+
   public $DBResetRequired = FALSE;
   protected $_apiversion;
   protected $_entity;
@@ -45,6 +47,14 @@ class api_v3_ContactTest extends CiviUnitTestCase {
   protected $_contactID;
   protected $_financialTypeId = 1;
 
+
+  /**
+   * Entity to be extended.
+   *
+   * @var string
+   */
+  protected $entity = 'Contact';
+
   /**
    * Test setup for every test.
    *
@@ -54,7 +64,6 @@ class api_v3_ContactTest extends CiviUnitTestCase {
   public function setUp() {
     // Connect to the database.
     parent::setUp();
-    $this->_apiversion = 3;
     $this->_entity = 'contact';
     $this->_params = array(
       'first_name' => 'abc1',
@@ -69,6 +78,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * @throws \Exception
    */
   public function tearDown() {
+    $this->_apiversion = 3;
     $this->callAPISuccess('Setting', 'create', array('includeOrderByClause' => TRUE));
     // truncate a few tables
     $tablesToTruncate = array(
@@ -87,6 +97,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
       'civicrm_group_contact',
       'civicrm_saved_search',
       'civicrm_group_contact_cache',
+      'civicrm_prevnext_cache',
     );
 
     $this->quickCleanup($tablesToTruncate, TRUE);
@@ -98,8 +109,12 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    *
    * Verify that attempt to create individual contact with only
    * first and last names succeeds
+   *
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testAddCreateIndividual() {
+  public function testAddCreateIndividual($version) {
+    $this->_apiversion = $version;
     $oldCount = CRM_Core_DAO::singleValueQuery('select count(*) from civicrm_contact');
     $params = array(
       'first_name' => 'abc1',
@@ -190,8 +205,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test civicrm_contact_create.
    *
    * Verify that preferred language can be set.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testAddCreateIndividualWithPreferredLanguage() {
+  public function testAddCreateIndividualWithPreferredLanguage($version) {
+    $this->_apiversion = $version;
     $params = array(
       'first_name' => 'abc1',
       'contact_type' => 'Individual',
@@ -207,6 +225,8 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test civicrm_contact_create with sub-types.
    *
    * Verify that sub-types are created successfully and not deleted by subsequent updates.
+   *
+   * v3 only - uses nonstandard syntax
    */
   public function testIndividualSubType() {
     $params = array(
@@ -232,8 +252,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Verify that we can retreive contacts of different sub types
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetMultipleContactSubTypes() {
+  public function testGetMultipleContactSubTypes($version) {
+    $this->_apiversion = $version;
 
     // This test presumes that there are no parents or students in the dataset
 
@@ -365,8 +388,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test creating individual by name.
    *
    * Verify create individual contact with only first and last names succeeds.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateNameIndividual() {
+  public function testCreateNameIndividual($version) {
+    $this->_apiversion = $version;
     $params = array(
       'first_name' => 'abc1',
       'contact_type' => 'Individual',
@@ -380,8 +406,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test creating individual by display_name.
    *
    * Display name & sort name should be set.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateDisplayNameIndividual() {
+  public function testCreateDisplayNameIndividual($version) {
+    $this->_apiversion = $version;
     $params = array(
       'display_name' => 'abc1',
       'contact_type' => 'Individual',
@@ -394,8 +423,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test that name searches are case insensitive.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetNameVariantsCaseInsensitive() {
+  public function testGetNameVariantsCaseInsensitive($version) {
+    $this->_apiversion = $version;
     $this->callAPISuccess('contact', 'create', [
       'display_name' => 'Abc1',
       'contact_type' => 'Individual',
@@ -403,7 +435,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
     $this->callAPISuccessGetSingle('Contact', ['display_name' => 'aBc1']);
     $this->callAPISuccessGetSingle('Contact', ['sort_name' => 'aBc1']);
     Civi::settings()->set('includeNickNameInName', TRUE);
-    $this->callAPISuccessGetSingle('Contact', ['display_name' => 'aBc1']);
+    $result = $this->callAPISuccessGetSingle('Contact', ['display_name' => 'aBc1']);
     $this->callAPISuccessGetSingle('Contact', ['sort_name' => 'aBc1']);
     Civi::settings()->set('includeNickNameInName', FALSE);
   }
@@ -464,8 +496,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    *
    * Verify that attempt to create household contact with only
    * household name succeeds
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateNameHousehold() {
+  public function testCreateNameHousehold($version) {
+    $this->_apiversion = $version;
     $params = array(
       'household_name' => 'The abc Household',
       'contact_type' => 'Household',
@@ -478,8 +513,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    *
    * Verify that attempt to create organization contact with only
    * organization name succeeds.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateNameOrganization() {
+  public function testCreateNameOrganization($version) {
+    $this->_apiversion = $version;
     $params = array(
       'organization_name' => 'The abc Organization',
       'contact_type' => 'Organization',
@@ -500,8 +538,12 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Check that permissions on API key are restricted (CRM-18112).
+   *
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateApiKey() {
+  public function testCreateApiKey($version) {
+    $this->_apiversion = $version;
     $config = CRM_Core_Config::singleton();
     $contactId = $this->individualCreate(array(
       'first_name' => 'A',
@@ -573,8 +615,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Note that the test is written on purpose without any
    * variables specific to participant so it can be replicated into other entities
    * and / or moved to the automated test suite
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateWithCustom() {
+  public function testCreateWithCustom($version) {
+    $this->_apiversion = $version;
     $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, __FILE__);
 
     $params = $this->_params;
@@ -606,8 +651,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * CRM-14232 test preferred language set to site default if not passed.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreatePreferredLanguageUnset() {
+  public function testCreatePreferredLanguageUnset($version) {
+    $this->_apiversion = $version;
     $this->callAPISuccess('Contact', 'create', array(
       'first_name' => 'Snoop',
       'last_name' => 'Dog',
@@ -619,8 +667,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * CRM-14232 test preferred language returns setting if not passed.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreatePreferredLanguageSet() {
+  public function testCreatePreferredLanguageSet($version) {
+    $this->_apiversion = $version;
     $this->callAPISuccess('Setting', 'create', array('contact_default_language' => 'fr_FR'));
     $this->callAPISuccess('Contact', 'create', array(
       'first_name' => 'Snoop',
@@ -633,6 +684,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * CRM-14232 test preferred language returns setting if not passed where setting is NULL.
+   * TODO: Api4
    */
   public function testCreatePreferredLanguageNull() {
     $this->callAPISuccess('Setting', 'create', array('contact_default_language' => 'null'));
@@ -647,8 +699,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * CRM-14232 test preferred language returns setting if not passed where setting is NULL.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreatePreferredLanguagePassed() {
+  public function testCreatePreferredLanguagePassed($version) {
+    $this->_apiversion = $version;
     $this->callAPISuccess('Setting', 'create', array('contact_default_language' => 'null'));
     $this->callAPISuccess('Contact', 'create', array(
       'first_name' => 'Snoop',
@@ -801,8 +856,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Check deceased contacts are not retrieved.
    *
    * Note at time of writing the default is to return default. This should possibly be changed & test added.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetDeceasedRetrieved() {
+  public function testGetDeceasedRetrieved($version) {
+    $this->_apiversion = $version;
     $this->callAPISuccess($this->_entity, 'create', $this->_params);
     $c2 = $this->callAPISuccess($this->_entity, 'create', array(
       'first_name' => 'bb',
@@ -849,8 +907,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test that we can retrieve contacts using array syntax.
    *
    * I.e 'id' => array('IN' => array('3,4')).
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetINIDArray() {
+  public function testGetINIDArray($version) {
+    $this->_apiversion = $version;
     $c1 = $this->callAPISuccess($this->_entity, 'create', $this->_params);
     $c2 = $this->callAPISuccess($this->_entity, 'create', array(
       'first_name' => 'bb',
@@ -906,8 +967,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test that sort works - new syntax.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetSortNewSyntax() {
+  public function testGetSortNewSyntax($version) {
+    $this->_apiversion = $version;
     $c1 = $this->callAPISuccess($this->_entity, 'create', $this->_params);
     $c2 = $this->callAPISuccess($this->_entity, 'create', array(
       'first_name' => 'bb',
@@ -942,8 +1006,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test sort and limit for chained relationship get.
    *
    * https://issues.civicrm.org/jira/browse/CRM-15983
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testSortLimitChainedRelationshipGetCRM15983() {
+  public function testSortLimitChainedRelationshipGetCRM15983($version) {
+    $this->_apiversion = $version;
     // Some contact
     $create_result_1 = $this->callAPISuccess('contact', 'create', array(
       'first_name' => 'Jules',
@@ -1005,8 +1072,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test apostrophe works in get & create.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetApostropheCRM10857() {
+  public function testGetApostropheCRM10857($version) {
+    $this->_apiversion = $version;
     $params = array_merge($this->_params, array('last_name' => "O'Connor"));
     $this->callAPISuccess($this->_entity, 'create', $params);
     $result = $this->callAPISuccess($this->_entity, 'getsingle', array(
@@ -1020,8 +1090,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test between accepts zero.
    *
    * In the past it incorrectly required !empty.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetBetweenZeroWorks() {
+  public function testGetBetweenZeroWorks($version) {
+    $this->_apiversion = $version;
     $this->callAPISuccess($this->_entity, 'get', [
       'contact_id' => ['BETWEEN' => [0, 9]],
     ]);
@@ -1037,6 +1110,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test retrieval by addressee id.
+   * V3 only - the "skip_greeting_processing" param is not currently in v4
    */
   public function testGetByAddresseeID() {
     $individual1ID = $this->individualCreate([
@@ -1291,8 +1365,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test for direction when chaining relationships.
    *
    * https://issues.civicrm.org/jira/browse/CRM-16084
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testDirectionChainingRelationshipsCRM16084() {
+  public function testDirectionChainingRelationshipsCRM16084($version) {
+    $this->_apiversion = $version;
     // Some contact, called Jules.
     $create_result_1 = $this->callAPISuccess('contact', 'create', array(
       'first_name' => 'Jules',
@@ -1350,9 +1427,6 @@ class api_v3_ContactTest extends CiviUnitTestCase {
     $this->callAPISuccess('contact', 'delete', array(
       'id' => $create_result_2['id'],
     ));
-    $this->callAPISuccess('contact', 'delete', array(
-      'id' => $create_result_2['id'],
-    ));
 
     // Assert.
     $this->assertEquals(1, $get_result['api.relationship.get']['count']);
@@ -1479,16 +1553,10 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Verify successful update of individual contact.
    */
   public function testUpdateIndividualWithAll() {
-    // Insert a row in civicrm_contact creating individual contact.
-    $op = new PHPUnit_Extensions_Database_Operation_Insert();
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(
-        dirname(__FILE__) . '/dataset/contact_ind.xml'
-      )
-    );
+    $contactID = $this->individualCreate();
 
-    $params = array(
-      'id' => 23,
+    $params = [
+      'id' => $contactID,
       'first_name' => 'abcd',
       'contact_type' => 'Individual',
       'nick_name' => 'This is nickname first',
@@ -1500,8 +1568,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
       'external_identifier' => '1928837465',
       'image_URL' => 'http://some.url.com/image.jpg',
       'home_url' => 'http://www.example.org',
-
-    );
+    ];
 
     $this->callAPISuccess('Contact', 'Update', $params);
     $getResult = $this->callAPISuccess('Contact', 'Get', $params);
@@ -1510,50 +1577,28 @@ class api_v3_ContactTest extends CiviUnitTestCase {
     //reducing this test partially back to api v2 level to get it through
     unset($params['home_url']);
     foreach ($params as $key => $value) {
-      $this->assertEquals($value, $getResult['values'][23][$key]);
+      $this->assertEquals($value, $getResult['values'][$contactID][$key]);
     }
-    // Check updated civicrm_contact against expected.
-    $expected = $this->createXMLDataSet(
-      dirname(__FILE__) . '/dataset/contact_ind_upd.xml'
-    );
-    $actual = new PHPUnit_Extensions_Database_DataSet_QueryDataSet(
-      $this->_dbconn
-    );
-    $actual->addTable('civicrm_contact');
-    $expected->matches($actual);
   }
 
   /**
    * Verify successful update of organization contact.
+   *
+   * @throws \Exception
    */
   public function testUpdateOrganizationWithAll() {
-    // Insert a row in civicrm_contact creating organization contact
-    $op = new PHPUnit_Extensions_Database_Operation_Insert();
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(
-        dirname(__FILE__) . '/dataset/contact_org.xml'
-      )
-    );
+    $contactID = $this->organizationCreate();
 
-    $params = array(
-      'id' => 24,
+    $params = [
+      'id' => $contactID,
       'organization_name' => 'WebAccess India Pvt Ltd',
       'legal_name' => 'WebAccess',
       'sic_code' => 'ABC12DEF',
       'contact_type' => 'Organization',
-    );
+    ];
 
     $this->callAPISuccess('Contact', 'Update', $params);
-
-    // Check updated civicrm_contact against expected.
-    $expected = $this->createXMLDataSet(
-      dirname(__FILE__) . '/dataset/contact_org_upd.xml'
-    );
-    $actual = new PHPUnit_Extensions_Database_DataSet_QueryDataSet(
-      $this->_dbconn
-    );
-    $actual->addTable('civicrm_contact');
-    $expected->matches($actual);
+    $this->getAndCheck($params, $contactID, 'Contact');
   }
 
   /**
@@ -1627,32 +1672,29 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Verify successful update of household contact.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testUpdateHouseholdWithAll() {
-    // Insert a row in civicrm_contact creating household contact
-    $op = new PHPUnit_Extensions_Database_Operation_Insert();
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(
-        dirname(__FILE__) . '/dataset/contact_hld.xml'
-      )
-    );
+  public function testUpdateHouseholdWithAll($version) {
+    $this->_apiversion = $version;
+    $contactID = $this->householdCreate();
 
-    $params = array(
-      'id' => 25,
+    $params = [
+      'id' => $contactID ,
       'household_name' => 'ABC household',
       'nick_name' => 'ABC House',
       'contact_type' => 'Household',
-    );
+    ];
 
     $result = $this->callAPISuccess('Contact', 'Update', $params);
 
-    $expected = array(
+    $expected = [
       'contact_type' => 'Household',
       'is_opt_out' => 0,
       'sort_name' => 'ABC household',
       'display_name' => 'ABC household',
       'nick_name' => 'ABC House',
-    );
+    ];
     $this->getAndCheck($expected, $result['id'], 'contact');
   }
 
@@ -1662,28 +1704,26 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Deliberately exclude contact_type as it should still cope using civicrm_api.
    *
    * CRM-7645.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testUpdateCreateWithID() {
-    // Insert a row in civicrm_contact creating individual contact.
-    $op = new PHPUnit_Extensions_Database_Operation_Insert();
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(
-        dirname(__FILE__) . '/dataset/contact_ind.xml'
-      )
-    );
-
-    $params = array(
-      'id' => 23,
+  public function testUpdateCreateWithID($version) {
+    $this->_apiversion = $version;
+    $contactID = $this->individualCreate();
+    $this->callAPISuccess('Contact', 'Update', [
+      'id' => $contactID,
       'first_name' => 'abcd',
       'last_name' => 'wxyz',
-    );
-    $this->callAPISuccess('Contact', 'Update', $params);
+    ]);
   }
 
   /**
    * Test civicrm_contact_delete() with no contact ID.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactDeleteNoID() {
+  public function testContactDeleteNoID($version) {
+    $this->_apiversion = $version;
     $params = array(
       'foo' => 'bar',
     );
@@ -1692,16 +1732,22 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test civicrm_contact_delete() with error.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactDeleteError() {
+  public function testContactDeleteError($version) {
+    $this->_apiversion = $version;
     $params = array('contact_id' => 999);
     $this->callAPIFailure('contact', 'delete', $params);
   }
 
   /**
    * Test civicrm_contact_delete().
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactDelete() {
+  public function testContactDelete($version) {
+    $this->_apiversion = $version;
     $contactID = $this->individualCreate();
     $params = array(
       'id' => $contactID,
@@ -1711,8 +1757,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test civicrm_contact_get() return only first name.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetRetFirst() {
+  public function testContactGetRetFirst($version) {
+    $this->_apiversion = $version;
     $contact = $this->callAPISuccess('contact', 'create', $this->_params);
     $params = array(
       'contact_id' => $contact['id'],
@@ -1729,8 +1778,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test civicrm_contact_get() return only first name & last name.
    *
    * Use comma separated string return with a space.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetReturnFirstLast() {
+  public function testContactGetReturnFirstLast($version) {
+    $this->_apiversion = $version;
     $contact = $this->callAPISuccess('contact', 'create', $this->_params);
     $params = array(
       'contact_id' => $contact['id'],
@@ -1756,8 +1808,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test civicrm_contact_get() return only first name & last name.
    *
    * Use comma separated string return without a space
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetReturnFirstLastNoComma() {
+  public function testContactGetReturnFirstLastNoComma($version) {
+    $this->_apiversion = $version;
     $contact = $this->callAPISuccess('contact', 'create', $this->_params);
     $params = array(
       'contact_id' => $contact['id'],
@@ -1788,55 +1843,37 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test civicrm_contact_getquick() with empty name param.
    */
   public function testContactGetQuick() {
-    // Insert a row in civicrm_contact creating individual contact.
-    $op = new PHPUnit_Extensions_Database_Operation_Insert();
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(
-        dirname(__FILE__) . '/dataset/contact_17.xml'
-      )
-    );
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(
-        dirname(__FILE__) . '/dataset/email_contact_17.xml'
-      )
-    );
-    $params = array(
-      'name' => "T",
-    );
+    $contactID = $this->individualCreate(['first_name' => 'Test', 'last_name' => 'Contact', 'email' => 'TestContact@example.com']);
 
-    $result = $this->callAPISuccess('contact', 'getquick', $params);
-    $this->assertEquals(17, $result['values'][0]['id']);
-    $params = array(
+    $result = $this->callAPISuccess('contact', 'getquick', ['name' => 'T']);
+    $this->assertEquals($contactID, $result['values'][0]['id']);
+    $params = [
       'name' => "TestContact@example.com",
       'field_name' => 'sort_name',
-    );
+    ];
     $result = $this->callAPISuccess('contact', 'getquick', $params);
-    $this->assertEquals(17, $result['values'][0]['id']);
+    $this->assertEquals($contactID, $result['values'][0]['id']);
   }
 
   /**
    * Test civicrm_contact_get) with empty params.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetEmptyParams() {
-    $this->callAPISuccess('contact', 'get', array());
+  public function testContactGetEmptyParams($version) {
+    $this->_apiversion = $version;
+    $this->callAPISuccess('contact', 'get', []);
   }
 
   /**
    * Test civicrm_contact_get(,true) with no matches.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetOldParamsNoMatches() {
-    // Insert a row in civicrm_contact creating contact 17.
-    $op = new PHPUnit_Extensions_Database_Operation_Insert();
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(
-        dirname(__FILE__) . '/dataset/contact_17.xml'
-      )
-    );
-
-    $params = array(
-      'first_name' => 'Fred',
-    );
-    $result = $this->callAPISuccess('contact', 'get', $params);
+  public function testContactGetOldParamsNoMatches($version) {
+    $this->_apiversion = $version;
+    $this->individualCreate();
+    $result = $this->callAPISuccess('contact', 'get', ['first_name' => 'Fred']);
     $this->assertEquals(0, $result['count']);
   }
 
@@ -1844,19 +1881,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test civicrm_contact_get(,true) with one match.
    */
   public function testContactGetOldParamsOneMatch() {
-    // Insert a row in civicrm_contact creating contact 17
-    $op = new PHPUnit_Extensions_Database_Operation_Insert();
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(dirname(__FILE__) . '/dataset/contact_17.xml'
-      )
-    );
+    $contactID = $this->individualCreate(['first_name' => 'Test', 'last_name' => 'Contact']);
 
-    $params = array(
-      'first_name' => 'Test',
-    );
-    $result = $this->callAPISuccess('contact', 'get', $params);
-    $this->assertEquals(17, $result['values'][17]['contact_id']);
-    $this->assertEquals(17, $result['id']);
+    $result = $this->callAPISuccess('contact', 'get', ['first_name' => 'Test']);
+    $this->assertEquals($contactID, $result['values'][$contactID]['contact_id']);
+    $this->assertEquals($contactID, $result['id']);
   }
 
   /**
@@ -1882,8 +1911,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Ensure consistent return format for option group fields.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testSetPreferredCommunicationNull() {
+  public function testSetPreferredCommunicationNull($version) {
+    $this->_apiversion = $version;
     $contact = $this->callAPISuccess('contact', 'create', array_merge($this->_params, array(
       'preferred_communication_method' => array('Phone', 'SMS'),
     )));
@@ -2119,8 +2151,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test creating multiple phones using chaining.
    *
    * @throws \Exception
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCRM13252MultipleChainedPhones() {
+  public function testCRM13252MultipleChainedPhones($version) {
+    $this->_apiversion = $version;
     $contactID = $this->householdCreate();
     $this->callAPISuccessGetCount('phone', array('contact_id' => $contactID), 0);
     $params = array(
@@ -2167,8 +2202,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Verify attempt to create individual with chained arrays and sequential.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetIndividualWithChainedArraysAndSequential() {
+  public function testGetIndividualWithChainedArraysAndSequential($version) {
+    $this->_apiversion = $version;
     $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, __FILE__);
     $params['custom_' . $ids['custom_field_id']] = "custom string";
 
@@ -2270,9 +2308,12 @@ class api_v3_ContactTest extends CiviUnitTestCase {
   /**
    * Verify attempt to create individual with chained arrays and sequential.
    *
-   *  See https://issues.civicrm.org/jira/browse/CRM-15815
+   * @see https://issues.civicrm.org/jira/browse/CRM-15815
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateIndividualWithChainedArrayAndSequential() {
+  public function testCreateIndividualWithChainedArrayAndSequential($version) {
+    $this->_apiversion = $version;
     $params = array(
       'sequential' => 1,
       'first_name' => 'abc5',
@@ -2457,6 +2498,8 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test checks usage of $values to pick & choose inputs.
+   *
+   * Api3 Only - chaining syntax is too funky for v4 (assuming entityTag "entity_id" field will be filled by magic)
    */
   public function testChainingValuesCreate() {
     $description = "This demonstrates the usage of chained api functions.  Specifically it has one 'parent function' &
@@ -2486,13 +2529,16 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test TrueFalse format - I couldn't come up with an easy way to get an error on Get.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetFormatIsSuccessTrue() {
-    $this->createContactFromXML();
+  public function testContactGetFormatIsSuccessTrue($version) {
+    $this->_apiversion = $version;
+    $contactID = $this->individualCreate(['first_name' => 'Test', 'last_name' => 'Contact']);
     $description = "This demonstrates use of the 'format.is_success' param.
     This param causes only the success or otherwise of the function to be returned as BOOLEAN";
     $subfile = "FormatIsSuccess_True";
-    $params = array('id' => 17, 'format.is_success' => 1);
+    $params = ['id' => $contactID, 'format.is_success' => 1];
     $result = $this->callAPIAndDocument('Contact', 'Get', $params, __FUNCTION__, __FILE__, $description, $subfile);
     $this->assertEquals(1, $result);
     $this->callAPISuccess('Contact', 'Delete', $params);
@@ -2500,8 +2546,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test TrueFalse format.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactCreateFormatIsSuccessFalse() {
+  public function testContactCreateFormatIsSuccessFalse($version) {
+    $this->_apiversion = $version;
 
     $description = "This demonstrates use of the 'format.is_success' param.
     This param causes only the success or otherwise of the function to be returned as BOOLEAN";
@@ -2515,8 +2564,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test long display names.
    *
    * CRM-21258
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactCreateLongDisplayName() {
+  public function testContactCreateLongDisplayName($version) {
+    $this->_apiversion = $version;
     $result = $this->callAPISuccess('Contact', 'Create', array(
       'first_name' => str_pad('a', 64, 'a'),
       'last_name' => str_pad('a', 64, 'a'),
@@ -2530,8 +2582,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * Test that we can set the sort name via the api or alter it via a hook.
    *
    * As of writing this is being fixed for Organization & Household but it makes sense to do for individuals too.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateAlterSortName() {
+  public function testCreateAlterSortName($version) {
+    $this->_apiversion = $version;
     $organizationID = $this->organizationCreate(['organization_name' => 'The Justice League', 'sort_name' => 'Justice League, The']);
     $organization = $this->callAPISuccessGetSingle('Contact', ['return' => ['sort_name', 'display_name'], 'id' => $organizationID]);
     $this->assertEquals('Justice League, The', $organization['sort_name']);
@@ -2560,27 +2615,32 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test Single Entity format.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetSingleEntityArray() {
-    $this->createContactFromXML();
+  public function testContactGetSingleEntityArray($version) {
+    $this->_apiversion = $version;
+    $contactID = $this->individualCreate(['first_name' => 'Test', 'last_name' => 'Contact']);
     $description = "This demonstrates use of the 'format.single_entity_array' param.
       This param causes the only contact to be returned as an array without the other levels.
       It will be ignored if there is not exactly 1 result";
     $subfile = "GetSingleContact";
-    $params = array('id' => 17);
-    $result = $this->callAPIAndDocument('Contact', 'GetSingle', $params, __FUNCTION__, __FILE__, $description, $subfile);
-    $this->assertEquals('Test Contact', $result['display_name']);
-    $this->callAPISuccess('Contact', 'Delete', $params);
+    $result = $this->callAPIAndDocument('Contact', 'GetSingle', ['id' => $contactID], __FUNCTION__, __FILE__, $description, $subfile);
+    $this->assertEquals('Mr. Test Contact II', $result['display_name']);
+    $this->callAPISuccess('Contact', 'Delete', ['id' => $contactID]);
   }
 
   /**
    * Test Single Entity format.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetFormatCountOnly() {
-    $this->createContactFromXML();
+  public function testContactGetFormatCountOnly($version) {
+    $this->_apiversion = $version;
+    $contactID = $this->individualCreate(['first_name' => 'Test', 'last_name' => 'Contact']);
     $description = "This demonstrates use of the 'getCount' action.
       This param causes the count of the only function to be returned as an integer.";
-    $params = array('id' => 17);
+    $params = ['id' => $contactID];
     $result = $this->callAPIAndDocument('Contact', 'GetCount', $params, __FUNCTION__, __FILE__, $description,
       'GetCountContact');
     $this->assertEquals('1', $result);
@@ -2589,38 +2649,47 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test id only format.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetFormatIDOnly() {
-    $this->createContactFromXML();
+  public function testContactGetFormatIDOnly($version) {
+    $this->_apiversion = $version;
+    $contactID = $this->individualCreate(['first_name' => 'Test', 'last_name' => 'Contact']);
     $description = "This demonstrates use of the 'format.id_only' param.
       This param causes the id of the only entity to be returned as an integer.
       It will be ignored if there is not exactly 1 result";
     $subfile = "FormatOnlyID";
-    $params = array('id' => 17, 'format.only_id' => 1);
+    $params = ['id' => $contactID, 'format.only_id' => 1];
     $result = $this->callAPIAndDocument('Contact', 'Get', $params, __FUNCTION__, __FILE__, $description, $subfile);
-    $this->assertEquals('17', $result);
+    $this->assertEquals($contactID, $result);
     $this->callAPISuccess('Contact', 'Delete', $params);
   }
 
   /**
    * Test id only format.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactGetFormatSingleValue() {
-    $this->createContactFromXML();
+  public function testContactGetFormatSingleValue($version) {
+    $this->_apiversion = $version;
+    $contactID = $this->individualCreate(['first_name' => 'Test', 'last_name' => 'Contact']);
     $description = "This demonstrates use of the 'format.single_value' param.
       This param causes only a single value of the only entity to be returned as an string.
       It will be ignored if there is not exactly 1 result";
     $subFile = "FormatSingleValue";
-    $params = array('id' => 17, 'return' => 'display_name');
+    $params = ['id' => $contactID, 'return' => 'display_name'];
     $result = $this->callAPIAndDocument('Contact', 'getvalue', $params, __FUNCTION__, __FILE__, $description, $subFile);
-    $this->assertEquals('Test Contact', $result);
+    $this->assertEquals('Mr. Test Contact II', $result);
     $this->callAPISuccess('Contact', 'Delete', $params);
   }
 
   /**
    * Test that permissions are respected when creating contacts.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactCreationPermissions() {
+  public function testContactCreationPermissions($version) {
+    $this->_apiversion = $version;
     $params = array(
       'contact_type' => 'Individual',
       'first_name' => 'Foo',
@@ -2630,7 +2699,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
     $config = CRM_Core_Config::singleton();
     $config->userPermissionClass->permissions = array('access CiviCRM');
     $result = $this->callAPIFailure('contact', 'create', $params);
-    $this->assertEquals('API permission check failed for Contact/create call; insufficient permission: require access CiviCRM and add contacts', $result['error_message'], 'lacking permissions should not be enough to create a contact');
+    $this->assertContains('failed', $result['error_message'], 'lacking permissions should not be enough to create a contact');
 
     $config->userPermissionClass->permissions = array('access CiviCRM', 'add contacts', 'import contacts');
     $this->callAPISuccess('contact', 'create', $params);
@@ -2638,6 +2707,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test that delete with skip undelete respects permissions.
+   * TODO: Api4
    */
   public function testContactDeletePermissions() {
     $contactID = $this->individualCreate();
@@ -2656,8 +2726,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test update with check permissions set.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testContactUpdatePermissions() {
+  public function testContactUpdatePermissions($version) {
+    $this->_apiversion = $version;
     $params = array(
       'contact_type' => 'Individual',
       'first_name' => 'Foo',
@@ -2687,19 +2760,6 @@ class api_v3_ContactTest extends CiviUnitTestCase {
     $this->callAPISuccess('contact', 'update', $params);
   }
 
-  /**
-   * Set up helper to create a contact.
-   */
-  public function createContactFromXML() {
-    // Insert a row in civicrm_contact creating contact 17.
-    $op = new PHPUnit_Extensions_Database_Operation_Insert();
-    $op->execute($this->_dbconn,
-      $this->createXMLDataSet(
-        dirname(__FILE__) . '/dataset/contact_17.xml'
-      )
-    );
-  }
-
   /**
    * Test contact proximity api.
    */
@@ -3183,8 +3243,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test the use of sql operators.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testSQLOperatorsOnContactAPI() {
+  public function testSQLOperatorsOnContactAPI($version) {
+    $this->_apiversion = $version;
     $this->individualCreate();
     $this->organizationCreate();
     $this->householdCreate();
@@ -3196,8 +3259,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * CRM-14743 - test api respects search operators.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetModifiedDateByOperators() {
+  public function testGetModifiedDateByOperators($version) {
+    $this->_apiversion = $version;
     $preExistingContactCount = CRM_Core_DAO::singleValueQuery('select count(*) FROM civicrm_contact');
     $contact1 = $this->individualCreate();
     $sql = "UPDATE civicrm_contact SET created_date = '2012-01-01', modified_date = '2013-01-01' WHERE id = " . $contact1;
@@ -3216,8 +3282,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * CRM-14743 - test api respects search operators.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testGetCreatedDateByOperators() {
+  public function testGetCreatedDateByOperators($version) {
+    $this->_apiversion = $version;
     $preExistingContactCount = CRM_Core_DAO::singleValueQuery('select count(*) FROM civicrm_contact');
     $contact1 = $this->individualCreate();
     $sql = "UPDATE civicrm_contact SET created_date = '2012-01-01' WHERE id = " . $contact1;
@@ -3458,6 +3527,57 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   }
 
+  /**
+   * Test merging 2 contacts with custom fields.
+   *
+   * @throws \Exception
+   */
+  public function testMergeCustomFields() {
+    $contact1 = $this->individualCreate();
+    // Not sure this is quite right but it does get it into the file table
+    $file = $this->callAPISuccess('Attachment', 'create', [
+      'name' => 'header.txt',
+      'mime_type' => 'text/plain',
+      'description' => 'My test description',
+      'content' => 'My test content',
+      'entity_table' => 'civicrm_contact',
+      'entity_id' => $contact1,
+    ]);
+
+    $this->createCustomGroupWithFieldsOfAllTypes();
+    $fileField = $this->getCustomFieldName('file');
+    $linkField = $this->getCustomFieldName('link');
+    $dateField = $this->getCustomFieldName('select_date');
+    $selectField = $this->getCustomFieldName('select_string');
+    $countryField = $this->getCustomFieldName('country');
+
+    $countriesByName = array_flip(CRM_Core_PseudoConstant::country(FALSE, FALSE));
+    $customFieldValues = [
+      // @todo fix the fatal bug on this & uncomment - see dev/core#723
+      $fileField => $file['id'],
+      $linkField => 'http://example.org',
+      $dateField => '2018-01-01 17:10:56',
+      $selectField => 'G',
+      // Currently broken.
+      //$countryField => $countriesByName['New Zealand'],
+    ];
+    $this->callAPISuccess('Contact', 'create', array_merge([
+      'id' => $contact1,
+    ], $customFieldValues));
+
+    $contact2 = $this->individualCreate();
+    $this->callAPISuccess('contact', 'merge', [
+      'to_keep_id' => $contact2,
+      'to_remove_id' => $contact1,
+      'auto_flip' => FALSE,
+    ]);
+    $contact = $this->callAPISuccessGetSingle('Contact', ['id' => $contact2, 'return' => array_keys($customFieldValues)]);
+    $this->assertEquals($contact2, CRM_Core_DAO::singleValueQuery('SELECT entity_id FROM civicrm_entity_file WHERE file_id = ' . $file['id']));
+    foreach ($customFieldValues as $key => $value) {
+      $this->assertEquals($value, $contact[$key]);
+    }
+  }
+
   /**
    * Test retrieving merged contacts.
    *
@@ -3796,8 +3916,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * CRM-21041 Test if 'communication style' is set to site default if not passed.
+   * @param int $version
+   * @dataProvider versionThreeAndFour
    */
-  public function testCreateCommunicationStyleUnset() {
+  public function testCreateCommunicationStyleUnset($version) {
+    $this->_apiversion = $version;
     $this->callAPISuccess('Contact', 'create', array(
       'first_name' => 'John',
       'last_name' => 'Doe',
@@ -3831,6 +3954,7 @@ class api_v3_ContactTest extends CiviUnitTestCase {
 
   /**
    * Test that creating a contact with various contact greetings works.
+   * V3 Only.
    */
   public function testContactGreetingsCreate() {
     $contact = $this->callAPISuccess('Contact', 'create', array('first_name' => 'Alan', 'last_name' => 'MouseMouse', 'contact_type' => 'Individual'));