You are here

services_entity.test in Services Entity API 7.2

Same filename and directory in other branches
  1. 7 tests/services_entity.test

Services Entity Tests

File

tests/services_entity.test
View source
<?php

/**
 * @file Services Entity Tests
 */

/**
 * Services Entity Test Helper class.
 */
class ServicesEntityTestHelper extends ServicesWebTestCase {

  /**
   * Need to override this :(
   *
   * @param array $resources
   *   A list of the resources which should be added.
   *
   * @todo: Remove when https://drupal.org/node/2089445 is fixed.
   */
  public function saveNewEndpoint(array $resources) {
    $edit = $this
      ->populateEndpointFAPI();
    $edit['path'] = 'endpoint';
    $edit['title'] = 'WT??';
    $endpoint = new stdClass();
    $endpoint->disabled = FALSE;

    /* Edit this to true to make a default endpoint disabled initially */
    $endpoint->api_version = 3;
    $endpoint->name = $edit['name'];
    $endpoint->title = $edit['title'];
    $endpoint->server = $edit['server'];
    $endpoint->path = $edit['path'];
    $endpoint->authentication = array(
      'services' => 'services',
    );
    $endpoint->server_settings = array(
      'formatters' => array(
        'json' => TRUE,
        'bencode' => TRUE,
        'rss' => TRUE,
        'plist' => TRUE,
        'xmlplist' => TRUE,
        'php' => TRUE,
        'yaml' => TRUE,
        'jsonp' => FALSE,
        'xml' => FALSE,
      ),
      'parsers' => array(
        'application/x-yaml' => TRUE,
        'application/json' => TRUE,
        'application/vnd.php.serialized' => TRUE,
        'application/plist' => TRUE,
        'application/plist+xml' => TRUE,
        'application/x-www-form-urlencoded' => TRUE,
      ),
    );
    $endpoint->resources = array(
      'system' => array(
        'alias' => '',
        'actions' => array(
          'connect' => array(
            'enabled' => 1,
          ),
          'get_variable' => array(
            'enabled' => 1,
          ),
          'set_variable' => array(
            'enabled' => 1,
          ),
        ),
      ),
      'user' => array(
        'alias' => '',
        'operations' => array(
          'create' => array(
            'enabled' => 1,
          ),
          'retrieve' => array(
            'enabled' => 1,
          ),
          'update' => array(
            'enabled' => 1,
          ),
          'delete' => array(
            'enabled' => 1,
          ),
          'index' => array(
            'enabled' => 1,
          ),
        ),
        'actions' => array(
          'login' => array(
            'enabled' => 1,
          ),
          'logout' => array(
            'enabled' => 1,
          ),
        ),
      ),
    );
    foreach ($resources as $resource) {

      // @todo Add other operations besides CRUD.
      $endpoint->resources += array(
        $resource => array(
          'operations' => array(
            'retrieve' => array(
              'enabled' => 1,
            ),
            'create' => array(
              'enabled' => 1,
            ),
            'update' => array(
              'enabled' => 1,
            ),
            'delete' => array(
              'enabled' => 1,
            ),
            'index' => array(
              'enabled' => 1,
            ),
          ),
        ),
      );
    }
    $endpoint->debug = 1;
    $endpoint->export_type = FALSE;
    services_endpoint_save($endpoint);
    $endpoint = services_endpoint_load($endpoint->name);
    $this
      ->assertTrue($endpoint->name == $edit['name'], 'Endpoint successfully created');
    return $endpoint;
  }

  /**
   * Log a user in via services.
   *
   * @param String $name
   *   The name of the user to log in.
   * @param String $pass
   *   The password of the user to log in.
   *
   * @return Object
   *   The newly logged in user object.
   */
  public function serviceLogin($name, $pass) {
    $response = $this
      ->servicesPost($this->endpoint->path . '/user/login', array(
      'username' => $name,
      'password' => $pass,
    ));
    $response_data = $response['body'];
    $proper_answer = isset($response_data->sessid) && isset($response_data->user) && $response_data->user->name == $name;
    $this
      ->assertTrue($proper_answer, 'User successfully logged in.');

    // Save session details.
    $this->session_id = $response_data->sessid;
    $this->session_name = $response_data->session_name;
    $this->loggedInUser = $response_data->user;
    return $this->loggedInUser;
  }

  /**
   * Log out via services.
   */
  public function serviceLogout() {
    $response = $this
      ->servicesPost($this->endpoint->path . '/user/logout');
    $this
      ->assertEqual($response['code'], 200, 'Received 200 response code from LOGOUT');
  }

