PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/core/modules/rest/src/Tests/RESTTestBase.php

https://gitlab.com/reasonat/test8
PHP | 434 lines | 241 code | 46 blank | 147 comment | 19 complexity | 9d57180c73cf61449a303d342191abf0 MD5 | raw file
  1. <?php
  2. namespace Drupal\rest\Tests;
  3. use Drupal\node\NodeInterface;
  4. use Drupal\simpletest\WebTestBase;
  5. /**
  6. * Test helper class that provides a REST client method to send HTTP requests.
  7. */
  8. abstract class RESTTestBase extends WebTestBase {
  9. /**
  10. * The default serialization format to use for testing REST operations.
  11. *
  12. * @var string
  13. */
  14. protected $defaultFormat;
  15. /**
  16. * The default MIME type to use for testing REST operations.
  17. *
  18. * @var string
  19. */
  20. protected $defaultMimeType;
  21. /**
  22. * The entity type to use for testing.
  23. *
  24. * @var string
  25. */
  26. protected $testEntityType = 'entity_test';
  27. /**
  28. * The default authentication provider to use for testing REST operations.
  29. *
  30. * @var array
  31. */
  32. protected $defaultAuth;
  33. /**
  34. * The raw response body from http request operations.
  35. *
  36. * @var array
  37. */
  38. protected $responseBody;
  39. /**
  40. * Modules to install.
  41. *
  42. * @var array
  43. */
  44. public static $modules = array('rest', 'entity_test', 'node');
  45. protected function setUp() {
  46. parent::setUp();
  47. $this->defaultFormat = 'hal_json';
  48. $this->defaultMimeType = 'application/hal+json';
  49. $this->defaultAuth = array('cookie');
  50. // Create a test content type for node testing.
  51. $this->drupalCreateContentType(array('name' => 'resttest', 'type' => 'resttest'));
  52. }
  53. /**
  54. * Helper function to issue a HTTP request with simpletest's cURL.
  55. *
  56. * @param string|\Drupal\Core\Url $url
  57. * A Url object or system path.
  58. * @param string $method
  59. * HTTP method, one of GET, POST, PUT or DELETE.
  60. * @param string $body
  61. * The body for POST and PUT.
  62. * @param string $mime_type
  63. * The MIME type of the transmitted content.
  64. *
  65. * @return string
  66. * The content returned from the request.
  67. */
  68. protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
  69. if (!isset($mime_type)) {
  70. $mime_type = $this->defaultMimeType;
  71. }
  72. if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) {
  73. // GET the CSRF token first for writing requests.
  74. $token = $this->drupalGet('rest/session/token');
  75. }
  76. $url = $this->buildUrl($url);
  77. $curl_options = array();
  78. switch ($method) {
  79. case 'GET':
  80. // Set query if there are additional GET parameters.
  81. $curl_options = array(
  82. CURLOPT_HTTPGET => TRUE,
  83. CURLOPT_CUSTOMREQUEST => 'GET',
  84. CURLOPT_URL => $url,
  85. CURLOPT_NOBODY => FALSE,
  86. CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
  87. );
  88. break;
  89. case 'HEAD':
  90. $curl_options = array(
  91. CURLOPT_HTTPGET => FALSE,
  92. CURLOPT_CUSTOMREQUEST => 'HEAD',
  93. CURLOPT_URL => $url,
  94. CURLOPT_NOBODY => TRUE,
  95. CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
  96. );
  97. break;
  98. case 'POST':
  99. $curl_options = array(
  100. CURLOPT_HTTPGET => FALSE,
  101. CURLOPT_POST => TRUE,
  102. CURLOPT_POSTFIELDS => $body,
  103. CURLOPT_URL => $url,
  104. CURLOPT_NOBODY => FALSE,
  105. CURLOPT_HTTPHEADER => array(
  106. 'Content-Type: ' . $mime_type,
  107. 'X-CSRF-Token: ' . $token,
  108. ),
  109. );
  110. break;
  111. case 'PUT':
  112. $curl_options = array(
  113. CURLOPT_HTTPGET => FALSE,
  114. CURLOPT_CUSTOMREQUEST => 'PUT',
  115. CURLOPT_POSTFIELDS => $body,
  116. CURLOPT_URL => $url,
  117. CURLOPT_NOBODY => FALSE,
  118. CURLOPT_HTTPHEADER => array(
  119. 'Content-Type: ' . $mime_type,
  120. 'X-CSRF-Token: ' . $token,
  121. ),
  122. );
  123. break;
  124. case 'PATCH':
  125. $curl_options = array(
  126. CURLOPT_HTTPGET => FALSE,
  127. CURLOPT_CUSTOMREQUEST => 'PATCH',
  128. CURLOPT_POSTFIELDS => $body,
  129. CURLOPT_URL => $url,
  130. CURLOPT_NOBODY => FALSE,
  131. CURLOPT_HTTPHEADER => array(
  132. 'Content-Type: ' . $mime_type,
  133. 'X-CSRF-Token: ' . $token,
  134. ),
  135. );
  136. break;
  137. case 'DELETE':
  138. $curl_options = array(
  139. CURLOPT_HTTPGET => FALSE,
  140. CURLOPT_CUSTOMREQUEST => 'DELETE',
  141. CURLOPT_URL => $url,
  142. CURLOPT_NOBODY => FALSE,
  143. CURLOPT_HTTPHEADER => array('X-CSRF-Token: ' . $token),
  144. );
  145. break;
  146. }
  147. $this->responseBody = $this->curlExec($curl_options);
  148. // Ensure that any changes to variables in the other thread are picked up.
  149. $this->refreshVariables();
  150. $headers = $this->drupalGetHeaders();
  151. $this->verbose($method . ' request to: ' . $url .
  152. '<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
  153. '<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
  154. '<hr />Response body: ' . $this->responseBody);
  155. return $this->responseBody;
  156. }
  157. /**
  158. * Creates entity objects based on their types.
  159. *
  160. * @param string $entity_type
  161. * The type of the entity that should be created.
  162. *
  163. * @return \Drupal\Core\Entity\EntityInterface
  164. * The new entity object.
  165. */
  166. protected function entityCreate($entity_type) {
  167. return $this->container->get('entity_type.manager')
  168. ->getStorage($entity_type)
  169. ->create($this->entityValues($entity_type));
  170. }
  171. /**
  172. * Provides an array of suitable property values for an entity type.
  173. *
  174. * Required properties differ from entity type to entity type, so we keep a
  175. * minimum mapping here.
  176. *
  177. * @param string $entity_type
  178. * The type of the entity that should be created.
  179. *
  180. * @return array
  181. * An array of values keyed by property name.
  182. */
  183. protected function entityValues($entity_type) {
  184. switch ($entity_type) {
  185. case 'entity_test':
  186. return array(
  187. 'name' => $this->randomMachineName(),
  188. 'user_id' => 1,
  189. 'field_test_text' => array(0 => array(
  190. 'value' => $this->randomString(),
  191. 'format' => 'plain_text',
  192. )),
  193. );
  194. case 'node':
  195. return array('title' => $this->randomString(), 'type' => 'resttest');
  196. case 'node_type':
  197. return array(
  198. 'type' => 'article',
  199. 'name' => $this->randomMachineName(),
  200. );
  201. case 'user':
  202. return array('name' => $this->randomMachineName());
  203. case 'comment':
  204. return [
  205. 'subject' => $this->randomMachineName(),
  206. 'entity_type' => 'node',
  207. 'comment_type' => 'comment',
  208. 'comment_body' => $this->randomString(),
  209. 'entity_id' => 'invalid',
  210. 'field_name' => 'comment',
  211. ];
  212. default:
  213. return array();
  214. }
  215. }
  216. /**
  217. * Enables the REST service interface for a specific entity type.
  218. *
  219. * @param string|FALSE $resource_type
  220. * The resource type that should get REST API enabled or FALSE to disable all
  221. * resource types.
  222. * @param string $method
  223. * The HTTP method to enable, e.g. GET, POST etc.
  224. * @param string|array $format
  225. * (Optional) The serialization format, e.g. hal_json, or a list of formats.
  226. * @param array $auth
  227. * (Optional) The list of valid authentication methods.
  228. */
  229. protected function enableService($resource_type, $method = 'GET', $format = NULL, $auth = NULL) {
  230. // Enable REST API for this entity type.
  231. $config = $this->config('rest.settings');
  232. $settings = array();
  233. if ($resource_type) {
  234. if (is_array($format)) {
  235. $settings[$resource_type][$method]['supported_formats'] = $format;
  236. }
  237. else {
  238. if ($format == NULL) {
  239. $format = $this->defaultFormat;
  240. }
  241. $settings[$resource_type][$method]['supported_formats'][] = $format;
  242. }
  243. if ($auth == NULL) {
  244. $auth = $this->defaultAuth;
  245. }
  246. $settings[$resource_type][$method]['supported_auth'] = $auth;
  247. }
  248. $config->set('resources', $settings);
  249. $config->save();
  250. $this->rebuildCache();
  251. }
  252. /**
  253. * Rebuilds routing caches.
  254. */
  255. protected function rebuildCache() {
  256. // Rebuild routing cache, so that the REST API paths are available.
  257. $this->container->get('router.builder')->rebuild();
  258. }
  259. /**
  260. * {@inheritdoc}
  261. *
  262. * This method is overridden to deal with a cURL quirk: the usage of
  263. * CURLOPT_CUSTOMREQUEST cannot be unset on the cURL handle, so we need to
  264. * override it every time it is omitted.
  265. */
  266. protected function curlExec($curl_options, $redirect = FALSE) {
  267. if (!isset($curl_options[CURLOPT_CUSTOMREQUEST])) {
  268. if (!empty($curl_options[CURLOPT_HTTPGET])) {
  269. $curl_options[CURLOPT_CUSTOMREQUEST] = 'GET';
  270. }
  271. if (!empty($curl_options[CURLOPT_POST])) {
  272. $curl_options[CURLOPT_CUSTOMREQUEST] = 'POST';
  273. }
  274. }
  275. return parent::curlExec($curl_options, $redirect);
  276. }
  277. /**
  278. * Provides the necessary user permissions for entity operations.
  279. *
  280. * @param string $entity_type
  281. * The entity type.
  282. * @param string $operation
  283. * The operation, one of 'view', 'create', 'update' or 'delete'.
  284. *
  285. * @return array
  286. * The set of user permission strings.
  287. */
  288. protected function entityPermissions($entity_type, $operation) {
  289. switch ($entity_type) {
  290. case 'entity_test':
  291. switch ($operation) {
  292. case 'view':
  293. return array('view test entity');
  294. case 'create':
  295. case 'update':
  296. case 'delete':
  297. return array('administer entity_test content');
  298. }
  299. case 'node':
  300. switch ($operation) {
  301. case 'view':
  302. return array('access content');
  303. case 'create':
  304. return array('create resttest content');
  305. case 'update':
  306. return array('edit any resttest content');
  307. case 'delete':
  308. return array('delete any resttest content');
  309. }
  310. case 'comment':
  311. switch ($operation) {
  312. case 'view':
  313. return ['access comments'];
  314. case 'create':
  315. return ['post comments', 'skip comment approval'];
  316. case 'update':
  317. return ['edit own comments'];
  318. case 'delete':
  319. return ['administer comments'];
  320. }
  321. break;
  322. case 'user':
  323. switch ($operation) {
  324. case 'view':
  325. return ['access user profiles'];
  326. default:
  327. return ['administer users'];
  328. }
  329. }
  330. }
  331. /**
  332. * Loads an entity based on the location URL returned in the location header.
  333. *
  334. * @param string $location_url
  335. * The URL returned in the Location header.
  336. *
  337. * @return \Drupal\Core\Entity\Entity|FALSE.
  338. * The entity or FALSE if there is no matching entity.
  339. */
  340. protected function loadEntityFromLocationHeader($location_url) {
  341. $url_parts = explode('/', $location_url);
  342. $id = end($url_parts);
  343. return entity_load($this->testEntityType, $id);
  344. }
  345. /**
  346. * Remove node fields that can only be written by an admin user.
  347. *
  348. * @param \Drupal\node\NodeInterface $node
  349. * The node to remove fields where non-administrative users cannot write.
  350. *
  351. * @return \Drupal\node\NodeInterface
  352. * The node with removed fields.
  353. */
  354. protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
  355. $node->set('status', NULL);
  356. $node->set('created', NULL);
  357. $node->set('changed', NULL);
  358. $node->set('promote', NULL);
  359. $node->set('sticky', NULL);
  360. $node->set('revision_timestamp', NULL);
  361. $node->set('revision_log', NULL);
  362. $node->set('uid', NULL);
  363. return $node;
  364. }
  365. /**
  366. * Check to see if the HTTP request response body is identical to the expected
  367. * value.
  368. *
  369. * @param $expected
  370. * The first value to check.
  371. * @param $message
  372. * (optional) A message to display with the assertion. Do not translate
  373. * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
  374. * variables in the message text, not t(). If left blank, a default message
  375. * will be displayed.
  376. * @param $group
  377. * (optional) The group this message is in, which is displayed in a column
  378. * in test output. Use 'Debug' to indicate this is debugging output. Do not
  379. * translate this string. Defaults to 'Other'; most tests do not override
  380. * this default.
  381. *
  382. * @return bool
  383. * TRUE if the assertion succeeded, FALSE otherwise.
  384. */
  385. protected function assertResponseBody($expected, $message = '', $group = 'REST Response') {
  386. return $this->assertIdentical($expected, $this->responseBody, $message ? $message : strtr('Response body @expected (expected) is equal to @response (actual).', array('@expected' => var_export($expected, TRUE), '@response' => var_export($this->responseBody, TRUE))), $group);
  387. }
  388. }