From c7f78ce112aa9b40da07e25f0ca1e6a435942a39 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Thu, 15 Sep 2022 17:47:36 +1200 Subject: [PATCH] Fix import to support entity_configuration --- CRM/Contribute/Import/Form/MapField.php | 2 +- CRM/Contribute/Import/Parser/Contribution.php | 52 +++++++++++++++---- CRM/Import/Form/MapField.php | 8 +-- CRM/Import/Parser.php | 38 ++++++++++++-- .../Activity/Import/Parser/ActivityTest.php | 5 +- .../Import/Parser/ContributionTest.php | 48 +++++++++++++---- .../Parser/data/soft_credit_extended.csv | 6 +-- .../CRM/Custom/Import/Parser/ApiTest.php | 2 + .../phpunit/CRMTraits/Import/ParserTrait.php | 10 ++++ 9 files changed, 135 insertions(+), 36 deletions(-) diff --git a/CRM/Contribute/Import/Form/MapField.php b/CRM/Contribute/Import/Form/MapField.php index 543583e334..9d1e069fa8 100644 --- a/CRM/Contribute/Import/Form/MapField.php +++ b/CRM/Contribute/Import/Form/MapField.php @@ -157,7 +157,7 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { $mapperError = []; try { $parser = $self->getParser(); - $rule = $parser->getDedupeRule($self->getContactType()); + $rule = $parser->getDedupeRule($self->getContactType(), $self->getUserJob()['metadata']['entity_configuration']['Contact']['dedupe_rule'] ?? NULL); if (!$self->isUpdateExisting()) { $missingDedupeFields = $self->validateDedupeFieldsSufficientInMapping($rule, $fields['mapper']); if ($missingDedupeFields) { diff --git a/CRM/Contribute/Import/Parser/Contribution.php b/CRM/Contribute/Import/Parser/Contribution.php index f54328ab44..0a4672ee8e 100644 --- a/CRM/Contribute/Import/Parser/Contribution.php +++ b/CRM/Contribute/Import/Parser/Contribution.php @@ -220,8 +220,14 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { // If we move this to the parent we can check if the entity config 'supports_multiple' if ($entity === 'SoftCreditContact') { $entityKey = json_encode($mappedField['entity_data']); - $entityInstance = $params[$entity][$entityKey] ?? $mappedField['entity_data']['soft_credit']; - $entityInstance['Contact'] = array_merge($entityInstance['Contact'] ?? [], [$this->getFieldMetadata($mappedField['name'])['name'] => $this->getTransformedFieldValue($mappedField['name'], $fieldValue)]); + if (isset($params[$entity][$entityKey])) { + $entityInstance = $params[$entity][$entityKey]; + } + else { + $entityInstance = $mappedField['entity_data']['soft_credit']; + $entityInstance['Contact']['contact_type'] = $this->getContactTypeForEntity($entity); + } + $entityInstance['Contact'] = array_merge($entityInstance['Contact'], [$this->getFieldMetadata($mappedField['name'])['name'] => $this->getTransformedFieldValue($mappedField['name'], $fieldValue)]); $params[$entity][$entityKey] = $entityInstance; } else { @@ -368,7 +374,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { 'unique_fields' => ['external_identifier', 'id'], 'is_contact' => TRUE, 'supports_multiple' => FALSE, - 'actions' => $this->isUpdateExisting() ? $this->getActions(['ignore', 'update']) : $this->getActions(['select', 'update', 'update_or_create']), + 'actions' => $this->isUpdateExisting() ? $this->getActions(['ignore', 'update']) : $this->getActions(['select', 'update', 'save']), 'selected' => [ 'action' => $this->isUpdateExisting() ? 'ignore' : 'select', 'contact_type' => $this->getSubmittedValue('contactType'), @@ -385,7 +391,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { 'unique_fields' => ['external_identifier', 'id'], 'is_contact' => TRUE, 'is_required' => FALSE, - 'actions' => array_merge([['id' => 'ignore', 'text' => ts('Do not import')]], $this->getActions(['select', 'update', 'update_or_create'])), + 'actions' => array_merge([['id' => 'ignore', 'text' => ts('Do not import')]], $this->getActions(['select', 'update', 'save'])), 'selected' => ['contact_type' => '', 'soft_credit_type_id' => '', 'action' => 'ignore'], 'default_action' => 'ignore', 'entity_name' => 'SoftCreditContact', @@ -426,13 +432,13 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { if (empty($contributionParams['id']) && $this->isUpdateExisting()) { throw new CRM_Core_Exception('Empty Contribution and Invoice and Transaction ID. Row was skipped.', CRM_Import_Parser::ERROR); } - $contactID = $contributionParams['contact_id'] = $this->getContactID($params['Contact'] ?? [], $contributionParams['contact_id'] ?? ($existingContribution['contact_id'] ?? NULL), 'Contact'); + $contributionParams['contact_id'] = $this->getContactID($params['Contact'] ?? [], $contributionParams['contact_id'] ?? ($existingContribution['contact_id'] ?? NULL), 'Contact', $this->getDedupeRulesForEntity('Contact')); $softCreditParams = []; foreach ($params['SoftCreditContact'] ?? [] as $index => $softCreditContact) { $softCreditParams[$index]['soft_credit_type_id'] = $softCreditContact['soft_credit_type_id']; - $softCreditParams[$index]['contact_id'] = $this->getContactID($softCreditContact['Contact'], $softCreditContact['id'] ?? NULL, 'SoftCreditContact'); - if (empty($softCreditParams[$index]['contact_id'])) { + $softCreditParams[$index]['contact_id'] = $this->getContactID($softCreditContact['Contact'], $softCreditContact['id'] ?? NULL, 'SoftCreditContact', $this->getDedupeRulesForEntity('SoftCreditContact')); + if (empty($softCreditParams[$index]['contact_id']) && in_array($this->getActionForEntity('SoftCreditContact'), ['update', 'select'])) { throw new CRM_Core_Exception(ts('Soft Credit Contact not found')); } } @@ -441,6 +447,12 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { // From this point on we are changing stuff - the prior rows were doing lookups and exiting // if the lookups failed. + + foreach ($params['SoftCreditContact'] ?? [] as $index => $softCreditContact) { + $softCreditParams[$index]['contact_id'] = $this->saveContact('SoftCreditContact', $softCreditContact['Contact']) ?: $softCreditParams[$index]['contact_id']; + } + $contributionParams['contact_id'] = $this->saveContact('Contact', $params['Contact'] ?? []) ?: $contributionParams['contact_id']; + if (isset($params['SoftCreditContact']) && $this->isUpdateExisting()) { //need to check existing soft credit contribution, CRM-3968 $this->deleteExistingSoftCredit($contributionParams['id']); @@ -460,7 +472,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { } } if (!empty($params['Note'])) { - $this->processNote($contributionID, $contactID, $params['Note']); + $this->processNote($contributionID, $contributionParams['contact_id'], $params['Note']); } //return soft valid since we need to show how soft credits were added // because ? historically we did but this seems a bit obsolete. @@ -676,6 +688,26 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { } } + /** + * Save the contact. + * + * @param string $entity + * @param array $contact + * + * @return int|null + * + * @throws \Civi\API\Exception\UnauthorizedException|\CRM_Core_Exception + */ + protected function saveContact(string $entity, array $contact): ?int { + if (in_array($this->getActionForEntity($entity), ['update', 'save', 'create'])) { + return Contact::save() + ->setRecords([$contact]) + ->execute() + ->first()['id']; + } + return NULL; + } + /** * Lookup matching contact. * @@ -819,8 +851,8 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { 'text' => ts('Update existing Contact.'), 'description' => ts('Update existing Contact. Skip row if not found'), ], - 'update_or_create' => [ - 'id' => 'update_or_create', + 'save' => [ + 'id' => 'save', 'text' => ts('Update existing Contact or Create'), 'description' => ts('Create new contact if not found'), ], diff --git a/CRM/Import/Form/MapField.php b/CRM/Import/Form/MapField.php index ababc7c455..d3b9c36c67 100644 --- a/CRM/Import/Form/MapField.php +++ b/CRM/Import/Form/MapField.php @@ -88,7 +88,7 @@ abstract class CRM_Import_Form_MapField extends CRM_Import_Forms { * @noinspection PhpUnhandledExceptionInspection */ public function postProcess() { - $this->updateUserJobMetadata('import_configuration', $this->getImportConfiguration()); + $this->updateUserJobMetadata('import_mappings', $this->getImportConfiguration()); $this->updateUserJobMetadata('submitted_values', $this->getSubmittedValues()); $this->saveMapping(); $parser = $this->getParser(); @@ -487,7 +487,7 @@ abstract class CRM_Import_Form_MapField extends CRM_Import_Forms { if (in_array($fieldName, $highlightedFields, TRUE)) { $childField['text'] .= '*'; } - $category = ($childField['has_location'] || $field['name'] === 'contact_id') ? 'Contact' : ($field['entity'] ?? $entity); + $category = ($childField['has_location'] || $field['name'] === 'contact_id') ? 'Contact' : $field['entity_instance'] ?? ($field['entity'] ?? $entity); if (empty($categories[$category])) { $category = $entity; } @@ -533,9 +533,9 @@ abstract class CRM_Import_Form_MapField extends CRM_Import_Forms { * @return array */ protected function getImportConfiguration(): array { - $importConfiguration = []; + $importConfiguration = $this->getUserJob()['metadata']['import_mappings'] ?? []; foreach ($this->getSubmittedValue('mapper') as $mapperField) { - $importConfiguration[] = $mapperField; + $importConfiguration['name'] = $mapperField; } return $importConfiguration; } diff --git a/CRM/Import/Parser.php b/CRM/Import/Parser.php index c76c845211..18aa62af1a 100644 --- a/CRM/Import/Parser.php +++ b/CRM/Import/Parser.php @@ -1427,6 +1427,32 @@ abstract class CRM_Import_Parser implements UserJobInterface { return $this->getUserJob()['metadata']['entity_configuration'][$entity]['action'] ?? ($this->getImportEntities()[$entity]['default_action'] ?? 'select'); } + /** + * Get the dedupe rule/s to use for the given entity. + * + * If none are returned then the code will use a default 'Unsupervised' rule in `getContactID` + * + * @param string $entity + * + * @return array + * @throws \API_Exception + */ + protected function getDedupeRulesForEntity(string $entity): array { + return (array) ($this->getUserJob()['metadata']['entity_configuration'][$entity]['dedupe_rule'] ?? []); + } + + /** + * Get the import action for the given entity. + * + * @param string $entity + * + * @return string|null + * @throws \API_Exception + */ + protected function getContactTypeForEntity(string $entity): ?string { + return $this->getUserJob()['metadata']['entity_configuration'][$entity]['contact_type'] ?? NULL; + } + /** * @param string $entity * @param string $action @@ -2206,7 +2232,7 @@ abstract class CRM_Import_Parser implements UserJobInterface { * Get contacts that match the input parameters, using a dedupe rule. * * @param array $params - * @param int|null $dedupeRuleID + * @param int|null|array $dedupeRuleID * @param bool $isApiMetadata * Is the import using api4 style metadata (in which case no conversion needed) - eventually * only contact import will use a different style (as it supports multiple locations) and the @@ -2290,12 +2316,14 @@ abstract class CRM_Import_Parser implements UserJobInterface { * @param int|null $contactID * @param string $entity * Entity, as described in getImportEntities. + * @param array|null $dedupeRules + * Dedupe rules to apply (will default to unsupervised rule) * * @return int|null * * @throws \CRM_Core_Exception */ - protected function getContactID(array $contactParams, ?int $contactID, string $entity): ?int { + protected function getContactID(array $contactParams, ?int $contactID, string $entity, ?array $dedupeRules = NULL): ?int { $contactType = $contactParams['contact_type'] ?? NULL; if ($contactID) { $this->validateContactID($contactID, $contactType); @@ -2305,14 +2333,14 @@ abstract class CRM_Import_Parser implements UserJobInterface { } if (!$contactID) { $action = $this->getActionForEntity($entity); - $possibleMatches = $this->getPossibleMatchesByDedupeRule($contactParams); + $possibleMatches = $this->getPossibleMatchesByDedupeRule($contactParams, $dedupeRules); if (count($possibleMatches) === 1) { $contactID = array_key_first($possibleMatches); } elseif (count($possibleMatches) > 1) { throw new CRM_Core_Exception(ts('Record duplicates multiple contacts: ') . implode(',', $possibleMatches)); } - elseif (!in_array($action, ['create', 'ignore', 'update_or_create'], TRUE)) { + elseif (!in_array($action, ['create', 'ignore', 'save'], TRUE)) { throw new CRM_Core_Exception(ts('No matching %1 found', [$entity, 'String'])); } } @@ -2512,7 +2540,7 @@ abstract class CRM_Import_Parser implements UserJobInterface { $dedupeRules = []; if (!empty($dedupeRuleIDs)) { foreach ($dedupeRuleIDs as $dedupeRuleID) { - $dedupeRules[] = $this->getDedupeRuleName($dedupeRuleID); + $dedupeRules[] = is_numeric($dedupeRuleID) ? $this->getDedupeRuleName($dedupeRuleID) : $dedupeRuleID; } return $dedupeRules; } diff --git a/tests/phpunit/CRM/Activity/Import/Parser/ActivityTest.php b/tests/phpunit/CRM/Activity/Import/Parser/ActivityTest.php index d036e46685..f37404e5b9 100644 --- a/tests/phpunit/CRM/Activity/Import/Parser/ActivityTest.php +++ b/tests/phpunit/CRM/Activity/Import/Parser/ActivityTest.php @@ -41,8 +41,6 @@ class CRM_Activity_Import_Parser_ActivityTest extends CiviUnitTestCase { /** * Clean up after test. - * - * @throws \CRM_Core_Exception */ public function tearDown():void { $this->quickCleanup(['civicrm_contact', 'civicrm_email', 'civicrm_activity', 'civicrm_activity_contact', 'civicrm_user_job', 'civicrm_queue', 'civicrm_queue_item'], TRUE); @@ -93,9 +91,10 @@ class CRM_Activity_Import_Parser_ActivityTest extends CiviUnitTestCase { * * @param array $values * @param int $expectedOutcome + * * @return string The error message */ - protected function importValues(array $values, $expectedOutcome = 1): string { + protected function importValues(array $values, int $expectedOutcome = 1): string { $importer = $this->createImportObject(array_keys($values)); try { $importer->validateValues(array_values($values)); diff --git a/tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php b/tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php index 569cf86086..ec34c7d063 100644 --- a/tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php +++ b/tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php @@ -203,6 +203,7 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { public function testImportFromUserJobConfiguration(): void { $importMappings = [ ['name' => 'organization_name'], + ['name' => 'legal_name'], ['name' => 'total_amount'], // Note that default_value is supported via the parser and the angular form // but there is no way to enter it on the quick form. @@ -225,6 +226,24 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { $this->submitDataSourceForm('soft_credit_extended.csv', $submittedValues); $metadata = UserJob::get()->addWhere('id', '=', $this->userJobID)->addSelect('metadata')->execute()->first()['metadata']; $metadata['import mappings'] = $importMappings; + $metadata['entity_configuration'] = [ + 'Contribution' => ['action' => 'create'], + 'Contact' => [ + 'action' => 'create', + 'contact_type' => 'Organization', + 'dedupe_rule' => 'OrganizationUnsupervised', + ], + 'SoftCreditContact' => [ + 'contact_type' => 'Individual', + 'action' => 'create', + 'dedupe_rule' => 'IndividualSupervised', + 'entity_data' => [ + 'soft_credit' => [ + 'soft_credit_type_id' => 1, + ], + ], + ], + ]; UserJob::update()->addWhere('id', '=', $this->userJobID) ->setValues(['metadata' => $metadata])->execute(); $form = $this->getMapFieldForm($submittedValues); @@ -235,6 +254,11 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { $row = $this->getDataSource()->getRow(); // a valid status here means it has been able to incorporate the default_value. $this->assertEquals('VALID', $row['_status']); + + $this->submitPreviewForm($submittedValues); + $row = $this->getDataSource()->getRow(); + // a valid status here means it has been able to incorporate the default_value. + $this->assertEquals('soft_credit_imported', $row['_status']); } /** @@ -276,6 +300,8 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { /** * Test importing to a pledge. + * + * @throws \CRM_Core_Exception */ public function testPledgeImport(): void { $contactID = $this->individualCreate(['email' => 'mum@example.com']); @@ -347,7 +373,7 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { $customField = $this->getCustomFieldName('checkbox'); $contactID = $this->individualCreate(); $values = ['contribution_contact_id' => $contactID, 'total_amount' => 10, 'financial_type_id' => 'Donation', $customField => 'L,V']; - $this->runImport($values, CRM_Import_Parser::DUPLICATE_SKIP, NULL); + $this->runImport($values, CRM_Import_Parser::DUPLICATE_SKIP); $initialContribution = $this->callAPISuccessGetSingle('Contribution', ['contact_id' => $contactID]); $this->assertContains('L', $initialContribution[$customField], 'Contribution Duplicate Skip Import contains L'); $this->assertContains('V', $initialContribution[$customField], 'Contribution Duplicate Skip Import contains V'); @@ -355,7 +381,7 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { // Now update. $values['contribution_id'] = $initialContribution['id']; $values[$customField] = 'V'; - $this->runImport($values, CRM_Import_Parser::DUPLICATE_UPDATE, NULL); + $this->runImport($values, CRM_Import_Parser::DUPLICATE_UPDATE); $updatedContribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $initialContribution['id']]); $this->assertNotContains('L', $updatedContribution[$customField], 'Contribution Duplicate Update Import does not contain L'); @@ -489,7 +515,7 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { ]; // First we try to create without total_amount mapped. // It will fail in create mode as total_amount is required for create. - $this->submitDataSourceForm('contributions.csv', $fieldMappings, ['onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP]); + $this->submitDataSourceForm('contributions.csv', $fieldMappings); $form = $this->getMapFieldForm([ 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP, 'mapper' => $this->getMapperFromFieldMappings($fieldMappings), @@ -580,14 +606,13 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { * @param array $originalValues * * @param int $onDuplicateAction - * @param int|null $expectedResult * @param array|null $mappings * @param array|null $fields * Array of field names. Will be calculated from $originalValues if not passed in. * * @throws \CRM_Core_Exception */ - protected function runImport(array $originalValues, int $onDuplicateAction, ?int $expectedResult = NULL, array $mappings = [], array $fields = NULL): void { + protected function runImport(array $originalValues, int $onDuplicateAction, array $mappings = [], array $fields = NULL): void { if (!$fields) { $fields = array_keys($originalValues); } @@ -595,12 +620,13 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { $mapper = $this->getMapperFromFieldMappings($mappings); } else { + $mapper = []; foreach ($fields as $field) { $mapper[] = [$field]; } } $values = array_values($originalValues); - $parser = new CRM_Contribute_Import_Parser_Contribution($fields); + $parser = new CRM_Contribute_Import_Parser_Contribution(); $parser->setUserJobID($this->getUserJobID([ 'onDuplicate' => $onDuplicateAction, 'mapper' => $mapper, @@ -700,10 +726,10 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { * * @param array $submittedValues * - * @return \CRM_Contribute_Import_Form_DataSource|\CRM_Core_Form|\CRM_Custom_Import_Form_DataSource - * @noinspection PhpUnnecessaryLocalVariableInspection + * @return \CRM_Contribute_Import_Form_DataSource + * @noinspection PhpIncompatibleReturnTypeInspection */ - protected function getDataSourceForm(array $submittedValues) { + protected function getDataSourceForm(array $submittedValues): CRM_Contribute_Import_Form_DataSource { return $this->getFormObject('CRM_Contribute_Import_Form_DataSource', $submittedValues); } @@ -740,9 +766,11 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { } /** + * @param array $submittedValues + * * @return \CRM_Import_DataSource_CSV */ - private function importContributionsDotCSV($submittedValues = []): CRM_Import_DataSource_CSV { + private function importContributionsDotCSV(array $submittedValues = []): CRM_Import_DataSource_CSV { $this->importCSV('contributions.csv', [ ['name' => 'first_name'], ['name' => 'total_amount'], diff --git a/tests/phpunit/CRM/Contribute/Import/Parser/data/soft_credit_extended.csv b/tests/phpunit/CRM/Contribute/Import/Parser/data/soft_credit_extended.csv index 8a6cd8e78a..ac9fca6815 100644 --- a/tests/phpunit/CRM/Contribute/Import/Parser/data/soft_credit_extended.csv +++ b/tests/phpunit/CRM/Contribute/Import/Parser/data/soft_credit_extended.csv @@ -1,3 +1,3 @@ -Organization Name,Amount,Financial Type,Source,Date,Soft credi contact email,Soft credit first name,Soft Credit last name -Big Firm,800,,Import,2022-09-08,jenny@example.org,Jenny,Hawthorn -Small Firm,70,Check,Import,2022-09-08,sarah@example.org,Sarah,Windsor +Organization Name,Legal Name,Amount,Financial Type,Source,Date,Soft credi contact email,Soft credit first name,Soft Credit last name +Big Firm,Big Firm inc.,800,,Import,2022-09-08,jenny@example.org,Jenny,Hawthorn +Small Firm,Small Firm inc.,70,Check,Import,2022-09-08,sarah@example.org,Sarah,Windsor diff --git a/tests/phpunit/CRM/Custom/Import/Parser/ApiTest.php b/tests/phpunit/CRM/Custom/Import/Parser/ApiTest.php index 644d33f82a..ec00b554ae 100644 --- a/tests/phpunit/CRM/Custom/Import/Parser/ApiTest.php +++ b/tests/phpunit/CRM/Custom/Import/Parser/ApiTest.php @@ -18,6 +18,8 @@ class CRM_Custom_Import_Parser_ApiTest extends CiviUnitTestCase { /** * Test the full form-flow import. + * + * @throws \CRM_Core_Exception */ public function testImport(): void { $this->individualCreate(); diff --git a/tests/phpunit/CRMTraits/Import/ParserTrait.php b/tests/phpunit/CRMTraits/Import/ParserTrait.php index 14ffcc450d..bc199e6e0f 100644 --- a/tests/phpunit/CRMTraits/Import/ParserTrait.php +++ b/tests/phpunit/CRMTraits/Import/ParserTrait.php @@ -49,10 +49,20 @@ trait CRMTraits_Import_ParserTrait { $form->buildForm(); $this->assertTrue($form->validate()); $form->postProcess(); + $this->submitPreviewForm($submittedValues); + } + + /** + * Submit the preview form, triggering the import. + * + * @param array $submittedValues + */ + protected function submitPreviewForm(array $submittedValues): void { $form = $this->getPreviewForm($submittedValues); $form->setUserJobID($this->userJobID); $form->buildForm(); $this->assertTrue($form->validate()); + try { $form->postProcess(); $this->fail('Expected a redirect'); -- 2.25.1