  /**
   * Utility function to recursively turn an object into an array.
   *
   * @param array/object $object
   *   The object, array or element to process
   *
   * @return array
   *   An array corresponding to $object, with any objects converted to arrays.
   *
   * @author ChrisO
   */
  protected function unObject($object) {
    if (is_object($object)) {
      $object = (array) $object;
    }
    if (is_array($object)) {
      foreach ($object as $key => $element) {
        $object[$key] = $this
          ->unObject($element);
      }
    }
    return $object;
  }

  /**
   * Helper to perform a RESTful update of a resource.
   *
   * @param String $resource
   *   The resource type to operate on.
   * @param int $name
   *   The name of the resource to update
   * @param array $data
   *   The update data to put.
   * @param int $code
   *   The expected response code (defaults to 200).
   */
  public function update($resource, $name, $data = array(), $code = 200) {
    $r = $this
      ->servicesPut($this->endpoint->path . '/' . $resource . '/' . $name, $data);
    $this
      ->assertEqual($r['code'], $code, "Received {$code} response code from UPDATE {$resource}/{$name} (actual response=" . $r['code'] . ")");
    return $this
      ->unObject($r['body']);
  }

  /**
   * Helper to perform a RESTful action of a resource.
   *
   * @param String $resource
   *   The resource type to operate on.
   * @param int $name
   *   The name of the resource to update
   * @param array $data
   *   The update data to put.
   * @param int $code
   *   The expected response code (defaults to 200).
   */
  public function action($resource, $name, $action, $data, $code) {
    if (!is_array($data)) {
      $data = (array) $data;
    }
    $destination = '';
    if (isset($name) && strlen($name) > 0) {
      $path = "{$resource}/{$name}/{$action}";
    }
    else {
      $path = "{$resource}/{$action}";
    }
    $r = $this
      ->servicesPost($this->endpoint_path . '/' . $path, $data);
    $this
      ->assertEqual($r['code'], $code, "Received {$code} response code from ACTION {$path} (actual response=" . $r['code'] . ")");
    return $this
      ->unObject($r['body']);
  }

  /**
   * Helper to perform a RESTful create of a resource.
   *
   * @param String $resource
   *   The resource type to operate on.
   * @param array $data
   *   The update data to post.
   * @param int $code
   *   The expected response code (defaults to 200).
   */
  public function create($resource, $data = array(), $code = 200) {
    $r = $this
      ->servicesPost($this->endpoint->path . '/' . $resource, $data);
    $this
      ->assertEqual($r['code'], $code, "Received {$code} response code from CREATE {$resource} (actual response=" . $r['code'] . ")");
    return $this
      ->unObject($r['body']);
  }

  /**
   * Helper function to perform a RESTful delete of a resource.
   *
   * @param String $resource
   *   The resource type to operate on.
   * @param int $name
   *   The name of the resource to update. (i.e. the ID).
   * @param int $code
   *   The expected response code (defaults to 200).
   */
  public function delete($resource, $name, $code = 200) {

    // Call to parent::servicesDelete
    $r = $this
      ->servicesDelete($this->endpoint->path . '/' . $resource . '/' . $name, NULL);
    $this
      ->assertEqual($r['code'], $code, "Received {$code} response code from DELETE {$resource}/{$name} (actual response=" . $r['code'] . ")");
    return $this
      ->unObject($r['body']);
  }

  /**
   * Helper to performa a RESTful retrieve of a resource.
   *
   * @param string $resource
   *   The resource type to operate on.
   * @param string $name
   *   The name of the resource to update
   * @param array $args
   *   Any additional args for the querystring.
   * @param integer $code
   *   The expected response code (defaults to 200).
   */
  public function retrieve($resource, $name, $args = array(), $code = 200) {
    $r = $this
      ->servicesGet($this->endpoint->path . '/' . $resource . '/' . $name, $args);
    $this
      ->assertEqual($r['code'], $code, "Received {$code} response code from RETRIEVE {$resource}/{$name} (actual response=" . $r['code'] . ")");
    return $this
      ->unObject($r['body']);
  }

  /**
   * Helper to performa a RESTful index of a resource.
   *
   * @param string $resource
   *   The resource type to operate on.
   * @param array $args
   *   Any additional args for the querystring.
   * @param integer $code
   *   The expected response code (defaults to 200).
   */
  public function index($resource, $args = array(), $code = 200) {
    $r = $this
      ->servicesGet($this->endpoint->path . '/' . $resource, $args);
    $this
      ->assertEqual($r['code'], $code, "Received {$code} response code from INDEX {$resource} (actual response=" . $r['code'] . ")");
    return $this
      ->unObject($r['body']);
  }

}

/**
 * Test resources on the Generic controller, using a test entity type.
 */
