[Ref][Import] Start adding testing to validation, simplify
authorEileen McNaughton <emcnaughton@wikimedia.org>
Mon, 9 May 2022 21:14:29 +0000 (09:14 +1200)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Tue, 10 May 2022 00:19:16 +0000 (12:19 +1200)
CRM/Contact/Import/Parser/Contact.php
tests/phpunit/CRM/Contact/Import/Form/data/individual_invalid_missing_name.csv [new file with mode: 0644]
tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php

index a32f53832306eeae1f262748c5493202f1f17223..842e819f83dbc753810e0a791d31d8a2c9e7ef95 100644 (file)
@@ -34,11 +34,6 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
   protected $_relationships;
 
   protected $_emailIndex;
-  protected $_firstNameIndex;
-  protected $_lastNameIndex;
-
-  protected $_householdNameIndex;
-  protected $_organizationNameIndex;
 
   protected $_phoneIndex;
 
@@ -55,7 +50,7 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
   protected $_retCode;
 
   protected $_externalIdentifierIndex;
-  protected $_allExternalIdentifiers;
+  protected $_allExternalIdentifiers = [];
   protected $_parseStreetAddress;
 
   /**
@@ -184,10 +179,6 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
 
     $this->_phoneIndex = -1;
     $this->_emailIndex = -1;
-    $this->_firstNameIndex = -1;
-    $this->_lastNameIndex = -1;
-    $this->_householdNameIndex = -1;
-    $this->_organizationNameIndex = -1;
     $this->_externalIdentifierIndex = -1;
 
     $index = 0;
@@ -198,22 +189,8 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
       if (substr($key, 0, 5) == 'phone') {
         $this->_phoneIndex = $index;
       }
-      if ($key == 'first_name') {
-        $this->_firstNameIndex = $index;
-      }
-      if ($key == 'last_name') {
-        $this->_lastNameIndex = $index;
-      }
-      if ($key == 'household_name') {
-        $this->_householdNameIndex = $index;
-      }
-      if ($key == 'organization_name') {
-        $this->_organizationNameIndex = $index;
-      }
-
       if ($key == 'external_identifier') {
         $this->_externalIdentifierIndex = $index;
-        $this->_allExternalIdentifiers = [];
       }
       $index++;
     }
@@ -353,7 +330,7 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
       return $response;
     }
 
-    $params = &$this->getActiveFieldParams();
+    $params = $this->getMappedRow($values);
     $formatted = [
       'contact_type' => $this->_contactType,
     ];
@@ -2294,68 +2271,14 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
     $dupeCheck = TRUE,
     $dedupeRuleGroupID = NULL) {
 
-    $requiredCheck = TRUE;
-
     if (isset($params['id']) && is_numeric($params['id'])) {
-      $requiredCheck = FALSE;
-    }
-    if ($requiredCheck) {
-      $required = [
-        'Individual' => [
-          ['first_name', 'last_name'],
-          'email',
-        ],
-        'Household' => [
-          'household_name',
-        ],
-        'Organization' => [
-          'organization_name',
-        ],
-      ];
-
-      // contact_type has a limited number of valid values
-      if (empty($params['contact_type'])) {
-        throw new CRM_Core_Exception("No Contact Type");
-      }
-      $fields = $required[$params['contact_type']] ?? NULL;
-      if ($fields == NULL) {
-        throw new CRM_Core_Exception("Invalid Contact Type: {$params['contact_type']}");
-      }
-
+      // @todo - ensure this is tested & remove - expectation is api call further
+      // down validates it.
       if ($csType = CRM_Utils_Array::value('contact_sub_type', $params)) {
         if (!(CRM_Contact_BAO_ContactType::isExtendsContactType($csType, $params['contact_type']))) {
           throw new CRM_Core_Exception("Invalid or Mismatched Contact Subtype: " . implode(', ', (array) $csType));
         }
       }
-
-      if (empty($params['contact_id']) && !empty($params['id'])) {
-        $valid = FALSE;
-        $error = '';
-        foreach ($fields as $field) {
-          if (is_array($field)) {
-            $valid = TRUE;
-            foreach ($field as $element) {
-              if (empty($params[$element])) {
-                $valid = FALSE;
-                $error .= $element;
-                break;
-              }
-            }
-          }
-          else {
-            if (!empty($params[$field])) {
-              $valid = TRUE;
-            }
-          }
-          if ($valid) {
-            break;
-          }
-        }
-
-        if (!$valid) {
-          throw new CRM_Core_Exception("Required fields not found for {$params['contact_type']} : $error");
-        }
-      }
     }
 
     if ($dupeCheck) {
@@ -2595,13 +2518,19 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
   /**
    * Format the field values for input to the api.
    *
+   * @param array $values
+   *   The row from the datasource.
+   *
    * @return array
-   *   (reference ) associative array of name/value pairs
+   *   Parameters mapped as described in getMappedRow
+   *
    * @throws \API_Exception
+   * @todo - clean this up a bit & merge back into `getMappedRow`
+   *
    */
