Start to use user_job to track import
authorEileen McNaughton <emcnaughton@wikimedia.org>
Tue, 19 Apr 2022 19:30:53 +0000 (07:30 +1200)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Wed, 20 Apr 2022 06:27:18 +0000 (18:27 +1200)
12 files changed:
CRM/Contact/Import/Form/DataSource.php
CRM/Core/BAO/UserJob.php
CRM/Import/DataSource.php
CRM/Import/DataSource/CSV.php
CRM/Import/DataSource/SQL.php
CRM/Import/Form/DataSourceConfig.php
CRM/Import/Forms.php
CRM/Import/Parser.php
templates/CRM/Contact/Import/Form/DataSource.tpl
tests/phpunit/CRM/Contact/Import/Form/DataSourceTest.php
tests/phpunit/CRM/Contact/Import/Form/data/yogi.csv [new file with mode: 0644]
tests/phpunit/CRM/Import/DataSource/CsvTest.php

index 0377f23fdf756c705f5ea60d719f0a18fe20192a..d4da8d74ed4ba98f4d6f800375c3f0af9ab16b26 100644 (file)
@@ -74,7 +74,7 @@ class CRM_Contact_Import_Form_DataSource extends CRM_Import_Forms {
   public function buildQuickForm() {
 
     $this->assign('urlPath', 'civicrm/import/datasource');
-    $this->assign('urlPathVar', 'snippet=4');
+    $this->assign('urlPathVar', 'snippet=4&user_job_id=' . $this->get('user_job_id'));
 
     $this->add('select', 'dataSource', ts('Data Source'), $this->getDataSources(), TRUE,
       ['onchange' => 'buildDataSourceFormBlock(this.value);']
@@ -168,10 +168,16 @@ class CRM_Contact_Import_Form_DataSource extends CRM_Import_Forms {
    * Call the DataSource's postProcess method.
    *
    * @throws \CRM_Core_Exception
+   * @throws \API_Exception
    */
   public function postProcess() {
     $this->controller->resetPage('MapField');
-
+    if (!$this->getUserJobID()) {
+      $this->createUserJob();
+    }
+    else {
+      $this->updateUserJobMetadata('submitted_values', $this->getSubmittedValues());
+    }
     // Setup the params array
     $this->_params = $this->controller->exportValues($this->_name);
 
@@ -208,6 +214,7 @@ class CRM_Contact_Import_Form_DataSource extends CRM_Import_Forms {
 
     $parser = new CRM_Contact_Import_Parser_Contact($mapper);
     $parser->setMaxLinesToProcess(100);
+    $parser->setUserJobID($this->getUserJobID());
     $parser->run($importTableName,
       $mapper,
       CRM_Import_Parser::MODE_MAPFIELD,
@@ -234,12 +241,12 @@ class CRM_Contact_Import_Form_DataSource extends CRM_Import_Forms {
    * @throws \CRM_Core_Exception
    */
   private function instantiateDataSource(): void {
-    $dataSourceName = $this->getDataSourceClassName();
-    $dataSource = new $dataSourceName();
+    $dataSource = $this->getDataSourceObject();
     // Get the PEAR::DB object
     $dao = new CRM_Core_DAO();
     $db = $dao->getDatabaseConnection();
     $dataSource->postProcess($this->_params, $db, $this);
+    $this->updateUserJobMetadata('DataSource', $dataSource->getDataSourceMetadata());
   }
 
   /**
index 5eaf41979e992fa5a0b9362b5e2dcb1e6b23bce7..193d7186e09bd9378c908621d7d3eaaf9e383e07 100644 (file)
@@ -50,11 +50,16 @@ class CRM_Core_BAO_UserJob extends CRM_Core_DAO_UserJob {
       ],
       [
         'id' => 2,
+        'name' => 'draft',
+        'label' => ts('Draft'),
+      ],
+      [
+        'id' => 3,
         'name' => 'scheduled',
         'label' => ts('Scheduled'),
       ],
       [
-        'id' => 3,
+        'id' => 4,
         'name' => 'in_progress',
         'label' => ts('In Progress'),
       ],
index e7a8cc8459d89e2a0e62ee6c625f3a2f5a2eabe3..c5a4a44cef99744a9dbb47a4371c75c750e8694d 100644 (file)
  * @copyright CiviCRM LLC https://civicrm.org/licensing
  */
 
+use Civi\Api4\UserJob;
+
 /**
  * This class defines the DataSource interface but must be subclassed to be
  * useful.
  */
 abstract class CRM_Import_DataSource {
 
+  /**
+   * Class constructor.
+   *
+   * @param int|null $userJobID
+   */
+  public function __construct(int $userJobID = NULL) {
+    if ($userJobID) {
+      $this->setUserJobID($userJobID);
+    }
+  }
+
+  /**
+   * Form fields declared for this datasource.
+   *
+   * @var string[]
+   */
+  protected $submittableFields = [];
+
+  /**
+   * User job id.
+   *
+   * This is the primary key of the civicrm_user_job table which is used to
+   * track the import.
+   *
+   * @var int
+   */
+  protected $userJobID;
+
+  /**
+   * @return int|null
+   */
+  public function getUserJobID(): ?int {
+    return $this->userJobID;
+  }
+
+  /**
+   * Set user job ID.
+   *
+   * @param int $userJobID
+   */
+  public function setUserJobID(int $userJobID): void {
+    $this->userJobID = $userJobID;
+  }
+
+  /**
+   * User job details.
+   *
+   * This is the relevant row from civicrm_user_job.
+   *
+   * @var array
+   */
+  protected $userJob;
+
+  /**
+   * Get User Job.
+   *
+   * API call to retrieve the userJob row.
+   *
+   * @return array
+   *
+   * @throws \API_Exception
+   */
+  protected function getUserJob(): array {
+    if (!$this->userJob) {
+      $this->userJob = UserJob::get()
+        ->addWhere('id', '=', $this->getUserJobID())
+        ->execute()
+        ->first();
+    }
+    return $this->userJob;
+  }
+
+  /**
+   * Generated metadata relating to the the datasource.
+   *
+   * This is values that are computed within the DataSource class and
+   * which are stored in the userJob metadata in the DataSource key - eg.
+   *
+   * ['table_name' => $]
+   *
+   * Will be in the user_job.metadata field encoded into the json like
+   *
+   * `{'DataSource' : ['table_name' => $], 'submitted_values' : .....}`
+   *
+   * @var array
+   */
+  protected $dataSourceMetadata = [];
+
+  /**
+   * @return array
+   */
+  public function getDataSourceMetadata(): array {
+    return $this->dataSourceMetadata;
+  }
+
+  /**
+   * Get the fields declared for this datasource.
+   *
+   * @return string[]
+   */
+  public function getSubmittableFields(): array {
+    return $this->submittableFields;
+  }
+
   /**
    * Provides information about the data source.
    *
index 9e0c83087763adb5d228be901d423a153a554ba2..c91ca15a8841776a9399aa220e894e8ca9967bff 100644 (file)
@@ -18,6 +18,13 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
   const
     NUM_ROWS_TO_INSERT = 100;
 
+  /**
+   * Form fields declared for this datasource.
+   *
+   * @var string[]
+   */
+  protected $submittableFields = ['skipColumnHeader', 'uploadField'];
+
   /**
    * Provides information about the data source.
    *
@@ -88,8 +95,12 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
       CRM_Utils_Array::value('fieldSeparator', $params, ',')
     );
 
-    $form->set('originalColHeader', CRM_Utils_Array::value('original_col_header', $result));
+    $form->set('originalColHeader', CRM_Utils_Array::value('column_headers', $result));
     $form->set('importTableName', $result['import_table_name']);
+    $this->dataSourceMetadata = [
+      'table_name' => $result['import_table_name'],
+      'column_headers' => $result['column_headers'] ?? NULL,
+    ];
   }
 
   /**
@@ -135,7 +146,7 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
     // create the column names from the CSV header or as col_0, col_1, etc.
     if ($headers) {
       //need to get original headers.
-      $result['original_col_header'] = $firstrow;
+      $result['column_headers'] = $firstrow;
 
       $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
       $columns = array_map($strtolower, $firstrow);
@@ -242,7 +253,6 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
 
     //get the import tmp table name.
     $result['import_table_name'] = $tableName;
-
     return $result;
   }
 
index 633b3dd05a1f11329c67e4da377396187d9b9b3e..2e712f43171e9bfba28256dde44877d03f9e97ba 100644 (file)
  */
 class CRM_Import_DataSource_SQL extends CRM_Import_DataSource {
 
+  /**
+   * Form fields declared for this datasource.
+   *
+   * @var string[]
+   */
+  protected $submittableFields = ['sqlQuery'];
+
   /**
    * Provides information about the data source.
    *
@@ -90,6 +97,9 @@ class CRM_Import_DataSource_SQL extends CRM_Import_DataSource {
     );
 
     $form->set('importTableName', $importJob->getTableName());
+    $this->dataSourceMetadata = [
+      'table_name' => $importJob->getTableName(),
+    ];
   }
 
 }
index 93b34e739f99b9f6aad934972ecf0688f0e85505..e5ddfc870bb85ce1db7069b3648bc0539c36f585 100644 (file)
@@ -29,13 +29,52 @@ class CRM_Import_Form_DataSourceConfig extends CRM_Import_Forms {
     $dataSourcePath = explode('_', $this->getDataSourceClassName());
     $templateFile = 'CRM/Contact/Import/Form/' . $dataSourcePath[3] . '.tpl';
     $this->assign('dataSourceFormTemplateFile', $templateFile ?? NULL);
+    if (CRM_Utils_Request::retrieveValue('user_job_id', 'Integer')) {
+      $this->setUserJobID(CRM_Utils_Request::retrieveValue('user_job_id', 'Integer'));
+    }
   }
 
   /**
    * Build the form object.
+   *
+   * @throws \CRM_Core_Exception
    */
   public function buildQuickForm(): void {
     $this->buildDataSourceFields();
   }
 
+  /**
+   * Set defaults.
+   *
+   * @return array
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   */
+  public function setDefaultValues() {
+    $defaults = [];
+    if ($this->userJobID) {
+      foreach ($this->getDataSourceFields() as $fieldName) {
+        $defaults[$fieldName] = $this->getSubmittedValue($fieldName);
+      }
+    }
+    return $defaults;
+  }
+
+  /**
+   * Get the submitted value, as saved in the user job.
+   *
+   * This form is not in the same flow as the DataSource but
+   * the value we want is saved to the userJob so load it from there.
+   *
+   * @param string $fieldName
+   *
+   * @return mixed|null
+   * @throws \API_Exception
+   */
+  public function getSubmittedValue(string $fieldName) {
+    $userJob = $this->getUserJob();
+    return $userJob['metadata']['submitted_values'][$fieldName];
+  }
+
 }
index b7d15b55d0b487459af3d4fbdcf48c7079027c32..be62a5200e2c343998bfe5d9205da7ec94f19012 100644 (file)
  * @copyright CiviCRM LLC https://civicrm.org/licensing
  */
 
+use Civi\Api4\UserJob;
+
 /**
  * This class helps the forms within the import flow access submitted & parsed values.
  */
 class CRM_Import_Forms extends CRM_Core_Form {
 
   /**
-   * Get the submitted value, accessing it from whatever form in the flow it is submitted on.
+   * User job id.
+   *
+   * This is the primary key of the civicrm_user_job table which is used to
+   * track the import.
+   *
+   * @var int
+   */
+  protected $userJobID;
+
+  /**
+   * @return int|null
+   */
+  public function getUserJobID(): ?int {
+    if (!$this->userJobID && $this->get('user_job_id')) {
+      $this->userJobID = $this->get('user_job_id');
+    }
+    return $this->userJobID;
+  }
+
+  /**
+   * Set user job ID.
+   *
+   * @param int $userJobID
+   */
+  public function setUserJobID(int $userJobID): void {
+    $this->userJobID = $userJobID;
+    // This set allows other forms in the flow ot use $this->get('user_job_id').
+    $this->set('user_job_id', $userJobID);
+  }
+
+  /**
+   * User job details.
+   *
+   * This is the relevant row from civicrm_user_job.
+   *
+   * @var array
+   */
+  protected $userJob;
+
+  /**
+   * Get User Job.
+   *
+   * API call to retrieve the userJob row.
+   *
+   * @return array
+   *
+   * @throws \API_Exception
+   */
+  protected function getUserJob(): array {
+    if (!$this->userJob) {
+      $this->userJob = UserJob::get()
+        ->addWhere('id', '=', $this->getUserJobID())
+        ->execute()
+        ->first();
+    }
+    return $this->userJob;
+  }
+
+  /**
+   * Get submitted values stored in the user job.
+   *
+   * @return array
+   * @throws \API_Exception
+   */
+  protected function getUserJobSubmittedValues(): array {
+    return $this->getUserJob()['metadata']['submitted_values'];
+  }
+
+  /**
+   * Fields that may be submitted on any form in the flow.
+   *
+   * @var string[]
+   */
+  protected $submittableFields = [
+    // Skip column header is actually a field that would be added from the
+    // datasource - but currently only in contact, it is always there for
+    // other imports, ditto uploadFile.
+    'skipColumnHeader' => 'DataSource',
+    'fieldSeparator' => 'DataSource',
+    'uploadFile' => 'DataSource',
+    'contactType' => 'DataSource',
+    'dateFormats' => 'DataSource',
+    'savedMapping' => 'DataSource',
+    'dataSource' => 'DataSource',
+  ];
+
+  /**
+   * Get the submitted value, accessing it from whatever form in the flow it is
+   * submitted on.
+   *
    * @param string $fieldName
    *
    * @return mixed|null
+   * @throws \CRM_Core_Exception
    */
   public function getSubmittedValue(string $fieldName) {
-    $mappedValues = [
-      'skipColumnHeader' => 'DataSource',
-      'fieldSeparator' => 'DataSource',
-      'uploadFile' => 'DataSource',
-      'contactType' => 'DataSource',
-      'dateFormats' => 'DataSource',
-      'savedMapping' => 'DataSource',
-      'dataSource' => 'DataSource',
-    ];
+    if ($fieldName === 'dataSource') {
+      // Hard-coded handling for DataSource as it affects the contents of
+      // getSubmittableFields and can cause a loop.
+      return $this->controller->exportValue('DataSource', 'dataSource');
+    }
+    $mappedValues = $this->getSubmittableFields();
     if (array_key_exists($fieldName, $mappedValues)) {
       return $this->controller->exportValue($mappedValues[$fieldName], $fieldName);
     }
@@ -43,6 +132,19 @@ class CRM_Import_Forms extends CRM_Core_Form {
 
   }
 
+  /**
+   * Get values submitted on any form in the multi-page import flow.
+   *
+   * @return array
+   */
+  public function getSubmittedValues(): array {
+    $values = [];
+    foreach (array_keys($this->getSubmittableFields()) as $key) {
+      $values[$key] = $this->getSubmittedValue($key);
+    }
+    return $values;
+  }
+
   /**
    * Get the available datasource.
    *
@@ -123,11 +225,45 @@ class CRM_Import_Forms extends CRM_Core_Form {
    * @throws \CRM_Core_Exception
    */
   protected function buildDataSourceFields(): void {
+    $dataSourceClass = $this->getDataSourceObject();
+    if ($dataSourceClass) {
+      $dataSourceClass->buildQuickForm($this);
+    }
+  }
+
+  /**
+   * Get the relevant datasource object.
+   *
+   * @return \CRM_Import_DataSource|null
+   *
+   * @throws \CRM_Core_Exception
+   */
+  protected function getDataSourceObject(): ?CRM_Import_DataSource {
+    $className = $this->getDataSourceClassName();
+    if ($className) {
+      /* @var CRM_Import_DataSource $dataSource */
+      return new $className($this->getUserJobID());
+    }
+    return NULL;
+  }
+
+  /**
+   * Allow the datasource class to add fields.
+   *
+   * This is called as a snippet in DataSourceConfig and
+   * also from DataSource::buildForm to add the fields such
+   * that quick form picks them up.
+   *
+   * @throws \CRM_Core_Exception
+   */
+  protected function getDataSourceFields(): array {
     $className = $this->getDataSourceClassName();
     if ($className) {
+      /* @var CRM_Import_DataSource $dataSourceClass */
       $dataSourceClass = new $className();
-      $dataSourceClass->buildQuickForm($this);
+      return $dataSourceClass->getSubmittableFields();
     }
+    return [];
   }
 
   /**
@@ -139,4 +275,62 @@ class CRM_Import_Forms extends CRM_Core_Form {
     return 'CRM_Import_DataSource_CSV';
   }
 
+  /**
+   * Get the fields that can be submitted in the Import form flow.
+   *
+   * These could be on any form in the flow & are accessed the same way from
+   * all forms.
+   *
+   * @return string[]
+   * @throws \CRM_Core_Exception
+   */
+  protected function getSubmittableFields(): array {
+    $dataSourceFields = array_fill_keys($this->getDataSourceFields(), 'DataSource');
+    return array_merge($this->submittableFields, $dataSourceFields);
+  }
+
+  /**
+   * Create a user job to track the import.
+   *
+   * @return int
+   *
+   * @throws \API_Exception
+   */
+  protected function createUserJob(): int {
+    $id = UserJob::create(FALSE)
+      ->setValues([
+        'created_id' => CRM_Core_Session::getLoggedInContactID(),
+        'type_id:name' => 'contact_import',
+        'status_id:name' => 'draft',
+        // This suggests the data could be cleaned up after this.
+        'expires_date' => '+ 1 week',
+        'metadata' => [
+          'submitted_values' => $this->getSubmittedValues(),
+        ],
+      ])
+      ->execute()
+      ->first()['id'];
+    $this->setUserJobID($id);
+    return $id;
+  }
+
+  /**
+   * @param string $key
+   * @param array $data
+   *
+   * @throws \API_Exception
+   * @throws \Civi\API\Exception\UnauthorizedException
+   */
+  protected function updateUserJobMetadata(string $key, array $data): void {
+    $metaData = array_merge(
+      $this->getUserJob()['metadata'],
+      [$key => $data]
+    );
+    UserJob::update(FALSE)
+      ->addWhere('id', '=', $this->getUserJobID())
+      ->setValues(['metadata' => $metaData])
+      ->execute();
+    $this->userJob['metadata'] = $metaData;
+  }
+
 }
index c88dc02f8ac3f4dfe18c7404be6df2595cc0486a..bd74b492b4dc306c710ab99a8b0b374bad8c9e72 100644 (file)
@@ -9,6 +9,8 @@
  +--------------------------------------------------------------------+
  */
 
+use Civi\Api4\UserJob;
+
 /**
  *
  * @package CRM
@@ -41,6 +43,48 @@ abstract class CRM_Import_Parser {
   const CONTACT_INDIVIDUAL = 1, CONTACT_HOUSEHOLD = 2, CONTACT_ORGANIZATION = 4;
 
 
+  /**
+   * User job id.
+   *
+   * This is the primary key of the civicrm_user_job table which is used to
+   * track the import.
+   *
+   * @var int
+   */
+  protected $userJobID;
+
+  /**
+   * @return int|null
+   */
+  public function getUserJobID(): ?int {
+    return $this->userJobID;
+  }
+
+  /**
+   * Set user job ID.
+   *
+   * @param int $userJobID
+   */
+  public function setUserJobID(int $userJobID): void {
+    $this->userJobID = $userJobID;
+  }
+
+  /**
+   * Get User Job.
+   *
+   * API call to retrieve the userJob row.
+   *
+   * @return array
+   *
+   * @throws \API_Exception
+   */
+  protected function getUserJob(): array {
+    return UserJob::get()
+      ->addWhere('id', '=', $this->getUserJobID())
+      ->execute()
+      ->first();
+  }
+
   /**
    * Total number of non empty lines
    * @var int
index deaef9695478dfb52455fc770a07758eff94c33a..58e4d29f85ffc243a6c5311d5de1585202d91c3c 100644 (file)
@@ -98,7 +98,7 @@
 
       function buildDataSourceFormBlock(dataSource)
       {
-        var dataUrl = {/literal}"{crmURL p=$urlPath h=0 q=$urlPathVar}"{literal};
+        var dataUrl = {/literal}"{crmURL p=$urlPath h=0 q=$urlPathVar|smarty:nodefaults}"{literal};
 
         if (!dataSource ) {
           var dataSource = cj("#dataSource").val();
index 68056c7e2d974617cf6850a3fdd8ead4ad9e21de..2d096185abe9059b37e6c008c59627aa443bfaf1 100644 (file)
@@ -14,6 +14,8 @@
  * File for the CRM_Contact_Import_Form_DataSourceTest class.
  */
 
+use Civi\Api4\UserJob;
+
 /**
  *  Test contact import datasource.
  *
  */
 class CRM_Contact_Import_Form_DataSourceTest extends CiviUnitTestCase {
 
+  /**
+   * Post test cleanup.
+   */
+  public function tearDown(): void {
+    $this->quickCleanup(['civicrm_user_job']);
+    parent::tearDown();
+  }
+
   /**
    * Test the form loads without error / notice and mappings are assigned.
    *
    * (Added in conjunction with fixed noting on mapping assignment).
    */
-  public function testBuildForm() {
+  public function testBuildForm(): void {
     $this->callAPISuccess('Mapping', 'create', ['name' => 'Well dressed ducks', 'mapping_type_id' => 'Import Contact']);
     $form = $this->getFormObject('CRM_Contact_Import_Form_DataSource');
     $form->buildQuickForm();
@@ -35,16 +45,89 @@ class CRM_Contact_Import_Form_DataSourceTest extends CiviUnitTestCase {
   }
 
   /**
-   * Check for (lack of) sql errors on sql import post process.
+   * Test sql and csv data-sources load and save user jobs.
+   *
+   * This test mimics a scenario where the form is submitted more than once
+   * and the user_job is updated to reflect the new data source.
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   * @throws \Civi\API\Exception\UnauthorizedException
    */
-  public function testSQLSource() {
+  public function testDataSources(): void {
+    $this->createLoggedInUser();
     $this->callAPISuccess('Mapping', 'create', ['name' => 'Well dressed ducks', 'mapping_type_id' => 'Import Contact']);
-    /** @var CRM_Import_DataSource_SQL $form */
-    $form = $this->getFormObject('CRM_Import_DataSource_SQL', [], 'SQL');
-    $coreForm = $this->getFormObject('CRM_Core_Form');
-    $db = NULL;
-    $params = ['sqlQuery' => 'SELECT 1 as id'];
-    $form->postProcess($params, $db, $coreForm);
+
+    $sqlFormValues = [
+      'dataSource' => 'CRM_Import_DataSource_SQL',
+      'sqlQuery' => 'SELECT "bob" as first_name FROM civicrm_option_value LIMIT 5',
+    ];
+    $form = $this->submitDataSourceForm($sqlFormValues);
+    $userJobID = $form->getUserJobID();
+    // Load the user job, using TRUE so permissions apply.
+    $userJob = UserJob::get(TRUE)
+      ->addWhere('id', '=', $userJobID)
+      ->addSelect('metadata')
+      ->execute()->first();
+    // Submitted values should be stored in the user job.
+    // There are some null values in the submitted_values array - we can
+    // filter these out as we have not passed in all possible values.
+    $this->assertEquals($sqlFormValues, array_filter($userJob['metadata']['submitted_values']));
+
+    // The user job holds the name of the table  - which should have 5 rows of bob.
+    $this->assertNotEmpty($userJob['metadata']['DataSource']['table_name']);
+    $sqlTableName = $userJob['metadata']['DataSource']['table_name'];
+    $this->assertEquals(5, CRM_Core_DAO::singleValueQuery(
+      'SELECT count(*) FROM ' . $sqlTableName
+      . " WHERE first_name = 'Bob'"
+    ));
+
+    // Now we imitate the scenario where the user goes back and
+    // re-submits the form selecting the csv datasource.
+    $csvFormValues = [
+      'dataSource' => 'CRM_Import_DataSource_CSV',
+      'skipColumnHeader' => 1,
+      'uploadFile' => [
+        'name' => __DIR__ . '/data/yogi.csv',
+        'type' => 'text/csv',
+      ],
+    ];
+    // Mimic form re-submission with new values.
+    $_SESSION['_' . $form->controller->_name . '_container']['values']['DataSource'] = $csvFormValues;
+    $form->buildForm();
+    $form->postProcess();
+    // The user job id should not have changed.
+    $this->assertEquals($userJobID, $form->getUserJobID());
+
+    $userJob = UserJob::get(TRUE)
+      ->addWhere('id', '=', $form->getUserJobID())
+      ->addSelect('metadata')
+      ->execute()->first();
+    // Submitted values should be updated in the user job.
+    $this->assertEquals($csvFormValues, array_filter($userJob['metadata']['submitted_values']));
+
+    $csvTableName = $userJob['metadata']['DataSource']['table_name'];
+    $this->assertEquals(1, CRM_Core_DAO::singleValueQuery(
+      'SELECT count(*) FROM ' . $csvTableName
+      . " WHERE first_name = 'yogi'"
+    ));
+  }
+
+  /**
+   * Submit the dataSoure form with the provided form values.
+   *
+   * @param array $sqlFormValues
+   *
+   * @return CRM_Contact_Import_Form_DataSource
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   */
+  private function submitDataSourceForm(array $sqlFormValues): CRM_Contact_Import_Form_DataSource {
+    /** @var CRM_Contact_Import_Form_DataSource $form */
+    $form = $this->getFormObject('CRM_Contact_Import_Form_DataSource', $sqlFormValues);
+    $form->buildForm();
+    $form->postProcess();
+    return $form;
   }
 
 }
diff --git a/tests/phpunit/CRM/Contact/Import/Form/data/yogi.csv b/tests/phpunit/CRM/Contact/Import/Form/data/yogi.csv
new file mode 100644 (file)
index 0000000..a8ccf3f
--- /dev/null
@@ -0,0 +1,2 @@
+Last Name,email,First Name\r
+BearĀ ,yogi@yellowstone.parkĀ ,Yogi\r
index 41626941ee8e343dcede2ca5bfe7254b87e1456b..a549a1bd076d8e36e1186d29b6eee538ff067e34 100644 (file)
  */
 class CRM_Import_DataSource_CsvTest extends CiviUnitTestCase {
 
+  /**
+   * Prepare for tests.
+   */
+  public function setUp(): void {
+    $this->createLoggedInUser();
+    parent::setUp();
+  }
+
   /**
    * Test the to csv function.
    *