class ServicesEntityGenericEntityResource extends ServicesEntityTestHelper {

  /**
   * The user for the test.
   */
  protected $privilegedUser;

  /**
   * The Services endpoint config object.
   */
  protected $endpoint = NULL;

  /**
   * The resource controller class to use.
   *
   * This is assigned to the system variable by setUp().
   */
  protected $services_entity_resource_class = 'ServicesEntityResourceController';

  /**
   * Implements getInfo().
   */
  public static function getInfo() {
    return array(
      'name' => 'Test Entity Generic Resource',
      'description' => "Test resources using the generic resource controller, with a test entity type.",
      'group' => 'Services Entity',
    );
  }

  /**
   * Implements setUp().
   */
  public function setUp() {
    parent::setUp('libraries', 'entity', 'services_entity', 'services_entity_test');

    // Set up endpoint.
    $this->endpoint = $this
      ->saveNewEndpoint(array(
      'entity_services_entity_test',
    ));
    $this->resource_path = $this->endpoint->path . '/entity_services_entity_test';

    // Create an unprivileged user.
    $this->unPrivilegedUser = $this
      ->drupalCreateUser();

    // Set the resource class to use.
    variable_set('services_entity_resource_class', $this->services_entity_resource_class);
  }

  /**
   * Test 'Retrieve' service.
   */
  public function testEntityRetrieve() {

    // Create our privileged user.
    $this->privilegedUser = $this
      ->drupalCreateUser(array(
      'view services_entity_test entities',
    ));

    // Create an entity to retrieve.
    $entity = entity_create('services_entity_test', array(
      'type' => 'alpha',
      'name' => $this
        ->randomString(),
      'uid' => $this->privilegedUser->uid,
    ));
    $wrapper = entity_metadata_wrapper('services_entity_test', $entity);

    // Set a field value.
    $test_text_value = $this
      ->randomString();
    $wrapper->field_test_text_alpha
      ->set($test_text_value);
    $entity
      ->save();
    $this
      ->drupalGet($this->resource_path . '/' . $entity->eid);

    // Log in as the unprivileged user.
    $this
      ->drupalLogin($this->unPrivilegedUser);

    // Try getting an entity without access.
    $response = $this
      ->servicesGet($this->endpoint->path . '/entity_services_entity_test/' . $entity->eid);
    $this
      ->assertTrue($response['code'] == '401', 'Retrieval of an entity without view access returns a 401.');

    // Log in as the privileged user.
    $this
      ->drupalLogin($this->privilegedUser);
    $response = $this
      ->servicesGet($this->endpoint->path . '/entity_services_entity_test/' . $entity->eid);
    $retrieved_entity_data = $response['body'];

    // Check values on the retrieved entity data.
    $this
      ->assertEqual($entity->name, $retrieved_entity_data->name, 'Retrieved entity has the name property correctly set.');
    $this
      ->assertEqual($test_text_value, $retrieved_entity_data->field_test_text_alpha['und'][0]['value'], 'Retrieved entity has the field value correctly set.');

    // Try getting an entity that doesn't exist.
    $responseArray = $this
      ->servicesGet($this->resource_path . '/42');
    $this
      ->assertTrue($responseArray['code'] == '404', 'Retrieval of a non-existent entity returns a 404.');
  }

  /**
   * Test 'Create' service.
   */
  public function testEntityCreate() {

    // Create our privileged user.
    $this->privilegedUser = $this
      ->drupalCreateUser(array(
      'create services_entity_test entities',
    ));

    // An array of entity data to pass to the service to create an entity.
    $entity_data = array(
      'type' => 'alpha',
      'name' => $this
        ->randomString(),
      'uid' => $this->privilegedUser->uid,
    );
    $test_text_value = $this
      ->randomString();
    $entity_data['field_test_text_alpha']['und'][0]['value'] = $test_text_value;

    // Log in as the unprivileged user.
    $this
      ->drupalLogin($this->unPrivilegedUser);

    // Try to create the entity using the service, without access.
    $response = $this
      ->servicesPost($this->resource_path, $entity_data);
    $this
      ->assertTrue($response['code'] == '401', "Creation of an entity without 'create' access returns a 401.");

    // Log in as the privileged user.
    $this
      ->drupalLogin($this->privilegedUser);

    // Create the entity using the service.
    $response = $this
      ->servicesPost($this->resource_path, $entity_data);

    // We get the new entity returned to us.
    $returned_entity = $response['body'];
    $new_entity_id = $returned_entity->eid;

    // Load the entity from the DB, using the entity ID from the response we
    // got back from the service.
    $entity = entity_load_single('services_entity_test', $new_entity_id);
    $wrapper = entity_metadata_wrapper('services_entity_test', $entity);
    $this
      ->assertEqual($entity->name, $entity_data['name'], 'Created entity has the name property correctly set.');
    $this
      ->assertEqual($wrapper->field_test_text_alpha
      ->raw(), $test_text_value, 'Created entity has the text field value correctly set.');
  }

