<?php

/**
 * @file
 * RESTful web services tests.
 */

class RestWSTestCase extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'RESTful web services tests',
      'description' => 'Tests CRUD operations via the REST web service.',
      'group' => 'Services',
    );
  }

  public function setUp() {
    parent::setUp('restws');
  }

  /**
   * CRUD tests for nodes.
   */
  public function testCRUD() {
    // Test Read.
    $title = $this->randomName(8);
    $node = $this->drupalCreateNode(array('title' => $title));
    $account = $this->drupalCreateUser(array('access resource node'));
    $this->drupalLogin($account);
    $result = $this->httpRequest('node/' . $node->nid . '.json', 'GET', $account);
    $node_array = drupal_json_decode($result);
    $this->assertEqual($node->title, $node_array['title'], 'Node title was received correctly.');
    $this->assertResponse('200', 'HTTP response code is correct.');
    $this->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');

    // Test Create.
    $account = $this->drupalCreateUser(array('access content',
      'bypass node access', 'access resource node'));
    $title = $this->randomName(8);
    $new_node = array(
      'body'      => array(LANGUAGE_NONE => array(array())),
      'title'     => $title,
      'type'      => 'page',
      'author'    => $account->uid,
    );
    $json = drupal_json_encode($new_node);
    $result = $this->httpRequest('node', 'POST', $account, $json);
    $result_array = drupal_json_decode($result);
    $nid = $result_array['id'];
    $node = node_load($nid);
    $this->assertEqual($title, $node->title, 'Node title in DB is equal to the new title.');
    $this->assertResponse('201', 'HTTP response code is correct.');
    $this->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');

    // Test Update.
    $new_title = $this->randomName(8);
    $json = drupal_json_encode(array('title' => $new_title));
    $this->httpRequest('node/' . $node->nid, 'PUT', $account, $json);
    // Clear the static cache, otherwise we won't see the update.
    $node = node_load($node->nid, NULL, TRUE);
    $this->assertEqual($new_title, $node->title, 'Node title in DB is equal to the updated title.');
    $this->assertResponse('200', 'HTTP response code is correct.');
    $this->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');

    // Test delete.
    $this->httpRequest('node/' . $node->nid, 'DELETE', $account);
    // Clear the static cache, otherwise we won't see the update.
    $node = node_load($node->nid, NULL, TRUE);
    $this->assertFalse($node, 'Node is not in the DB anymore.');
    $this->assertResponse('200', 'HTTP response code is correct.');
    $this->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');
  }

  /**
   * Tests bad requests.
   */
  public function testBadRequests() {
    // Assure that nodes without types won't be created.
    $account = $this->drupalCreateUser(array('access content', 'bypass node access', 'access resource node', 'administer users'));
    $title = $this->randomName(8);
    $new_node = array(
      'body'  => array(LANGUAGE_NONE => array(array())),
      'title' => $title,
    );
    $json = drupal_json_encode($new_node);
    $result = $this->httpRequest('node', 'POST', $account, $json);
    $node = entity_load('node', FALSE, array('title' => $title));
    $this->assertEqual(count($node), 0, "Node wasn't created");

    $this->assertResponse('406', 'Missing bundle: type');
  }

  /**
   * Tests access to restricted input formats.
   */
  public function testBadInputFormat() {
    module_enable(array('php'));
    // Reset the cache of valid permissions so that the PHP code format
    // permission exists.
    $this->checkPermissions(array(), TRUE);

    // Assure that users can't create nodes with unauthorized input formats.
    $unprivileged_account = $this->drupalCreateUser(array('bypass node access', 'access resource node'));
    $title = $this->randomName(8);
    $new_node = array(
      'body'  => array(
        'value' => $this->randomName(30),
        'format' => 'php_code',
      ),
      'title' => $title,
      'type' => 'page',
    );
    $json = drupal_json_encode($new_node);
    $result = $this->httpRequest('node', 'POST', $unprivileged_account, $json);
    $this->assertResponse('403');
    $this->assertEqual($result, '403 Forbidden: Not authorized to set property body');
    $node = entity_load('node', FALSE, array('title' => $title));
    $this->assertEqual(count($node), 0, "Node with unauthorized input format wasn't created");

    // Check that the format is allowed if the permission is present.
    $privileged_account = $this->drupalCreateUser(array('bypass node access', 'access resource node', 'use text format php_code'));
    $this->httpRequest('node', 'POST', $privileged_account, $json);
    $this->assertResponse('201');

    $node = entity_load('node', FALSE, array('title' => $title));
    $this->assertEqual(count($node), 1, "Node was created");
    $node = reset($node);
    $this->assertEqual($node->body[LANGUAGE_NONE][0]['value'], $new_node['body']['value'], 'The new node body has the correct value');
    $this->assertEqual($node->body[LANGUAGE_NONE][0]['format'], 'php_code', 'The new node has the correct format');

    // Check that users can't update nodes with unauthorized input formats.
    $node->body[LANGUAGE_NONE][0]['format'] = 'filtered_html';
    node_save($node);

    $new_body = $this->randomName(30);
    $update = array(
      'body'  => array(
        'value' => $new_body,
        'format' => 'php_code',
      ),
    );
    $json = drupal_json_encode($update);
    $result = $this->httpRequest('node/1', 'PUT', $unprivileged_account, $json);
    $this->assertResponse('403');
    $this->assertEqual($result, '403 Forbidden: Not authorized to set property body');
    $node = node_load(1, NULL, TRUE);
    $this->assertNotEqual($node->body[LANGUAGE_NONE][0]['value'], $new_body);
    $this->assertEqual($node->body[LANGUAGE_NONE][0]['format'], 'filtered_html');

    // Check that the format is allowed if the permission is present.
    $this->httpRequest('node/1', 'PUT', $privileged_account, $json);
    $this->assertResponse('200');
    $node = node_load(1, NULL, TRUE);
    $this->assertEqual($node->body[LANGUAGE_NONE][0]['value'], $new_body);
    $this->assertEqual($node->body[LANGUAGE_NONE][0]['format'], 'php_code');
  }

  /**
   * Test field level access restrictions.
   *
   * @see restws_test_field_access()
   */
  public function testFieldAccess() {
    module_enable(array('restws_test'));

    // Add text field to nodes.
    $field_info = array(
      'field_name' => 'field_text',
      'type' => 'text',
      'entity_types' => array('node'),
    );
    field_create_field($field_info);

    $instance = array(
      'label' => 'Text Field',
      'field_name' => 'field_text',
      'entity_type' => 'node',
      'bundle' => 'page',
      'settings' => array(),
      'required' => FALSE,
    );
    field_create_instance($instance);

    // A user without the "administer users" permission should not be able to
    // create a node with the access protected field.
    $unprivileged_account = $this->drupalCreateUser(array('bypass node access', 'access resource node'));
    $title = $this->randomName(8);
    $new_node = array(
      'title' => $title,
      'type' => 'page',
      'field_text' => 'test',
    );
    $json = drupal_json_encode($new_node);
    $this->httpRequest('node', 'POST', $unprivileged_account, $json);
    $this->assertResponse('403');
    $nodes = entity_load('node', FALSE, array('title' => $title));
    $this->assertEqual(count($nodes), 0, "Node with access protected field wasn't created");

    // Test again with the additional permission, this should work now.
    $privileged_account = $this->drupalCreateUser(array('bypass node access', 'access resource node', 'administer users'));
    $this->httpRequest('node', 'POST', $privileged_account, $json);
    $this->assertResponse('201');
    $node = node_load(1, NULL, TRUE);
    $this->assertEqual($node->field_text[LANGUAGE_NONE][0]['value'], 'test');

    // Update test: unpriviledged users should not be able to change the
    // protected field.
    $update = array('field_text' => 'newvalue');
    $json = drupal_json_encode($update);
    $result = $this->httpRequest('node/1', 'PUT', $unprivileged_account, $json);
    $this->assertResponse('403');
    $this->assertEqual($result, '403 Forbidden: Not authorized to set property field_text');
    $node = node_load(1, NULL, TRUE);
    $this->assertEqual($node->field_text[LANGUAGE_NONE][0]['value'], 'test');

    // Check that the update is allowed if the permission is present.
    $this->httpRequest('node/1', 'PUT', $privileged_account, $json);
    $this->assertResponse('200');
    $node = node_load(1, NULL, TRUE);
    $this->assertEqual($node->field_text[LANGUAGE_NONE][0]['value'], 'newvalue');
  }

  /**
   * Test entity references with an array which contains id, entity type.
   */
  public function testResourceArray() {
    $account = $this->drupalCreateUser(array(
      'access content', 'bypass node access', 'access resource node',
    ));
    $this->drupalLogin($account);
    $this->createTerm("foo");
    $this->createTerm("bar");

    // Test json create.
    $title = $this->randomName(8);
    $new_node = array(
      'body' => array(LANGUAGE_NONE => array(array())),
      'type' => 'article',
      'title' => 'foo',
      'field_tags' => array(
        array(
          'id' => '2', 'resource' => 'taxonomy_term',
        ), array(
          'id' => '1', 'resource' => 'taxonomy_term',
        ),
      ),
      'author' => array(
        'id' => $account->uid, 'resource' => 'user',
      ),
    );
    $json = drupal_json_encode($new_node);
    $result = $this->httpRequest('node', 'POST', $account, $json);
    $result_array = drupal_json_decode($result);
    $nid = $result_array['id'];
    $node = node_load($nid);
    $this->assertEqual($node->field_tags[LANGUAGE_NONE][0]['tid'], 2, 'Taxonomy term 1 was correctly added.');
    $this->assertEqual($node->field_tags[LANGUAGE_NONE][1]['tid'], 1, 'Taxonomy term 2 was correctly added.');

    // Test XML update.
    $xml = '
      <node>
        <title>bar</title>
        <type>article</type>
        <author resource="user" id="' . $account->uid . '">' . restws_resource_uri('user', $account->uid) . '</author>
        <field_tags>
          <item resource="taxonomy_term" id="1">' . restws_resource_uri('taxonomy_term', 1) . '</item>
          <item resource="taxonomy_term" id="2">' . restws_resource_uri('taxonomy_term', 2) . '</item>
        </field_tags>
      </node>';
    $result = $this->httpRequest('node/' . $nid, 'PUT', $account, $xml, 'xml');
    $node = node_load($nid, NULL, TRUE);
    $this->assertEqual($node->field_tags[LANGUAGE_NONE][0]['tid'], 1, 'Taxonomy term 1 was correctly updated.');
    $this->assertEqual($node->field_tags[LANGUAGE_NONE][1]['tid'], 2, 'Taxonomy term 2 was correctly updated.');

    // Test XML create.
    $result = $this->httpRequest('node', 'POST', $account, $xml, 'xml');
    $xml_element = simplexml_load_string($result);
    $nid = $xml_element->attributes()->id;
    $node = node_load((int) $nid, NULL, TRUE);
    $this->assertEqual($node->field_tags[LANGUAGE_NONE][0]['tid'], 1, 'Taxonomy term 1 was correctly added.');
    $this->assertEqual($node->field_tags[LANGUAGE_NONE][1]['tid'], 2, 'Taxonomy term 2 was correctly added.');
  }

  /**
   * Tests using the xml formatter.
   */
  public function testXmlFormatter() {
    // Test Read.
    $account = $this->drupalCreateUser(array('access content',
      'bypass node access', 'access resource node')
    );
    $this->drupalLogin($account);
    $title = $this->randomName(8);
    $node = $this->drupalCreateNode(array('title' => $title));

    $result = $this->drupalGet("node/$node->nid", array(), array('Accept: application/xml'));
    $this->assertRaw("<title>$title</title>", 'XML has been generated.');

    // Test update.
    $new_title = 'foo';
    $result = $this->httpRequest('node/' . $node->nid, 'PUT', $account, "<node><title>$new_title</title></node>", 'xml');
    // Clear the static cache, otherwise we won't see the update.
    $node = node_load($node->nid, NULL, TRUE);
    $this->assertEqual($new_title, $node->title, 'Node title in DB is equal to the updated title.');
    $this->assertResponse('200', 'HTTP response code is correct.');
    $this->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/xml', 'HTTP content type is correct.');
  }

  /**
   * Test requests to non-existing resources and other errors.
   */
  public function testErrors() {
    // Read non-existing resource.
    $random_nid = rand(1, 1000);
    $result = $this->httpRequest('node/' . $random_nid, 'GET');
    $this->assertResponse('404', 'HTTP response code is correct.');

    // Update a node with an unknown property.
    $account = $this->drupalCreateUser(array('access content',
      'bypass node access', 'access resource node')
    );
    $node = $this->drupalCreateNode();
    $property_name = $this->randomName(8);
    $json = drupal_json_encode(array($property_name => $property_name));
    $result = $this->httpRequest('node/' . $node->nid, 'PUT', $account, $json);
    $this->assertEqual($result, "406 Not Acceptable: Unknown data property $property_name.", 'Response body is correct');
    $this->assertResponse('406', 'HTTP response code is correct.');

    // Create a node with an unknown property.
    $title = $this->randomName(8);
    $new_node = array(
      'body'      => array(LANGUAGE_NONE => array(array())),
      'title'     => $this->randomName(8),
      'type'      => 'page',
      'author'    => $account->uid,
      $property_name => $property_name,
    );
    $json = drupal_json_encode($new_node);
    $result = $this->httpRequest('node', 'POST', $account, $json);
    $this->assertEqual($result, "406 Not Acceptable: Unknown data property $property_name.", 'Response body is correct');
    $this->assertResponse('406', 'HTTP response code is correct.');


    // Simulate a CSRF attack without the required token.
    $new_title = 'HACKED!';
    $json = drupal_json_encode(array('title' => $new_title));
    $this->curlExec(array(
      CURLOPT_HTTPGET => FALSE,
      CURLOPT_POST => TRUE,
      CURLOPT_CUSTOMREQUEST => 'POST',
      CURLOPT_POSTFIELDS => $json,
      CURLOPT_URL => url('node/' . $node->nid, array('absolute' => TRUE)),
      CURLOPT_NOBODY => FALSE,
      CURLOPT_HTTPHEADER => array('Content-Type: application/json'),
    ));
    $this->assertResponse(403);
    // Clear the static cache, otherwise we won't see the update.
    $node = node_load($node->nid, NULL, TRUE);
    $this->assertNotEqual($node->title, $new_title, 'Node title was not updated in the database.');

    // Simulate a cache poisoning attack where JSON could get into the page
    // cache.
    // Grant node resource access to anonymous users.
    user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access resource node'));
    // Enable page caching.
    variable_set('cache', 1);
    // Reset cURL here to delete any stored request settings.
    unset($this->curlHandle);
    // Request the JSON representation of the node.
    $this->drupalGet("node/$node->nid", array(), array('Accept: application/json'));
    $this->assertUrl("node/$node->nid.json", array(), 'Requesting a resource with JSON Accept header redirects to the .json URL.');
    // Now request the HTML representation.
    $result = $this->drupalGet("node/$node->nid");
    $content_type = $this->drupalGetHeader('content-type');
    $this->assertNotEqual($content_type, 'application/json', 'Content type header is not JSON after requesting HTML.');
    $this->assertNull(drupal_json_decode($result), 'Response body is not JSON after requesting HTML.');
  }

  /**
   * Tests resource querying.
   */
  public function testQuerying() {
    $account = $this->drupalCreateUser(array('access content',
        'bypass node access', 'access resource node')
    );
    $this->drupalLogin($account);

    $this->createTerm('foo');
    $nodes = array();
    for ($i = 0; $i < 5; $i++) {
      $title = "node$i";
      $node = array(
        'title' => $title,
        'type' => 'article',
      );
      // Add tags to the nodes 0 and 3.
      if ($i % 3 == 0) {
        $node['field_tags'][LANGUAGE_NONE][]['tid'] = 1;
      }

      // Set a body and the format to full_html for nodes 0 and 4.
      if ($i % 4 == 0) {
        $node['body'] = array(LANGUAGE_NONE => array(array('value' => l('foo', 'node'), 'format' => 'full_html')));
      }
      $nodes[$i] = $this->drupalCreateNode($node);
    }

    // Retrieve a list of nodes with json sorted by the title descending.
    $result = $this->httpRequest('node.json', 'GET', $account, array('sort' => 'title', 'direction' => 'DESC'));
    $result_nodes = drupal_json_decode($result);

    // Start by checking if the last node created is the first in the result.
    $i = 4;
    foreach ($result_nodes['list'] as $key => $node) {
      $this->assertEqual($nodes[$i]->title, $node['title'], "Node title $key was received correctly.");
      $i--;
    }
    $this->assertResponse('200', 'HTTP response code is correct.');
    $this->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');

    // Retrieve a list of nodes with xml.
    $result = $this->drupalGet('node', array(), array('Accept: application/xml'));
    $this->assertRaw('<list>', 'XML has been generated.');
    for ($i = 0; $i < 5; $i++) {
      $this->assertRaw("<title>node$i</title>", 'XML has been generated.');
    }

    // Query for a node with the title 'title1'.
    $result = $this->httpRequest('node.json', 'GET', $account, array('title' => 'node1'));
    $node = drupal_json_decode($result);
    $this->assertEqual($node['list'][0]['title'], 'node1', 'Node title was received correctly.');

    // Query for nodes with the taxonomy term foo which has the tid 1.
    $result = $this->httpRequest('node.json', 'GET', $account, array('field_tags' => '1'));
    $nodes = drupal_json_decode($result);

    $this->assertEqual($nodes['list'][0]['title'], 'node0', 'Right node title was received.');
    $this->assertEqual($nodes['list'][0]['field_tags'][0]['id'], 1, 'Node has taxonomy term.');

    $this->assertEqual($nodes['list'][1]['title'], 'node3', 'Right node title was received.');
    $this->assertEqual($nodes['list'][1]['field_tags'][0]['id'], 1, 'Node has taxonomy term.');

    // Test paging and limiting.
    $result = $this->httpRequest('node.json', 'GET', $account, array('limit' => 2, 'page' => 0));
    $result_nodes = drupal_json_decode($result);

    $this->assertTrue(count($result_nodes['list'] > 2), 'Only two elements where returned');

    $this->assertTrue($result_nodes['self'] == url('node', array('absolute' => TRUE, 'query' => array('limit' => 2, 'page' => 0))), 'Self link was generated');
    $this->assertTrue($result_nodes['first'] == url('node', array('absolute' => TRUE, 'query' => array('limit' => 2, 'page' => 0))), 'First link was generated');
    $this->assertTrue($result_nodes['last'] == url('node', array('absolute' => TRUE, 'query' => array('limit' => 2, 'page' => 2))), 'Last link was generated');
    $this->assertTrue($result_nodes['next'] == url('node', array('absolute' => TRUE, 'query' => array('limit' => 2, 'page' => 1))), 'Next link was generated');
    $this->assertFalse(isset($result_nodes['prev']), 'Prev link was not generated');

    $result = $this->httpRequest('node.json', 'GET', $account, array('limit' => 2, 'page' => 2));
    $result_nodes = drupal_json_decode($result);

    $this->assertFalse(isset($result_nodes['next']), 'Next link was not generated');
    $this->assertTrue($result_nodes['prev'] == url('node', array('absolute' => TRUE, 'query' => array('limit' => 2, 'page' => 1))), 'Prev link was generated');

    $result = $this->httpRequest('node.json', 'GET', $account, array('limit' => 2, 'page' => 5));
    $this->assertResponse('404', 'HTTP response code is correct.');

    // Test meta control full.
    $result = $this->httpRequest('node.json', 'GET', $account, array('full' => 0));
    $result_nodes = drupal_json_decode($result);

    foreach ($result_nodes['list'] as $node) {
      $this->assertTrue($node['uri'] == restws_resource_uri('node', $node['id']), 'Rerence to node ' . $node['id'] . ' was received correctly.');
    }

    // Test field column queries.
    $result = $this->httpRequest('node.json', 'GET', $account, array('body[format]' => 'full_html'));
    $result_nodes = drupal_json_decode($result);

    $this->assertEqual($result_nodes['list'][0]['title'], 'node0', 'Right node title was received.');
    $this->assertEqual($result_nodes['list'][0]['body']['format'], 'full_html', 'Node has body with full_html.');

    $this->assertEqual($result_nodes['list'][1]['title'], 'node4', 'Right node title was received.');
    $this->assertEqual($result_nodes['list'][1]['body']['format'], 'full_html', 'Node has body with full_html.');

    // Test SQL injection via order direction.
    $this->httpRequest('node.json', 'GET', $account, array('sort' => 'title', 'direction' => 'ASC; DELETE FROM ' . $this->databasePrefix . 'node WHERE nid = 1; --'));
    $node = node_load(1, NULL, TRUE);
    $this->assertNotEqual($node, FALSE, 'Node has not been deleted through SQL injection.');
  }

  /**
   * Test that sensitive user data is hidden for the "access user profiles"
   * permission and unpublished nodes.
   */
  public function testPermissions() {
    // Test other user with "access user profiles" permission.
    $test_user = $this->drupalCreateUser();
    $account = $this->drupalCreateUser(array('access resource user', 'access user profiles'));
    $result = $this->httpRequest('user/' . $test_user->uid . '.json', 'GET', $account);
    $user_array = drupal_json_decode($result);
    $this->assertEqual($test_user->name, $user_array['name'], 'User name was received correctly.');
    $this->assertFalse(isset($user_array['mail']), 'User mail is not present in the response.');
    $this->assertFalse(isset($user_array['roles']), 'User roles are not present in the response.');
    $this->assertResponse('200', 'HTTP response code is correct.');
    $this->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');

    // Test the own user - access to sensitive information should be allowed.
    $result = $this->httpRequest('user/' . $account->uid . '.json', 'GET', $account);
    $user_array = drupal_json_decode($result);
    $this->assertEqual($account->name, $user_array['name'], 'User name was received correctly.');
    $this->assertEqual($account->mail, $user_array['mail'], 'User mail is present in the response.');
    $role_keys = array_keys($account->roles);
    $this->assertEqual(sort($role_keys), sort($user_array['roles']), 'User roles are present in the response.');
    $this->assertResponse('200', 'HTTP response code is correct.');
    $this->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');

    // Test node access with an unpublished node.
    $this->drupalCreateNode(array('title' => 'foo', 'status' => 0));

    $this->drupalLogout();

    $account = $this->drupalCreateUser(array('access resource node'));
    $this->drupalLogin($account);

    $result = $this->httpRequest('node.json', 'GET', $account);
    $nodes = drupal_json_decode($result);
    // No node should be returned.
    $this->assertEqual(count($nodes['list']), 0, 'Unpublished node was successfully hidden.');
    $this->assertNoResponse(404, 'An empty collection should not cause a 404 response.');
  }

  /**
   * Test menu path resource setting.
   */
  public function testMenuPath() {
    $account = $this->drupalCreateUser(array('access content',
        'bypass node access', 'access resource node')
    );
    $this->drupalLogin($account);

    $title = $this->randomName(8);
    $node = $this->drupalCreateNode(array('title' => $title));

    module_enable(array('restws_test'), TRUE);

    foreach (array('bar', 'foo/bar', 'foo/bar/node', 'api-v1.2', 'this/is/a/really/long/path') as $menu_path) {
      // Register the menu_path for this test.
      variable_set('restws_test_menu_path', $menu_path);
      variable_set('menu_rebuild_needed', TRUE);

      // Verify that the query uri is working.
      $result = $this->drupalGet($menu_path . '.json');
      $this->assertResponse(200);
      $result = drupal_json_decode($result);
      $this->assertEqual($result['list'][0]['title'], $title);

      // Verify that the view uri is working.
      $result = $this->drupalGet($menu_path . '/1.json');
      $this->assertResponse(200);
      $result = drupal_json_decode($result);
      $this->assertEqual($result['title'], $title);
   }
  }

  /**
   * Creates a term.
   */
  protected function createTerm($term_name) {
    $term = new stdClass();
    $term->name = $term_name;
    $term->vid = 1;
    taxonomy_term_save($term);
  }

  /**
   * Helper function to issue a HTTP request with simpletest's cURL.
   *
   * @param array $body
   *   Either the body for POST and PUT or additional URL parameters for GET.
   */
  protected function httpRequest($url, $method, $account = NULL, $body = NULL, $format = 'json') {
    if (isset($account)) {
      unset($this->curlHandle);
      $this->drupalLogin($account);
    }
    if (in_array($method, array('POST', 'PUT', 'DELETE'))) {
      // GET the CSRF token first for writing requests.
      $token = $this->drupalGet('restws/session/token');
    }
    switch ($method) {
      case 'GET':
        // Set query if there are addition GET parameters.
        $options = isset($body) ? array('absolute' => TRUE, 'query' => $body) : array('absolute' => TRUE);
        $curl_options = array(
          CURLOPT_HTTPGET => TRUE,
          CURLOPT_URL => url($url, $options),
          CURLOPT_NOBODY => FALSE,
        );
        break;

      case 'POST':
        $curl_options = array(
          CURLOPT_HTTPGET => FALSE,
          CURLOPT_POST => TRUE,
          CURLOPT_POSTFIELDS => $body,
          CURLOPT_URL => url($url, array('absolute' => TRUE)),
          CURLOPT_NOBODY => FALSE,
          CURLOPT_HTTPHEADER => array(
            'Content-Type: application/' . $format,
            'X-CSRF-Token: ' . $token,
          ),
        );
        break;

      case 'PUT':
        $curl_options = array(
          CURLOPT_HTTPGET => FALSE,
          CURLOPT_CUSTOMREQUEST => 'PUT',
          CURLOPT_POSTFIELDS => $body,
          CURLOPT_URL => url($url, array('absolute' => TRUE)),
          CURLOPT_NOBODY => FALSE,
          CURLOPT_HTTPHEADER => array(
            'Content-Type: application/' . $format,
            'X-CSRF-Token: ' . $token,
          ),
        );
        break;

      case 'DELETE':
        $curl_options = array(
          CURLOPT_HTTPGET => FALSE,
          CURLOPT_CUSTOMREQUEST => 'DELETE',
          CURLOPT_URL => url($url, array('absolute' => TRUE)),
          CURLOPT_NOBODY => FALSE,
          CURLOPT_HTTPHEADER => array('X-CSRF-Token: ' . $token),
        );
        break;
    }

    $response = $this->curlExec($curl_options);
    $headers = $this->drupalGetHeaders();
    $headers = implode("\n", $headers);

    $this->verbose($method . ' request to: ' . $url .
      '<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
      '<hr />Response headers: ' . $headers .
      '<hr />Response body: ' . $response);

    return $response;
  }
}
