You are here

services_client.test in Services Client 7.2

Tests for the Administration menu module.

File

tests/services_client.test
View source
<?php

/**
 * @file
 * Tests for the Administration menu module.
 */
abstract class ServicesClientBaseWebTestCase extends DrupalWebTestCase {
  function setUp() {
    $modules = func_get_args();
    if (isset($modules[0]) && is_array($modules[0])) {
      $modules = $modules[0];
    }
    $modules = array_merge($modules, array(
      'ctools',
      'services_client_connection',
      'services_client',
    ));
    parent::setUp($modules);
    variable_set('services_client_id', 'local_id');
    ctools_include('export');
    $this->admin = $this
      ->drupalCreateUser(array(
      'administer services client',
      'skip sending entity',
    ));
    $this->connection = $this
      ->createSCConnection();
  }

  /**
   * Creates services client connection object.
   *
   * @param array $connection
   *   Overrides that will overwrite default configuration.
   *
   * @return object
   *   Connection object.
   */
  protected function createSCConnection($connection = array()) {
    $name = 'fake_connection';
    $defaults = array(
      'name' => $name,
      'admin_title' => $name,
      'version' => 3,
      'endpoint' => url('services_client', array(
        'absolute' => TRUE,
      )),
      'config' => array(
        'auth' => array(
          'plugin' => 'ServicesClientConnectionSessionAuth',
          'config' => array(
            'username' => 'admin',
            'password' => 'admin',
            'token' => 1,
          ),
        ),
        'server' => array(
          'plugin' => 'ServicesClientConnectionRestServer',
          'config' => array(
            'request_formatter' => 'json',
            'response_parser' => 'json',
          ),
        ),
        'request' => array(
          'plugin' => 'ServicesClientConnectionCurlRequest',
          'config' => array(
            'request_timeout' => '30',
            'ssl_verifypeer_skip' => 1,
          ),
        ),
      ),
      'services_client_id' => 'remote_site',
    );
    $connection = (object) drupal_array_merge_deep($defaults, $connection);
    ctools_export_crud_save('services_client_connection', $connection);
    return $connection;
  }

  /**
   * Create new services client event.
   *
   * @return event object.
   */
  protected function createSCEvent($event = array()) {
    $name = isset($event['name']) ? $event['name'] : 'event';
    $this
      ->drupalPost('admin/structure/services_client/add', $event + array(
      'title' => $name,
      'name' => $name,
      'connection' => 'fake_connection',
      'entity_type' => 'node',
      'event' => 'save',
      'plugin' => 'EntitySaveHandler',
    ), "Save");
    return $this
      ->loadEvent($name);
  }

  /**
   * Load existing event from storage.
   *
   * @param string $name
   *   Name of event.
   *
   * @return ServicesClientEvent
   *   Event instance.
   */
  protected function loadEvent($name, $reset = FALSE) {
    if ($reset) {
      $this
        ->resetEventCache();
    }
    return ctools_export_crud_load('services_client_connection_event', $name);
  }

  /**
   * Reset ctools load cache.
   */
  protected function resetEventCache() {
    ctools_export_load_object_reset('services_client_connection_event');
  }

  /**
   * Retrieve plugin UUID from URL. If no url is provided, current client URL is used.
   *
   * @param string $url
   *   URL for UUID search.
   *
   * @return string|NULL
   *   UUID if exists otherwise NULL
   */
  protected function getPluginUuidFromUrl($url = NULL) {
    $url = $url ?: $this
      ->getUrl();
    $match = array();
    preg_match('~^.*/(condition|mapping)/(?P<uuid>[\\d\\w-]+)/edit$~i', $url, $match);
    return !empty($match['uuid']) ? $match['uuid'] : NULL;
  }