  /**
   * Test 'Update' service.
   */
  public function testEntityUpdate() {

    // Create our privileged user.
    $this->privilegedUser = $this
      ->drupalCreateUser(array(
      'update services_entity_test entities',
    ));

    // Create an entity to update.
    $original_entity_data = array(
      'type' => 'alpha',
      'name' => $this
        ->randomString(),
      'uid' => $this->privilegedUser->uid,
    );
    $original_entity = entity_create('services_entity_test', $original_entity_data);
    $original_entity_wrapper = entity_metadata_wrapper('services_entity_test', $original_entity);

    // Set a field value.
    $original_entity_wrapper->field_test_text_alpha
      ->set($this
      ->randomString());
    $original_entity
      ->save();

    // Build an array of data to update to the entity.
    $update_entity_data = $original_entity_data;

    // We have to add the entity id.
    $update_entity_data['eid'] = $original_entity->eid;

    // Change the name.
    $update_entity_data['name'] = $this
      ->randomString();

    // Change the field value.
    $test_text_value = $this
      ->randomString();
    $update_entity_data['field_test_text_alpha']['und'][0]['value'] = $test_text_value;

    // Log in as the unprivileged user.
    $this
      ->drupalLogin($this->unPrivilegedUser);

    // Attempt to update the entity using the service, without access.
    $response = $this
      ->servicesPut($this->resource_path . '/' . $original_entity->eid, $update_entity_data);
    $this
      ->assertTrue($response['code'] == '401', "Update of an entity without 'update' access returns a 401.");

    // Log in as the privileged user.
    $this
      ->drupalLogin($this->privilegedUser);

    // Update the entity using the service.
    $response = $this
      ->servicesPut($this->resource_path . '/' . $original_entity->eid, $update_entity_data);

    // We get the updated entity returned to us.
    $returned_entity = $response['body'];

    // Load the entity from the DB, using the entity ID from the response we
    // got back from the service.
    // Clear the cache first.
    entity_get_controller('services_entity_test')
      ->resetCache();
    $updated_entity = entity_load_single('services_entity_test', $original_entity->eid);
    $updated_entity_wrapper = entity_metadata_wrapper('services_entity_test', $updated_entity);
    $this
      ->assertEqual($update_entity_data['name'], $updated_entity->name, 'Name property was changed on the updated entity.');
    $this
      ->assertEqual($test_text_value, $updated_entity_wrapper->field_test_text_alpha
      ->raw(), 'Field value was changed on the updated entity.');
  }

  /**
   * Test 'Delete' service.
   */
  public function testEntityDelete() {

    // Create our privileged user.
    $this->privilegedUser = $this
      ->drupalCreateUser(array(
      'delete services_entity_test entities',
    ));

    // Create an entity to delete.
    $entity_data = array(
      'type' => 'alpha',
      'name' => $this
        ->randomString(),
      'uid' => $this->privilegedUser->uid,
    );
    $entity = entity_create('services_entity_test', $entity_data);
    $entity
      ->save();

    // Log in as the unprivileged user.
    $this
      ->drupalLogin($this->unPrivilegedUser);
    $response = $this
      ->servicesDelete($this->resource_path . '/' . $entity->eid);
    $this
      ->assertTrue($response['code'] == '401', "Deletion of an entity without 'delete' access returns a 401.");

    // Log in as the privileged user.
    $this
      ->drupalLogin($this->privilegedUser);
    $response = $this
      ->servicesDelete($this->resource_path . '/' . $entity->eid);

    // Load the entity from the DB to check it's been deleted.
    // Clear the cache first.
    entity_get_controller('services_entity_test')
      ->resetCache();
    $deleted_entity = entity_load_single('services_entity_test', $entity->eid);
    $this
      ->assertFalse($deleted_entity, 'The entity has been deleted.');
  }