-  public function &getActiveFieldParams() {
+  private function getParams(array $values): array {
     $params = [];
-    $mapper = $this->getSubmittedValue('mapper');
+
     foreach ($this->getFieldMappings() as $i => $mappedField) {
       // The key is in the format 5_a_b where 5 is the relationship_type_id and a_b is the direction.
       $relatedContactKey = $mappedField['relationship_type_id'] ? ($mappedField['relationship_type_id'] . '_' . $mappedField['relationship_direction']) : NULL;
@@ -2622,7 +2551,7 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
       $imProviderID = $relatedContactKey ? NULL : $mappedField['im_provider_id'];
       $websiteTypeID = $relatedContactKey ? NULL : $mappedField['website_type_id'];
 
-      $importedValue = $this->_activeFields[$i]->_value;
+      $importedValue = $values[$i];
 
       if (isset($importedValue)) {
         if (!$relatedContactKey) {
@@ -3339,8 +3268,7 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
    * @throws \API_Exception
    */
   public function getMappedRow(array $values): array {
-    $this->setActiveFieldValues($values);
-    $params = $this->getActiveFieldParams();
+    $params = $this->getParams($values);
     $params['contact_type'] = $this->getContactType();
     return $params;
   }
@@ -3364,42 +3292,40 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
    * The values array represents a row in the datasource.
    *
    * @param array $values
+   *
+   * @throws \API_Exception
    */
   public function validateValues(array $values): void {
     $errorMessage = NULL;
     $errorRequired = FALSE;
     $params = $this->getMappedRow($values);
-    switch ($this->_contactType) {
+    $missingNames = [];
+    switch ($params['contact_type']) {
       case 'Individual':
-        $missingNames = [];
-        if ($this->_firstNameIndex < 0 || empty($values[$this->_firstNameIndex])) {
-          $errorRequired = TRUE;
+        if (empty($params['first_name'])) {
           $missingNames[] = ts('First Name');
         }
-        if ($this->_lastNameIndex < 0 || empty($values[$this->_lastNameIndex])) {
-          $errorRequired = TRUE;
+        if (empty($params['last_name'])) {
           $missingNames[] = ts('Last Name');
         }
-        if ($errorRequired) {
-          $and = ' ' . ts('and') . ' ';
-          $errorMessage = ts('Missing required fields:') . ' ' . implode($and, $missingNames);
-        }
         break;
 
       case 'Household':
-        if ($this->_householdNameIndex < 0 || empty($values[$this->_householdNameIndex])) {
-          $errorRequired = TRUE;
-          $errorMessage = ts('Missing required fields:') . ' ' . ts('Household Name');
+        if (empty($params['household_name'])) {
+          $missingNames[] = ts('Missing required fields:') . ' ' . ts('Household Name');
         }
         break;
 
       case 'Organization':
-        if ($this->_organizationNameIndex < 0 || empty($values[$this->_organizationNameIndex])) {
-          $errorRequired = TRUE;
-          $errorMessage = ts('Missing required fields:') . ' ' . ts('Organization Name');
+        if (empty($params['organization_name'])) {
+          $missingNames[] = ts('Missing required fields:') . ' ' . ts('Organization Name');
         }
         break;
     }
+    if (!empty($missingNames)) {
+      $errorMessage = ts('Missing required fields:') . ' ' . implode(' ' . ts('and') . ' ', $missingNames);
+      $errorRequired = TRUE;
+    }
 
     if ($this->_emailIndex >= 0) {
       /* If we don't have the required fields, bail */
diff --git a/tests/phpunit/CRM/Contact/Import/Form/data/individual_invalid_missing_name.csv b/tests/phpunit/CRM/Contact/Import/Form/data/individual_invalid_missing_name.csv
new file mode 100644 (file)
index 0000000..439f73c
--- /dev/null
@@ -0,0 +1,2 @@
+First Name
+Joseph
index 03e8593d1bf68fcd5484534157eb873a37ffd420..4a8d07cc2b20e115b4243b3d505e6b7ea807a44d 100644 (file)
@@ -77,7 +77,7 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
       NULL,
       NULL,
       'organization_name',
-    ], [], [], [], [], []);
+    ]);
     $parser->setUserJobID($userJobID);
     $parser->_onDuplicate = CRM_Import_Parser::DUPLICATE_UPDATE;
     $parser->init();
@@ -716,6 +716,36 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
     $this->assertEquals([], $errorMessage);
   }
 
+  /**
+   * Test the import validation.
+   *
+   * @dataProvider validateDataProvider
+   *
+   * @throws \API_Exception
+   */
+  public function testValidation($csv, $mapper, $expectedError): void {
+    try {
+      $this->validateCSV($csv, $mapper);
+    }
+    catch (CRM_Core_Exception $e) {
+      $this->assertSame($expectedError, $e->getMessage());
+      return;
+    }
+    if ($expectedError) {
+      $this->fail('expected error :' . $expectedError);
+    }
+  }
+
+  public function validateDataProvider(): array {
+    return [
+      'individual_required' => [
+        'csv' => 'individual_invalid_missing_name.csv',
+        'mapper' => [['last_name']],
+        'expected_error' => 'Missing required fields: First Name OR Email Address',
+      ],
+    ];
+  }
+
   /**
    * Test that setting duplicate action to fill doesn't blow away data
    * that exists, but does fill in where it's empty.
@@ -1084,4 +1114,29 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
     ])->execute()->first()['id'];
   }
 
+  /**
+   * Validate the csv file values.
+   *
+   * @param string $csv Name of csv file.
+   * @param array $mapper Mapping as entered on MapField form.
+   *   e.g [['first_name']['email', 1]].
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   */
+  protected function validateCSV(string $csv, array $mapper): void {
+    $userJobID = $this->getUserJobID([
+      'uploadFile' => ['name' => __DIR__ . '/../Form/data/' . $csv],
+      'skipColumnHeader' => TRUE,
+      'fieldSeparator' => ',',
+      'mapper' => $mapper,
+    ]);
+    $dataSource = new CRM_Import_DataSource_CSV($userJobID);
+    $dataSource->initialize();
+    $parser = new CRM_Contact_Import_Parser_Contact();
+    $parser->setUserJobID($userJobID);
+    $parser->init();
+    $parser->validateValues(array_values($dataSource->getRow()));
+  }
+
 }