  /**
   * Adds condition to event.
   *
   * @param ServicesClientEvent $event
   *   Event where should be condition added.
   *
   * @param string $name
   *   Condition plugin name.
   *
   * @param array $config
   *   Plugin configuration.
   *
   * @return string
   *   New plugin uuid.
   */
  protected function addEventCondition($event, $name, $config) {

    // Test adding new condition
    $this
      ->drupalPost($event
      ->getHandler()
      ->getUrl('add_plugin/condition'), array(
      'type' => $name,
    ), "Save");
    $uuid = $this
      ->getPluginUuidFromUrl();
    $this
      ->drupalPost($event
      ->getHandler()
      ->getUrl('plugin/condition/' . $uuid . '/edit'), $config, "Submit");
    return $uuid;
  }

  /**
   * Remove existing event plugin.
   *
   * @param ServicesClientEvent $event
   *   Event from which should be condition removed.
   *
   * @param string $type
   *   Plugin type - condition, mapping
   *
   * @param string $uuid
   *   Condition UUID.
   */
  protected function removeEventPlugin($event, $type, $uuid) {
    $this
      ->drupalGet($event
      ->getHandler()
      ->getUrl('configure'));
    $xpath_selector = '//table//a[contains(text(), "Remove")][contains(@href, "' . $uuid . '")]';
    $result = $this
      ->xpath($xpath_selector);
    $result = reset($result);
    if (!empty($result)) {
      $parsed = parse_url((string) $result['href']);
      $query = array();
      parse_str($parsed['query'], $query);
      if (isset($query['token'])) {

        // Run remove plugin action.
        $this
          ->drupalGet($event
          ->getHandler()
          ->getUrl('plugin/' . $type . '/' . $uuid . '/remove'), array(
          'query' => array(
            'token' => $query['token'],
          ),
        ));
        $this
          ->assertText('Plugin was removed', "Plugin remove action was successful.");

        // Confirm that plugin was removed.
        $result = $this
          ->xpath($xpath_selector);
        if (empty($result)) {
          return TRUE;
        }
      }
    }
    return FALSE;
  }

  /**
   * Remove existing event condition.
   *
   * @param ServicesClientEvent $event
   *   Event from which should be condition removed.
   *
   * @param string $uuid
   *   Condition UUID.
   */
  protected function removeEventCondition($event, $uuid) {
    $result = $this
      ->removeEventPlugin($event, 'condition', $uuid);
    if ($result) {
      $this
        ->pass("Condition {$uuid} was removed from event {$event->name}.");
    }
    else {
      $this
        ->fail("Error when removing condition {$uuid} from event {$event->name}.");
    }
  }

  /**
   * Add new mapping row to event.
   *
   * @param ServicesClientEvent $event
   *   Event where should be mapping added.
   *
   * @param string $reader
   *   Reader plugin name.
   *
   * @param array $reader_config
   *   Reader configuration.
   *
   * @param string $formatter
   *   Formatter plugin name.
   *
   * @param array $formatter_config
   *   Formatter configuration.
   */
  protected function addEventMapping($event, $reader, $reader_config, $formatter, $formatter_config) {
    $this
      ->drupalGet($event
      ->getHandler()
      ->getUrl('add_plugin/mapping'));
    $uuid = $this
      ->getPluginUuidFromUrl();

    // Add initial plugin configuration.
    $this
      ->drupalPost($event
      ->getHandler()
      ->getUrl('plugin/mapping/' . $uuid . '/edit'), array(
      'reader' => $reader,
      'formatter' => $formatter,
    ), "Submit");

    // Submit plugin configuration.
    $config = array();
    foreach ($reader_config as $name => $value) {
      $config['reader_config[' . $name . ']'] = $value;
    }
    foreach ($formatter_config as $name => $value) {
      $config['formatter_config[' . $name . ']'] = $value;
    }
    $this
      ->drupalPost($event
      ->getHandler()
      ->getUrl('plugin/mapping/' . $uuid . '/edit'), $config, "Submit");
    return $uuid;
  }

  /**
   * Remove existing event mapping row.
   *
   * @param ServicesClientEvent $event
   *   Event from which should be condition removed.
   *
   * @param string $uuid
   *   Mapping UUID.
   */
  protected function removeEventMapping($event, $uuid) {
    $result = $this
      ->removeEventPlugin($event, 'mapping', $uuid);
    if ($result) {
      $this
        ->pass("Mapping {$uuid} was removed from event {$event->name}.");
    }
    else {
      $this
        ->fail("Error when removing mapping {$uuid} from event {$event->name}.");
    }
  }