  /**
   * Test 'Index' service.
   */
  public function testEntityIndex() {

    // Create our privileged user.
    $this->privilegedUser = $this
      ->drupalCreateUser(array(
      'view services_entity_test entities',
    ));

    // Create some entities to index.
    $entity_data = array(
      'type' => 'alpha',
      'name' => 'one',
      'uid' => $this->privilegedUser->uid,
    );
    $entity = entity_create('services_entity_test', $entity_data);
    $wrapper = entity_metadata_wrapper('services_entity_test', $entity);
    $wrapper->field_test_text_alpha
      ->set('field-value-1');
    $entity
      ->save();
    $entity_data = array(
      'type' => 'alpha',
      'name' => 'two',
      'uid' => $this->privilegedUser->uid,
    );
    $entity = entity_create('services_entity_test', $entity_data);
    $wrapper = entity_metadata_wrapper('services_entity_test', $entity);
    $wrapper->field_test_text_alpha
      ->set('field-value-1');
    $entity
      ->save();
    $entity_data = array(
      'type' => 'alpha',
      'name' => 'three',
      'uid' => 0,
    );
    $entity = entity_create('services_entity_test', $entity_data);
    $wrapper = entity_metadata_wrapper('services_entity_test', $entity);
    $wrapper->field_test_text_alpha
      ->set('field-value-2');
    $entity
      ->save();
    $entity_data = array(
      'type' => 'beta',
      'name' => 'one',
      'uid' => 0,
    );
    $entity = entity_create('services_entity_test', $entity_data);
    $entity
      ->save();

    // Log in as the unprivileged user.
    $this
      ->drupalLogin($this->unPrivilegedUser);

    // Attempt to get the index of all entities without access.
    $response = $this
      ->servicesGet($this->resource_path);
    $this
      ->assertTrue($response['code'] == '404', "Retrieving an index of entities without 'view' access for any returns a 404.");

    // Log in as the privileged user.
    $this
      ->drupalLogin($this->privilegedUser);

    // Get the index of all entities.
    $response = $this
      ->servicesGet($this->resource_path);
    $retrieved_data = $response['body'];
    $this
      ->assertEqual(count($retrieved_data), 4, 'All entities were listed by the index service.');

    // Get the index of only the 'alpha' bundle entities.
    $response = $this
      ->servicesGet($this->resource_path, array(
      'parameters[type]' => 'alpha',
    ));
    $retrieved_data = $response['body'];
    $this
      ->assertEqual(count($retrieved_data), 3, 'The correct number of entities was returned by the index when filtered by entity type.');
    $all_correct = TRUE;
    foreach ($retrieved_data as $retrieved_entity) {
      $all_correct &= $retrieved_entity->type == 'alpha';
    }
    $this
      ->assertTrue($all_correct, 'All the retrieved entities were of the requested entity type.');

    // Get the index of entities by name.
    $response = $this
      ->servicesGet($this->resource_path, array(
      'parameters[name]' => 'one',
    ));
    $retrieved_data = $response['body'];
    $this
      ->assertEqual(count($retrieved_data), 2, "The correct number of entities was returned by the index when filtered by entity 'name' property.");
    $all_correct = TRUE;
    foreach ($retrieved_data as $retrieved_entity) {
      $all_correct &= $retrieved_entity->name == 'one';
    }
    $this
      ->assertTrue($all_correct, 'All the retrieved entities had the requested entity property.');

    // Get the index of entities by uid.
    $response = $this
      ->servicesGet($this->resource_path, array(
      'parameters[uid]' => '0',
    ));
    $retrieved_data = $response['body'];
    debug($retrieved_data);
    $this
      ->assertEqual(count($retrieved_data), 2, "The correct number of entities was returned by the index when filtered by entity 'uid' property.");
    $all_correct = TRUE;
    foreach ($retrieved_data as $retrieved_entity) {
      $all_correct &= $retrieved_entity->uid == 0;
    }
    $this
      ->assertTrue($all_correct, 'All the retrieved entities had the requested entity property.');

    // Get the index of entities by multiple properties.
    $response = $this
      ->servicesGet($this->resource_path, array(
      'parameters[type]' => 'alpha',
      'parameters[uid]' => '0',
    ));
    $retrieved_data = $response['body'];
    $this
      ->assertEqual(count($retrieved_data), 1, "The correct number of entities was returned by the index when filtered by entity type and 'uid' property.");

    // Get the index of entities by a field value.
    $response = $this
      ->servicesGet($this->resource_path, array(
      'parameters[field_test_text_alpha]' => 'field-value-1',
    ));
    $retrieved_data = $response['body'];
    $this
      ->assertEqual(count($retrieved_data), 2, "The correct number of entities was returned by the index when filtered by a field value.");

    // todo: check that if a single entity denies access, it is excluded from
    // the returned list of entities.
  }

}

/**
 * Tests entity_node services for both the generic and clean controller.
 */
class ServicesEntityNodeResourceTest extends ServicesEntityTestHelper {

  /**
   * Returns information about this test case.
   *
   * @return
   *   An array of information about this test case
   */
  public static function getInfo() {
    return array(
      'name' => 'Entity Node Resource',
      'description' => 'Ensure that the entity_node resource functions correctly.',
      'group' => 'Services Entity',
    );
  }

