diff --git a/modules/salesforce_mapping/salesforce_mapping.module b/modules/salesforce_mapping/salesforce_mapping.module index 0aea9ced238387671f11a56b6e9785635ae803ea..d6f5b4216f2204bf27f12e39f260c993816bb7c1 100644 --- a/modules/salesforce_mapping/salesforce_mapping.module +++ b/modules/salesforce_mapping/salesforce_mapping.module @@ -43,7 +43,36 @@ function salesforce_mapping_entity_update(EntityInterface $entity) { function salesforce_mapping_entity_delete(EntityInterface $entity) { $storage = \Drupal::entityTypeManager() ->getStorage('salesforce_mapped_object'); - $storage->delete($storage->loadByEntity($entity)); + + if (\Drupal::moduleHandler()->moduleExists('salesforce_push') && + \Drupal::database()->schema()->tableExists('salesforce_push_queue')) { + $mapped_objects = $storage->loadByEntity($entity); + + if (!$mapped_objects) { + return; + } + + foreach ($mapped_objects as $mapped_object) { + $mapping = $mapped_object->getMapping(); + $queued_items = \Drupal::database() + ->select('salesforce_push_queue', 'spq') + ->fields('spq', ['item_id']) + ->condition('entity_id', $entity->id()) + ->condition('name', $mapping->id()) + ->condition('op', 'push_delete') + ->execute(); + + // If this was queued to be deleted, don't delete the mapping yet, defer + // to salesforce_push. + if (!$queued_items->fetch()) { + $mapped_object->delete(); + } + } + } + else { + $storage->delete($storage->loadByEntity($entity)); + } + } /** @@ -53,3 +82,18 @@ function salesforce_mapping_field_formatter_info_alter(array &$info) { $info['link']['field_types'][] = 'mapped_entity_link'; $info['link']['field_types'][] = 'salesforce_link'; } + +/** + * Implements hook_module_implements_alter(). + */ +function salesforce_mapping_module_implements_alter(&$implementations, $hook) { + // This needs to go last to make sure the mapping isn't deleted before + // everything is done being cleaned up. + switch ($hook) { + case 'entity_delete': + $group = $implementations['salesforce_mapping']; + unset($implementations['salesforce_mapping']); + $implementations['salesforce_mapping'] = $group; + break; + } +} diff --git a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/salesforce_mapping.salesforce_mapping.test_mapping.yml b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/salesforce_mapping.salesforce_mapping.test_mapping.yml index 757b81a4c4b5344f2b79b1ffce6793ad79c47a4e..9bd338e040efce610292a102997801150e7964be 100644 --- a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/salesforce_mapping.salesforce_mapping.test_mapping.yml +++ b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/salesforce_mapping.salesforce_mapping.test_mapping.yml @@ -25,10 +25,10 @@ pull_where_clause: '' sync_triggers: push_create: true push_update: true - push_delete: false + push_delete: true pull_create: true pull_update: true - pull_delete: false + pull_delete: true salesforce_object_type: Contact drupal_entity_type: node drupal_bundle: salesforce_mapping_test_content diff --git a/modules/salesforce_push/src/Plugin/SalesforcePushQueueProcessor/Rest.php b/modules/salesforce_push/src/Plugin/SalesforcePushQueueProcessor/Rest.php index 212527b1b80dfb4a5bb3933a6cea3c9865d0497e..ce863b125d7ad84467be71ebe48f7196c48db3f6 100644 --- a/modules/salesforce_push/src/Plugin/SalesforcePushQueueProcessor/Rest.php +++ b/modules/salesforce_push/src/Plugin/SalesforcePushQueueProcessor/Rest.php @@ -160,6 +160,9 @@ class Rest extends PluginBase implements PushQueueProcessorInterface { // If this is a delete, destroy the SF object and we're done. if ($item->op == MappingConstants::SALESFORCE_MAPPING_SYNC_DRUPAL_DELETE) { $mapped_object->pushDelete(); + // This has to be cleaned up here because we need the object to process + // Async. + $mapped_object->delete(); } else { $entity = $this->etm diff --git a/modules/salesforce_push/tests/src/Functional/PushQueueTest.php b/modules/salesforce_push/tests/src/Functional/PushQueueTest.php index 9e9096898dacecd3620a8e02ee28b7b0f13ec2ed..e100f01522cefaafed8a5a3aab76dca690271942 100644 --- a/modules/salesforce_push/tests/src/Functional/PushQueueTest.php +++ b/modules/salesforce_push/tests/src/Functional/PushQueueTest.php @@ -3,6 +3,8 @@ namespace Drupal\Tests\salesforce_push\Functional; use Drupal\node\Entity\Node; +use Drupal\salesforce_mapping\Entity\MappedObject; +use Drupal\salesforce_mapping\Entity\SalesforceMapping; use Drupal\Tests\BrowserTestBase; /** @@ -15,27 +17,98 @@ class PushQueueTest extends BrowserTestBase { public static $modules = ['typed_data', 'dynamic_entity_reference', 'salesforce_mapping', 'salesforce_mapping_test', 'salesforce_push']; /** - * Test that saving mapped nodes enqueues them for push to Salesforce. + * Test queue features. + * + * Test creation of queue items. + * Test mocked push queue create and creation of mapped objects. + * Test mocked push queue update and update of mapped objects. + * Test deletion of entities and corresponding deletion of SF related records. */ - public function testEnqueue() { + public function testQueue() { /** @var \Drupal\salesforce_push\PushQueue $queue */ $queue = \Drupal::service('queue.salesforce_push'); + $mapping = SalesforceMapping::load('test_mapping'); + // Set mapping to async, otherwise the push will be attempted immediately. + $mapping->set('async', TRUE); + $mapping->save(); + + /** @var \Drupal\salesforce_mapping\MappedObjectStorage $mappedObjectStorage */ + $mappedObjectStorage = \Drupal::entityTypeManager()->getStorage('salesforce_mapped_object'); $this->assertEquals(0, $queue->numberOfItems()); - Node::create([ + $node1 = Node::create([ 'type' => 'salesforce_mapping_test_content', 'title' => 'Test Example', ] - )->save(); + ); + $node1->save(); $this->assertEquals(1, $queue->numberOfItems()); - Node::create([ + $node2 = Node::create([ 'type' => 'salesforce_mapping_test_content', 'title' => 'Test Example 2', ] - )->save(); + ); + $node2->save(); + $this->assertEquals(2, $queue->numberOfItems()); + + // Two items are queued. Run the queue and ensure that mapped objects are + // created from our mocked results. + $queue->processQueue($mapping); + $this->assertEquals(0, $queue->numberOfItems()); + + $mappedObject1 = $mappedObjectStorage->loadByEntityAndMapping($node1, $mapping); + $this->assertNotNull($mappedObject1); + $mappedObject1Vid = $mappedObject1->getRevisionId(); + + $mappedObject2 = $mappedObjectStorage->loadByEntityAndMapping($node2, $mapping); + $this->assertNotNull($mappedObject2); + $mappedObject2Vid = $mappedObject2->getRevisionId(); + + // Update those nodes and test that they get re-queued. + $node1->setTitle('Updated Title')->save(); + $this->assertEquals(1, $queue->numberOfItems()); + + $node2->setTitle('Updated Title 2')->save(); + $this->assertEquals(2, $queue->numberOfItems()); + + // Run the queue again and test that mapped objects get updated. + $queue->processQueue($mapping); + $this->assertEquals(0, $queue->numberOfItems()); + + // Make sure a new revision was created, implying the update. + $mappedObjectStorage->resetCache([$mappedObject1->id(), $mappedObject2->id()]); + + $mappedObject1 = $mappedObjectStorage->loadByEntityAndMapping($node1, $mapping); + $mappedObject1UpdatedVid = $mappedObject1->getRevisionId(); + $this->assertNotEquals($mappedObject1Vid, $mappedObject1UpdatedVid); + + $mappedObject2 = $mappedObjectStorage->loadByEntityAndMapping($node2, $mapping); + $mappedObject2UpdatedVid = $mappedObject2->getRevisionId(); + $this->assertNotEquals($mappedObject2Vid, $mappedObject2UpdatedVid); + + // Delete nodes and test that corresponding records are deleted. + $node1->delete(); + $node2->delete(); + + // Make sure the mapped object is not deleted yet. + $mappedObjectStorage->resetCache([$mappedObject1->id(), $mappedObject2->id()]); + $mappedObject1 = $mappedObjectStorage->load($mappedObject1->id()); + $this->assertNotNull($mappedObject1); + $mappedObject2 = $mappedObjectStorage->load($mappedObject2->id()); + $this->assertNotNull($mappedObject2); + + // Make sure the delete queue items were created. $this->assertEquals(2, $queue->numberOfItems()); + // Process the queue to make sure the deletes get sent. + $queue->processQueue($mapping); + $this->assertEquals(0, $queue->numberOfItems()); + + // Finally, make sure the mapped objects have been deleted. + $mappedObjectStorage->resetCache([$mappedObject1->id(), $mappedObject2->id()]); + $this->assertNull($mappedObjectStorage->load($mappedObject1->id())); + $this->assertNull($mappedObjectStorage->load($mappedObject2->id())); } } \ No newline at end of file diff --git a/src/Tests/TestRestClient.php b/src/Tests/TestRestClient.php index f67922e0f127b00bdb50a7e0fa81159c806df5f9..9ca618cef68043c4c942482f93ab28cbe22de01a 100644 --- a/src/Tests/TestRestClient.php +++ b/src/Tests/TestRestClient.php @@ -2,10 +2,12 @@ namespace Drupal\salesforce\Tests; +use Drupal\Component\Utility\Random; use Drupal\salesforce\Rest\RestResponse; use Drupal\salesforce\Rest\RestResponseDescribe; use Drupal\salesforce\SelectQueryInterface; use Drupal\salesforce\SelectQueryResult; +use Drupal\salesforce\SFID; use Drupal\salesforce\SObject; use GuzzleHttp\Psr7\Response; use Drupal\salesforce\Rest\RestClient; @@ -119,4 +121,37 @@ class TestRestClient extends RestClient { return (object) ['resources' => []]; } + /** + * @inheritDoc + */ + public function objectCreate($name, array $params) { + $random = new Random(); + return new SFID(strtoupper($random->string(SFID::MAX_LENGTH))); + } + + /** + * @inheritDoc + */ + public function objectUpsert($name, $key, $value, array $params) { + $random = new Random(); + return new SFID(strtoupper($random->string(SFID::MAX_LENGTH))); + } + + /** + * @inheritDoc + */ + public function objectUpdate($name, $id, array $params) { + // Object update does... NOTHING! + return NULL; + } + + /** + * @inheritDoc + */ + public function objectDelete($name, $id, $throw_exception = FALSE) { + // Object delete does... NOTHING! + return NULL; + } + + } diff --git a/src/Tests/TestSalesforceAuthProviderPluginManager.php b/src/Tests/TestSalesforceAuthProviderPluginManager.php index 83a5dea912c3402b89745e7224beb0c6c69473ba..a3438eddacca15edf7af338aa6d09f6f0417de15 100644 --- a/src/Tests/TestSalesforceAuthProviderPluginManager.php +++ b/src/Tests/TestSalesforceAuthProviderPluginManager.php @@ -3,10 +3,15 @@ namespace Drupal\salesforce\Tests; use Drupal\salesforce\SalesforceAuthProviderPluginManager; +use Drupal\salesforce\Token\SalesforceToken; class TestSalesforceAuthProviderPluginManager extends SalesforceAuthProviderPluginManager { public function getProvider() { return new TestSalesforceAuthProvider(); } + + public function getToken() { + return new SalesforceToken(); + } } \ No newline at end of file