  /**
   * Save event configuration. Saves any configuration changes to permanent storage.
   *
   * @param ServicesClientEvent $event
   *   Event that should be saved.
   *
   * @param array $config
   *   Optional event configuration on /configure page
   *
   * @param bool $refresh
   *   Weather original event should be refreshed.
   */
  protected function saveEventConfiguration(&$event, $config = array(), $refresh = FALSE) {
    $this
      ->drupalPost($event
      ->getHandler()
      ->getUrl('configure'), $config, "Save");
    if ($refresh) {
      $event = $this
        ->loadEvent($event->name, TRUE);
    }
  }

  /**
   * Cancel event configuration. Cancel any configuration from object cache.
   *
   * @param ServicesClientEvent $event
   *   Event that should be saved.
   */
  protected function cancelEventConfiguration($event) {
    $this
      ->drupalPost($event
      ->getHandler()
      ->getUrl('configure'), array(), "Cancel");
  }

  /**
   * Enables event in ctools export api.
   *
   * @param ServicesClientEvent $event
   *   Event that should be enabled.
   */
  protected function enabledEvent($event) {
    ctools_export_crud_set_status($event->table, $event, FALSE);
    $this
      ->resetEventCache();
  }

  /**
   * Clears whole queue.
   *
   * @param string $name
   *   Optional name of the queue. If non provided, default services client queue
   *   will be queued.
   */
  protected function clearQueue($name = 'services_client_sync') {
    $queue = DrupalQueue::get($name, TRUE);
    $queue
      ->deleteQueue();
  }

  /**
   * Retrieve single item from queue.
   *
   * @param string $name
   *   Optional name of the queue. If non provided, default services client queue
   *   will be queued.
   *
   * @return stdClass
   *   Drupal queue item.
   */
  protected function getQueueItem($name = 'services_client_sync') {
    $queue = DrupalQueue::get($name, TRUE);
    return $queue
      ->claimItem();
  }

}

/**
 * Base class for all administration menu web test cases.
 */
class ServicesClientWebTestCase extends ServicesClientBaseWebTestCase {
  protected $admin;
  protected $connection;
  public static function getInfo() {
    return array(
      'name' => 'Basic functionality',
      'description' => 'Tests basic configuration and UI.',
      'group' => 'Services Client',
    );
  }
  function setUp() {
    parent::setUp();
  }