  /**
   * @see ServicesWebTestCase::setUp()
   */

  ///* Restore this commenting-out to test using local db (a bit faster that way).
  public function setUp() {
    parent::setUp('libraries', 'entity', 'services_entity');
    $this->endpoint = $this
      ->saveNewEndpoint(array(
      'entity_node',
    ));
  }

  // */ public function setUp() { $this->setup = TRUE; $this->endpoint = services_endpoint_load('services'); $this->cookieFile = drupal_tempnam(variable_get('file_temporary_path'), 'services_cookiejar'); $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile;} public function tearDown()  {}

  /**
   * Test index functionality with the clean controller.
   */
  public function testIndexClean() {
    $this
      ->testIndex(TRUE);
  }

  /**
   * Test index functionality.
   *
   * @param boolean $clean
   *   TRUE to use the clean controller, FALSE to use the generic.
   */
  public function testIndex($clean = FALSE) {

    // Choose the controller.
    if ($clean) {
      variable_set('services_entity_resource_class', 'ServicesEntityResourceControllerClean');
    }
    else {
      variable_set('services_entity_resource_class', 'ServicesEntityResourceController');
    }

    // Create some users so we can have multiple authors.
    $account1 = $this
      ->drupalCreateUser(array(
      'bypass node access',
    ));
    $account2 = $this
      ->drupalCreateUser(array(
      'bypass node access',
    ));

    // Create some nodes
    for ($i = 0; $i < 6; $i++) {
      $values = array(
        'type' => $i < 4 ? 'page' : 'article',
        'uid' => $i % 2 == 0 ? $account1->uid : $account2->uid,
        'title' => $this
          ->randomName(8),
        'body' => array(
          LANGUAGE_NONE => array(
            '0' => array(
              'value' => $this
                ->randomName(30),
              'format' => filter_default_format(),
              'summary' => '',
            ),
          ),
        ),
      );
      $nodes[] = $this
        ->drupalCreateNode($values);
    }

    // Fetch the index and verify that it returns the correct number of nodes.
    $resource = 'entity_node';
    $r = $this
      ->index($resource);
    $this
      ->assertEqual(count($r), count($nodes), 'Index returned the correct number of nodes.');

    // Try the same for each type.
    $r = $this
      ->index($resource, array(
      'parameters[type]' => 'page',
    ));
    $this
      ->assertEqual(count($r), 4, 'Index returned the correct number of pages.');
    $r = $this
      ->index($resource, array(
      'parameters[type]' => 'article',
    ));
    $this
      ->assertEqual(count($r), 2, 'Index returned the correct number of articles.');

    // Try filtering by uid/author
    // We need a user with view profiles permission to see the author property.
    $admin_user = $this
      ->drupalCreateUser(array(
      'access user profiles',
    ));
    $this
      ->serviceLogin($admin_user->name, $admin_user->pass_raw);
    foreach (array(
      $account1,
      $account2,
    ) as $account) {
      $author_field = $clean ? 'author' : 'uid';

      //$this->use_xdebug = 1;
      $r = $this
        ->index($resource, array(
        "parameters[{$author_field}]" => $account->uid,
      ));
      $this
        ->assertEqual(count($r), 3, 'Index returned the correct number of nodes by author ' . $account->uid);
      foreach ($r as $node) {
        if ($clean) {
          $this
            ->assertEqual($node['author']['id'], $account->uid, 'All returned nodes have the correct author ' . $account->uid);
        }
        else {
          $this
            ->assertEqual($node['uid'], $account->uid, 'All returned nodes have the correct author ' . $account->uid);
        }
      }
    }

    // Try filtering by an invalid property and verify that we get an error.
    $result = $this
      ->index($resource, array(
      "parameters[foo]" => 3,
    ), 406);
  }

  /**
   * Tests node services using the 'clean' resource controller.
   */
  public function testCRUDClean() {
    $this
      ->testCRUD(TRUE);
  }

