PageRenderTime 59ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/inc/api/testing/case/functional.php

https://bitbucket.org/marcammann/sbb-api
PHP | 315 lines | 219 code | 15 blank | 81 comment | 2 complexity | cb6028f4229e2ccf9c1138cd1e431ff9 MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. /**
  3. * Base class for functional tests. Provides methods to trigger a
  4. * complete Okapi request without actually doing any HTTP request.
  5. * Instead api_controller::process() is called directly.
  6. */
  7. class api_testing_case_functional extends UnitTestCase {
  8. /** api_controller: Controller to handle requests. */
  9. protected $controller = null;
  10. /** DOMDocument: DOM returned by the previous request. */
  11. protected $responseDom = null;
  12. /** string: Original include path before setUp() was called. Used to
  13. recover the include path in the tearDown() method. */
  14. protected $includepathOriginal = '';
  15. /**
  16. * Sets up the testing environment for the functional tests.
  17. * Prepends the directory mocks/functional to the include
  18. * path to make sure that the mock api_model_factory and
  19. * api_response are used in all functional tests.
  20. */
  21. function setUp() {
  22. api_init::start();
  23. // Set include path to include mock objects.
  24. $this->includepathOriginal = get_include_path();
  25. set_include_path(dirname(__FILE__).'/../mocks/functional/:' . get_include_path());
  26. api_model_factory::reset();
  27. parent::setUp();
  28. }
  29. /**
  30. * Resets the testing environment. Reverts to the original include path.
  31. */
  32. function tearDown() {
  33. set_include_path($this->includepathOriginal);
  34. parent::tearDown();
  35. }
  36. /**
  37. * Executes the given request internally using the GET method.
  38. * @param $path string: Path relative to the application root to
  39. * request. This path is passed to the routing
  40. * engine. Path can include query string parameters.
  41. */
  42. protected function get($path) {
  43. $_SERVER['REQUEST_METHOD'] = 'GET';
  44. $this->request($path, array());
  45. }
  46. /**
  47. * Executes the given request internally using the HEAD method.
  48. * @param $path string: Path relative to the application root to
  49. * request. This path is passed to the routing
  50. * engine. Path can include query string parameters.
  51. */
  52. protected function head($path) {
  53. $_SERVER['REQUEST_METHOD'] = 'HEAD';
  54. $this->request($path, array());
  55. }
  56. /**
  57. * Executes the given request internally using the POST method.
  58. * @param $path string: Path relative to the application root to
  59. * request. This path is passed to the routing
  60. * engine. Path can include query string parameters.
  61. * @param $params array: POST parameters to pass to the request.
  62. */
  63. protected function post($path, $params) {
  64. $_SERVER['REQUEST_METHOD'] = 'POST';
  65. $this->request($path, $params);
  66. }
  67. /**
  68. * Executes the given request internally using the PUT method.
  69. * @param $path string: Path relative to the application root to
  70. * request. This path is passed to the routing
  71. * engine. Path can include query string parameters.
  72. * @param $params array: POST parameters to pass to the request.
  73. */
  74. protected function put($path, $params) {
  75. $_SERVER['REQUEST_METHOD'] = 'PUT';
  76. $this->request($path, $params);
  77. }
  78. /**
  79. * Executes the given request internally using the DELETE method.
  80. * @param $path string: Path relative to the application root to
  81. * request. This path is passed to the routing
  82. * engine. Path can include query string parameters.
  83. * @param $params array: POST parameters to pass to the request.
  84. */
  85. protected function delete($path) {
  86. $_SERVER['REQUEST_METHOD'] = 'DELETE';
  87. $this->request($path, array());
  88. }
  89. /**
  90. * Common request handling for get/post.
  91. * @param $path string: Path relative to the application root to
  92. * request. This path is passed to the routing
  93. * engine. Path can include query string parameters.
  94. * @param $params array: POST parameters to pass to the request.
  95. */
  96. private function request($path, $params) {
  97. $_SERVER["REQUEST_URI"] = $path;
  98. $components = parse_url($path);
  99. $_GET = $_POST = $_REQUEST = $_FILES = array();
  100. if (isset($components['query'])) {
  101. $query = array();
  102. parse_str($components['query'], $query);
  103. $_GET = $query;
  104. }
  105. $_POST = $params;
  106. $this->uploadFiles($_POST, $_FILES);
  107. $_REQUEST = array_merge($_GET, $_POST);
  108. api_request::getInstance(true);
  109. api_response::getInstance(true);
  110. $this->controller = new api_controller();
  111. $this->controller->process();
  112. $this->loadResponse();
  113. $this->removeUploadedFiles();
  114. }
  115. /**
  116. * Loads the response into the DOM.
  117. * May be overwritten in implementations where the response
  118. * is not expected to be XML.
  119. */
  120. protected function loadResponse() {
  121. $response = api_response::getInstance();
  122. $resp = $response->getContents();
  123. $this->responseDom = DOMDocument::loadXML($resp);
  124. $this->assertIsA($this->responseDom, 'DOMDocument',
  125. "The view didn't output valid XML. $resp");
  126. }
  127. /**
  128. * Takes all paramaters form the POST array whose value's
  129. * start with an `@' and interprets those as file uploads.
  130. * This is compatible with the way curl handles uploads.
  131. *
  132. * Example usage with file uploads:
  133. * \code
  134. * $this->post('/test', array(
  135. * 'Type' => 'IMAGE',
  136. * 'File' => '@vw_golf.jpg',
  137. * ));
  138. * \endcode
  139. *
  140. * This passes a normal POST parameter "Type" and additionally
  141. * uploads the picture vw_golf.jpg (which has to exist in the
  142. * current working directory for that example).
  143. */
  144. private function uploadFiles(&$post, &$files) {
  145. foreach ($post as $key => $value) {
  146. if (strlen($value) >= 1 && $value[0] == '@') {
  147. $orig_file = substr($value, 1);
  148. $upload_file = '';
  149. if (file_exists($orig_file)) {
  150. $upload_file = sys_get_temp_dir() . '/_file_' . count($files);
  151. copy($orig_file, $upload_file);
  152. }
  153. // Get MIME type
  154. if (function_exists('finfo_open')) {
  155. $finfo = finfo_open(FILEINFO_MIME);
  156. $type = finfo_file($finfo, $orig_file);
  157. finfo_close($finfo);
  158. } else if (function_exists('mime_content_type')) {
  159. $type = mime_content_type($orig_file);
  160. } else {
  161. $type = '';
  162. }
  163. // File upload
  164. $files[$key] = array(
  165. 'name' => basename($orig_file),
  166. 'type' => $type,
  167. 'size' => filesize($orig_file),
  168. 'tmp_name' => $upload_file,
  169. 'error' => 0,
  170. );
  171. unset($_POST[$key]);
  172. }
  173. }
  174. }
  175. /**
  176. * Remove all uploaded files from the current request.
  177. */
  178. private function removeUploadedFiles() {
  179. foreach ($_FILES as $key => $arr) {
  180. if (is_file($arr['tmp_name'])) {
  181. unlink($arr['tmp_name']);
  182. }
  183. }
  184. }
  185. /**
  186. * Constructs the correct URI for the given route path.
  187. * @param $route string: Relative URL from the application root.
  188. * @param $lang string: Language to include in the path.
  189. */
  190. protected function getURI($route, $lang = 'de') {
  191. return API_HOST . $lang . API_MOUNTPATH . substr($route, 1);
  192. }
  193. /**
  194. * Constructs the correct path relative to the root of the host for
  195. * the given route path. Prepends the mount path and language.
  196. * @param $route string: Relative URL from the application root.
  197. * @param $lang string: Language to include in the path.
  198. */
  199. protected function getPath($route, $lang = 'de') {
  200. return '/' . $lang . API_MOUNTPATH . substr($route, 1);
  201. }
  202. /**
  203. * Gets the DOM node matching the given XPath expression.
  204. * @param $xpath string: XPath expression to return node for.
  205. * @see api_helpers_xpath::getNode()
  206. */
  207. protected function getNode($xpath) {
  208. return api_helpers_xpath::getNode($this->responseDom, $xpath);
  209. }
  210. /**
  211. * Asserts that the given node exists.
  212. * @param $xpath string: XPath expression to test.
  213. * @param $message string: Message to output in case of failure.
  214. */
  215. public function assertNode($xpath, $message = null) {
  216. if ($message != null) {
  217. $message = "$message :: ";
  218. }
  219. $this->assertNotNull($this->getNode($xpath), "{$message}No node found for $xpath");
  220. }
  221. /**
  222. * Asserts that the given node does not exist.
  223. * @param $xpath string: XPath expression to test.
  224. * @param $message string: Message to output in case of failure.
  225. */
  226. public function assertNotNode($xpath, $message = null) {
  227. if ($message != null) {
  228. $message = "$message :: ";
  229. }
  230. $this->assertNull($this->getNode($xpath), "{$message}Node found for $xpath but none was expected.");
  231. }
  232. /**
  233. * Gets the first result of the current page by XPath.
  234. * @param $xpath string: XPath expression to return text for.
  235. * @see api_helpers_xpath::getText()
  236. */
  237. public function getText($xpath) {
  238. return api_helpers_xpath::getText($this->responseDom, $xpath);
  239. }
  240. /**
  241. * Asserts that the text retrieved by an XPath expression matches.
  242. * @param $xpath string: XPath expression to test.
  243. * @param $expected string: Expected value returned by the XPath
  244. * expression.
  245. * @param $message string: Message to output in case of failure.
  246. */
  247. public function assertText($xpath, $expected, $message = '%s') {
  248. return $this->assertEqual($expected, $this->getText($xpath), $message);
  249. }
  250. /**
  251. * Gets the first result of the current page by XPath.
  252. * @param $xpath string: XPath expression to return attribute for. The
  253. * XPath expression must contain two components
  254. * separated by `a'. First the expression to find
  255. * the node, then the attribute to return the value
  256. * for.
  257. * @see api_helpers_xpath::getAttribute()
  258. */
  259. public function getAttribute($xpath) {
  260. return api_helpers_xpath::getAttribute($this->responseDom, $xpath);
  261. }
  262. /**
  263. * Asserts that the attribute retrieved by an XPath expression matches.
  264. * @param $xpath string: XPath expression.
  265. * @see api_testing_case_functional::getAttribute().
  266. * @param $expected string: Expected value returned by the XPath
  267. * expression.
  268. * @param $message string: Message to output in case of failure.
  269. */
  270. public function assertAttribute($xpath, $expected, $message = '%s') {
  271. return $this->assertEqual($expected, $this->getAttribute($xpath), $message);
  272. }
  273. /**
  274. * Expect the next request to redirect to the given page.
  275. * @param $path string: Path to the page where the redirect should go to.
  276. * @param $absolute bool: True if the given path is absolute. Otherwise
  277. * the redirect is assumed to be inside the current
  278. * application relative to the application root.
  279. * @param $lang string: Language to which the redirect is expected. Only
  280. * relevant is $absolute=false.
  281. */
  282. public function expectRedirect($path, $absolute = false, $lang = 'de') {
  283. if (!$absolute) {
  284. $path = '/' . $lang . API_MOUNTPATH . substr($path, 1);
  285. }
  286. $this->expectException(new api_testing_exception("Redirect 301 => $path"));
  287. }
  288. }