  /**
   * Test basic event configuration actions.
   */
  public function testAddingEvent() {

    // Login admin user.
    $this
      ->drupalLogin($this->admin);

    // Load services client page.
    $this
      ->drupalGet('admin/structure/services_client');
    $this
      ->assertText('There are no events to display.', "No events are created when module installed.");
    $event = $this
      ->createSCEvent();
    $this
      ->assertIdentical($event->disabled, TRUE, "Newly created event is disabled by default.");

    // Add new property condition.
    $conditions['status'] = $this
      ->addEventCondition($event, 'ServicesClientPropertyCondition', array(
      'property' => 'status',
      'condition' => 'equals',
      'value' => '1',
    ));

    // Test adding new mapping.
    $reader_config = array(
      'property' => 'uid',
    );
    $formatter_config = array(
      'property' => 'uid',
    );
    $mapping['uid'] = $this
      ->addEventMapping($event, 'ServicesClientPropertyReader', $reader_config, 'ServicesClientPropertyFormatter', $formatter_config);

    // Check that non-saved event doesn't have any configuration.
    $event = $this
      ->loadEvent($event->name, TRUE);
    $this
      ->assertIdentical($event->config, array(), "Non saved event configured event isn't changed in permanent storage.");

    // Store new configuration to permanent storage.
    $this
      ->saveEventConfiguration($event, array(), TRUE);
    $this
      ->assertTrue(isset($event->config['condition'][$conditions['status']]), "Condition plugin was saved to event.");
    $this
      ->assertTrue(isset($event->config['mapping'][$mapping['uid']]), "Mapping row was saved to event.");

    // Test condition evalution.
    $fake_entity = (object) array(
      'status' => 0,
    );
    $handler = $event
      ->getHandler();
    $handler
      ->setEntity($fake_entity);
    $this
      ->assertIdentical($handler
      ->isMatching(), FALSE, 'Single non matching condition is executed correctly');
    $fake_entity->status = 1;
    $this
      ->assertIdentical($handler
      ->isMatching(), TRUE, 'Single matching condition is executed correctly');
    $handler
      ->setEntity(NULL);
    $this
      ->assertIdentical($handler
      ->isMatching(), FALSE, "Null entity isn't matching event.");

    // Test removing event condition without token.
    $this
      ->drupalGet($event
      ->getHandler()
      ->getUrl('plugin/condition/' . $conditions['status'] . '/remove'));
    $this
      ->assertText('Invalid token', "When token isn't provided plugin can't be deleted.");
    $this
      ->assertResponse(403, "No token requested resulted in 403 - access denied.");
    $this
      ->drupalGet($event
      ->getHandler()
      ->getUrl('plugin/mapping/' . $mapping['uid'] . '/remove'));
    $this
      ->assertText('Invalid token', "When token isn't provided plugin can't be deleted.");
    $this
      ->assertResponse(403, "No token requested resulted in 403 - access denied.");

    // Test removing event condition.
    $this
      ->removeEventCondition($event, $conditions['status']);

    // Test removing event row mapping.
    $this
      ->removeEventMapping($event, $mapping['uid']);

    // Store new configuration.
    $this
      ->saveEventConfiguration($event, array(), TRUE);
    $this
      ->assertIdentical($event->config['condition'], array(), "Removing condition resulted in empty conditions config array.");
    $this
      ->assertIdentical($event->config['mapping'], array(), "Removing mapping row resulted in empty mapping config array.");

    // Test field condition plugin.
    $conditions['field_test'] = $this
      ->addEventCondition($event, 'ServicesClientFieldCondition', array(
      'field' => 'field_test',
      'language' => LANGUAGE_NONE,
      'property' => 'value',
      'condition' => 'equals',
      'value' => 'myval',
    ));
    $this
      ->saveEventConfiguration($event, array(), TRUE);
    $handler = $event
      ->getHandler();
    $fake_entity->field_test[LANGUAGE_NONE][0]['value'] = 'myval';
    $handler
      ->setEntity($fake_entity);
    $this
      ->assertIdentical($handler
      ->isMatching(), TRUE, 'Field condition is executed correctly when value is matching');
    $fake_entity->field_test[LANGUAGE_NONE][0]['value'] = 'otherval';
    $handler
      ->setEntity($fake_entity);
    $this
      ->assertIdentical($handler
      ->isMatching(), FALSE, 'Field condition is executed correctly when value is not matching');

    // Test handler tags
    $this
      ->assertIdentical($handler
      ->hasTag('test'), FALSE, 'By default no tag is assigned to handler');
    $handler
      ->addTag('test');
    $this
      ->assertIdentical($handler
      ->hasTag('test'), TRUE, 'Added tag is repsonding.');
    $handler
      ->removeTag('test');
    $this
      ->assertIdentical($handler
      ->hasTag('test'), FALSE, "Removed tag isn't responding.");
  }
  public function testServicesClientProcessing() {

    // Login admin user.
    $this
      ->drupalLogin($this->admin);
    $content_type = $this
      ->drupalCreateContentType(array(
      'type' => 'post',
      'name' => 'Post',
    ));
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'post',
      'title' => 'Test post',
    ));
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertIdentical($result, array(), "No events are triggered by default.");
    $event = $this
      ->createSCEvent();

    // Add new property condition.
    $conditions['status'] = $this
      ->addEventCondition($event, 'ServicesClientPropertyCondition', array(
      'property' => 'status',
      'condition' => 'equals',
      'value' => '1',
    ));
    $conditions['status'] = $this
      ->addEventCondition($event, 'ServicesClientPropertyCondition', array(
      'property' => 'type',
      'condition' => 'equals',
      'value' => 'post',
    ));

    // Add uid mapping.
    $mapping['uid'] = $this
      ->addEventMapping($event, 'ServicesClientPropertyReader', array(
      'property' => 'uid',
    ), 'ServicesClientPropertyFormatter', array(
      'property' => 'uid',
    ));
    $reader = array(
      'field' => 'body',
      'property' => 'value',
    );
    $formatter = array(
      'field' => 'field_body',
      'property' => 'value',
    );
    $mapping['body'] = $this
      ->addEventMapping($event, 'ServicesClientFieldReader', $reader, 'ServicesClientFieldFormatter', $formatter);
    $formatter = array(
      'field' => 'field_body_d6',
      'property' => 'value',
    );
    $mapping['body_d6'] = $this
      ->addEventMapping($event, 'ServicesClientFieldReader', $reader, 'ServicesClientFieldD6Formatter', $formatter);
    $this
      ->saveEventConfiguration($event, array(), TRUE);
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertIdentical($result, array(), "Disabled events are not triggered by default.");

    // Enable configured event.
    $this
      ->enabledEvent($event);
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertIdentical(count($result), 1, "One event was triggered automatically.");
    $result = reset($result);
    $this
      ->assertIdentical(get_class($result), 'ServicesClientEventResult', "Syncing operation result is returned in correct format");
    $this
      ->assertIdentical($result
      ->getEntityId(), $node->nid, "Response object extracts entity id correctly.");
    $this
      ->assertIdentical($result
      ->success(), FALSE, "Failed event is reporting correct result status.");
    $this
      ->assertTrue(!empty($result->object), "Sync event created non-empty object.");
    $this
      ->assertTrue(!empty($result->object->_services_client), 'Control data was correctly set to mapped object.');
    $this
      ->assertIdentical($result->object->uid, $node->uid, "Node uid was mapped correctly.");
    $this
      ->assertIdentical($result->object->field_body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['value'], "Body field was mapped correclty with field mapper.");
    $this
      ->assertIdentical($result->object->field_body_d6[0]['value'], $node->body[LANGUAGE_NONE][0]['value'], "Body field was mapped correclty with field mapper.");

    // Test looping control
    $node->_services_client['origin'] = 'remote_site';
    $node->_services_client['visted'] = array(
      'remote_site',
    );
    $handler = $event
      ->getHandler();
    $result = $handler
      ->setEntity($node)
      ->execute();
    $this
      ->assertIdentical($result
      ->success(), FALSE, "Looping event is reporting correct result status.");
    $this
      ->assertIdentical($result->error_type, ServicesClientErrorType::LOOP, "Loop error type is reported.");

    // Test control data queueing.
    $this
      ->clearQueue();
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertIdentical($result, array(), "No events were processed when node has control data.");
    $item = $this
      ->getQueueItem();
    $this
      ->assertIdentical($node->nid, $item->data['entity']->nid, "Processed entity was queued when contain controlling data");
    $this
      ->clearQueue();

    // Bypass queue processing.
    $node->_services_client['bypass_queue'] = TRUE;
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertTrue(!empty($result), "Event was processed directly if bypas_queue flag was present.");
    $result = reset($result);
    $this
      ->assertIdentical($result
      ->getEntityId(), $node->nid, "Response object extracts entity id correctly.");
    $this
      ->assertIdentical($result
      ->success(), FALSE, "Failed event is reporting correct result status.");
    $item = $this
      ->getQueueItem();
    $this
      ->assertTrue(empty($item), "No object was queued if bypass_queue is present.");

    // Test auto queueing
    $this
      ->saveEventConfiguration($event, array(
      'queue' => 1,
    ), TRUE);
    unset($node->_services_client);
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertIdentical($result, array(), "No events were processed when force queue is enabled.");
    $item = $this
      ->getQueueItem();
    $this
      ->assertIdentical($node->nid, $item->data['entity']->nid, "Processed entity was queued when force queue is enabled.");
    $this
      ->clearQueue();

    // Test non-triggered events.
    $this
      ->saveEventConfiguration($event, array(
      'queue' => FALSE,
      'auto_triggered' => FALSE,
    ), TRUE);
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertIdentical($result, array(), "No events were processed when auto trigger is off.");
    $item = $this
      ->getQueueItem();
    $this
      ->assertTrue(empty($item), "No queue item was created when auto trigger is turned off.");
  }

}