  /**
   * Tests basic CRUD and index actions of a node via the entity_node service.
   */
  public function testCRUD($clean = FALSE) {
    $resource = 'entity_node';

    // Certain things are different depending on whether or not we are using
    // the 'clean' resource controller.
    if ($clean) {
      variable_set('services_entity_resource_class', 'ServicesEntityResourceControllerClean');
      $node = array(
        'type' => 'page',
        'title' => $this
          ->randomName(10),
        'body' => array(
          'value' => $this
            ->randomName(50),
          'format' => 'full_html',
          'summary' => '',
        ),
      );
    }
    else {
      variable_set('services_entity_resource_class', 'ServicesEntityResourceController');
      $node = array(
        'type' => 'page',
        'title' => $this
          ->randomName(10),
        'body' => array(
          'und' => array(
            '0' => array(
              'value' => $this
                ->randomName(50),
              'format' => 'full_html',
              'summary' => '',
            ),
          ),
        ),
      );
    }

    // Test retrieving a specific revision.
    $revisions = $this
      ->createNodeRevisions();
    foreach ($revisions as $revision) {
      $retrieved = $this
        ->retrieve($resource, $revision->nid, array(
        'revision' => $revision->vid,
      ), 200);
      $this
        ->assertEqual($retrieved['title'], $revision->title, 'Revision ' . $revision->vid . ' has the correct title.');
    }

    // Ensure that retrieving the node without specifying a revision gets the
    // most recent revision.
    $retrieved = $this
      ->retrieve($resource, $revision->nid);
    $this
      ->assertEqual($retrieved['title'], $revision->title, 'Plain retrieve gets the lastest revision.');

    // We are only going to test properties that we set explicitly.
    $test_properties = array_keys($node);

    // Test node access
    $account = $this
      ->drupalCreateUser(array());
    $this
      ->serviceLogin($account->name, $account->pass_raw);
    $this
      ->create($resource, $node, 401);
    $this
      ->serviceLogout();

    // Test format access.
    $unpriv_account = $this
      ->drupalCreateUser(array(
      'create page content',
      'edit any page content',
      'delete any page content',
    ));
    $this
      ->serviceLogin($unpriv_account->name, $unpriv_account->pass_raw);
    $this
      ->create($resource, $node, 403);
    $this
      ->serviceLogout();

    // User with format access should be able to create the node.
    $account = $this
      ->drupalCreateUser(array(
      'bypass node access',
      'use text format full_html',
    ));
    $this
      ->serviceLogin($account->name, $account->pass_raw);

    // Set the 'author' or 'uid' property, depending on which controller we're using.
    if ($clean) {
      $node['author'] = $account->uid;
    }
    else {
      $node['uid'] = $account->uid;
    }

    // We cannot create a node with the clean controller because of [issue link]
    // @todo remove this conditional once that lands.
    $created_node = $this
      ->create($resource, $node);
    $this
      ->assertTrue($created_node['nid'], t('node create response has a node id'));
    $nid = $created_node['nid'];

    // Verify that the actual node was created.
    $drupal_node = $this
      ->nodeLoad($nid, $clean);
    $this
      ->assertNodeProperties($created_node, $drupal_node, $test_properties, 'Node CREATE response');
    $this
      ->assertNodeProperties($node, $drupal_node, $test_properties, 'Created node');

    // Test an update of the created node.
    // Change our title and body.
    $created_node['title'] = $this
      ->randomName(10);
    if ($clean) {
      $created_node['body']['value'] = $this
        ->randomname(50);
    }
    else {
      $created_node['body']['und'][0]['value'] = $this
        ->randomname(50);
    }
    $update_result = $this
      ->update($resource, $nid, $created_node, 200);
    $drupal_node = $this
      ->nodeLoad($nid, $clean);

    // @todo restore this once [insert issue -- generic-update-entity] lands
    if (!$clean) {
      $this
        ->assertNodeProperties($update_result, $drupal_node, $test_properties, 'Created node UPDATE response');
    }
    $this
      ->assertNodeProperties($created_node, $drupal_node, $test_properties, 'Updated created node');

    // Test retrieving the new node via services.
    $fetched_node = $this
      ->retrieve($resource, $nid);
    $this
      ->assertNodeProperties($fetched_node, $drupal_node, $test_properties, 'Node RETRIEVE response');

    // Test an update of the fetched node.
    // Change our title and body.
    $fetched_node['title'] = $this
      ->randomName(10);
    if ($clean) {
      $fetched_node['body']['value'] = $this
        ->randomname(50);
    }
    else {
      $fetched_node['body']['und'][0]['value'] = $this
        ->randomname(50);
    }
    $update_result = $this
      ->update($resource, $nid, $fetched_node, 200);
    $drupal_node = $this
      ->nodeLoad($nid, $clean);
    $this
      ->assertNodeProperties($update_result, $drupal_node, $test_properties, 'Fetched node UPDATE response');
    $this
      ->assertNodeProperties($fetched_node, $drupal_node, $test_properties, 'Updated fetched node');

    // Test updating with a disallowed text format in the body.
    $drupal_node = node_load($nid, NULL, TRUE);
    $drupal_node->body['und'][0]['format'] = filter_default_format();
    node_save($drupal_node);
    $this
      ->serviceLogout();
    $this
      ->serviceLogin($unpriv_account->name, $unpriv_account->pass_raw);
    $fetched_node['body']['format'] = 'full_html';
    $this
      ->update($resource, $nid, $fetched_node, 403);
    $drupal_node = node_load($nid, NULL, TRUE);
    $this
      ->assertEqual($drupal_node->body['und'][0]['format'], filter_default_format(), 'Unprivileged user was unable to update with disallowed text format.');

    // Test a delete of a node.
    $delete_result = $this
      ->delete($resource, $nid);

    // Confirm the node was deleted.
    $deleted_node = $this
      ->retrieve($resource, $nid, array(), 404);
    $drupal_node = node_load($nid, NULL, TRUE);
    $this
      ->assertFalse($drupal_node, 'The deleted node was removed from the db.');

    // Test creating a node without a body field.
    $node_sans_body = array(
      'type' => 'page',
      'title' => $this
        ->randomName(10),
    );

    // We need an author to create a node via clean controller
    // See https://drupal.org/node/1237014
    if ($clean) {
      $node_sans_body['author'] = $account->uid;
    }
    $created_node_sans_body = $this
      ->create($resource, $node_sans_body);
    $this
      ->assertTrue($created_node_sans_body['nid'], 'Created Node without body has a response with a node id');
    $drupal_node = $this
      ->nodeLoad($created_node_sans_body['nid'], $clean);
    $this
      ->assertNodeProperties($node_sans_body, $drupal_node, array_keys($node_sans_body), 'Created node without body');
  }

  /**
   * Create several revisions of a node.
   *
   * @param int $number
   *   The number of revisions to create.
   *
   * @return
   *   An array containing the original node and revisions.
   */
  protected function createNodeRevisions($number = 3) {
    $nodes = array();
    for ($i = 0; $i < $number; $i++) {
      if (empty($nodes)) {
        $nodes[] = $this
          ->drupalCreateNode(array(
          'title' => $this
            ->randomName(),
        ));
      }
      else {
        $node = end($nodes);
        $new_node = (object) array(
          'nid' => $node->nid,
          'vid' => $node->vid,
          'uid' => $node->uid,
          'type' => $node->type,
          'title' => $this
            ->randomName(),
          'revision' => 1,
        );
        node_save($new_node);
        $nodes[] = $new_node;
      }
    }
    return $nodes;
  }

  /**
   * Asserts that all properties in the list are the same in both versions
   * of the node.
   *
   * @param array $a
   *   One version of the node, converted to an array of values.
   * @param array $b
   *   The other version of the node, similarly converted.
   * @param $keys
   *   The list of properties to compare.
   * @param $msg
   *   An assertion message prefix.
   */
  protected function assertNodeProperties($a, $b, $keys, $msg) {

    // List of drupal fields which require text_format processing.
    $special_fields = array(
      'body',
    );
    foreach ($keys as $name) {
      if (in_array($name, $special_fields)) {

        // Clean controller.
        if (isset($aprop['format']) && isset($bprop['format'])) {

          // Compare the formatted values.
          $aprop = check_markup($aprop['value'], $aprop['format']);
          $bprop = check_markup($bprop['value'], $bprop['format']);
        }
        elseif (isset($aprop['und'][0]['format']) && isset($bprop['und'][0]['format'])) {

          // Compare the formatted values.
          $aprop = check_markup($aprop['und'][0]['value'], $aprop['und'][0]['format']);
          $bprop = check_markup($bprop['und'][0]['value'], $bprop['und'][0]['format']);
        }
      }
      else {
        $aprop = $a[$name];
        $bprop = $b[$name];
      }
      $this
        ->assertEqual($aprop, $bprop, "{$msg}: Property {$name} matches");
    }
  }

  /**
   * Helper function to load a node and process it so it matches the format
   * used by the service controller.
   *
   * @param $nid
   *   The node to load.
   * @param $clean
   *   If TRUE, use the ServicesResourceControllerClean class.
   * @return
   *   The loaded, processed node.
   */
  public function nodeLoad($nid, $clean = FALSE) {
    $node = $this
      ->unObject(node_load($nid, NULL, TRUE));
    if ($clean) {
      $node['author'] = $node['uid'];
      if (!empty($node['body'])) {
        $node['body'] = reset($node['body'][$node['language']]);
      }
    }
    return $node;
  }

}

Classes

Namesort descending Description
ServicesEntityGenericEntityResource Test resources on the Generic controller, using a test entity type.
ServicesEntityNodeResourceTest Tests entity_node services for both the generic and clean controller.
ServicesEntityTestHelper Services Entity Test Helper class.