/**
 * Base class for all administration menu web test cases.
 */
class ServicesClientHooksWebTestCase extends ServicesClientBaseWebTestCase {
  protected $admin;
  protected $connection;
  public static function getInfo() {
    return array(
      'name' => 'Hooks functionality',
      'description' => 'Tests services client hooks',
      'group' => 'Services Client',
    );
  }
  function setUp() {
    parent::setUp('services_client_test');
  }
  public function testServicesClientHooks() {

    // Login admin user.
    $this
      ->drupalLogin($this->admin);
    $content_type = $this
      ->drupalCreateContentType(array(
      'type' => 'post',
      'name' => 'Post',
    ));
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'post',
      'title' => 'Test post',
    ));
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertIdentical($result, array(), "No events are triggered by default.");
    $event = $this
      ->createSCEvent();

    // Add new property condition.
    $conditions['status'] = $this
      ->addEventCondition($event, 'ServicesClientPropertyCondition', array(
      'property' => 'status',
      'condition' => 'equals',
      'value' => '1',
    ));
    $conditions['status'] = $this
      ->addEventCondition($event, 'ServicesClientPropertyCondition', array(
      'property' => 'type',
      'condition' => 'equals',
      'value' => 'post',
    ));

    // Add uid mapping.
    $mapping['uid'] = $this
      ->addEventMapping($event, 'ServicesClientPropertyReader', array(
      'property' => 'uid',
    ), 'ServicesClientPropertyFormatter', array(
      'property' => 'uid',
    ));
    $reader = array(
      'field' => 'body',
      'property' => 'value',
    );
    $formatter = array(
      'field' => 'field_body',
      'property' => 'value',
    );
    $mapping['body'] = $this
      ->addEventMapping($event, 'ServicesClientFieldReader', $reader, 'ServicesClientFieldFormatter', $formatter);
    $formatter = array(
      'field' => 'field_body_d6',
      'property' => 'value',
    );
    $mapping['body_d6'] = $this
      ->addEventMapping($event, 'ServicesClientFieldReader', $reader, 'ServicesClientFieldD6Formatter', $formatter);
    $this
      ->saveEventConfiguration($event, array(), TRUE);
    $this
      ->enabledEvent($event);

    // Test basic sync hooks
    $result = services_client_process_events('save', $node, 'node');
    $result = reset($result);
    $this
      ->assertIdentical($result->object->services_client_test, TRUE, "hook_services_client_mapped_object_alter was triggered correctly.");
    $this
      ->assertIdentical($result->object->services_client_test_name, $event->name, "Event handler  is availbale in hook_services_client_mapped_object_alter.");
    $this
      ->assertIdentical($result->object->services_client_before_request, TRUE, "hook_services_client_before_request was triggered correctly.");
    $this
      ->assertIdentical($result->object->services_client_after_request, TRUE, "hook_services_client_after_request was triggered correctly.");
    $this
      ->assertIdentical($result->services_client_process_events, TRUE, "hook_services_client_process_events was triggered correctly.");
    $node->services_client_skip_autosync = TRUE;
    $result = services_client_process_events('save', $node, 'node');
    $this
      ->assertIdentical($result, array(), "Skip autosync hook was correctly triggered and prevented entity syncing.");
  }

}

// Test user handler features
// Test node handler

Classes

Namesort descending Description
ServicesClientBaseWebTestCase @file Tests for the Administration menu module.
ServicesClientHooksWebTestCase Base class for all administration menu web test cases.
ServicesClientWebTestCase Base class for all administration menu web test cases.