api de gestion de ticket, basé sur php-crud-api. Le but est de décorrélé les outils de gestion des données, afin
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tests.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <?php
  2. if (!file_exists(__DIR__.'/config.php')) {
  3. copy(__DIR__.'/config.php.dist',__DIR__.'/config.php');
  4. }
  5. require __DIR__.'/config.php';
  6. require __DIR__.'/../api.php';
  7. class API
  8. {
  9. protected $test;
  10. protected $api;
  11. public function __construct($test)
  12. {
  13. $this->test = $test;
  14. }
  15. private function action($method,$url,$data='')
  16. {
  17. $url = parse_url($url);
  18. $query = isset($url['query'])?$url['query']:'';
  19. parse_str($query,$get);
  20. $data = 'data://text/plain;base64,'.base64_encode($data);
  21. $this->api = new PHP_CRUD_API(array(
  22. 'dbengine'=>PHP_CRUD_API_Config::$dbengine,
  23. 'hostname'=>PHP_CRUD_API_Config::$hostname,
  24. 'username'=>PHP_CRUD_API_Config::$username,
  25. 'password'=>PHP_CRUD_API_Config::$password,
  26. 'database'=>PHP_CRUD_API_Config::$database,
  27. // callbacks
  28. 'table_authorizer'=>function($action,$database,$table) { return true; },
  29. 'column_authorizer'=>function($action,$database,$table,$column) { return !($column=='password'&&$action=='list'); },
  30. 'record_filter'=>function($action,$database,$table) { return ($table=='posts')?array('id,ne,13'):false; },
  31. 'tenancy_function'=>function($action,$database,$table,$column) { return ($table=='users'&&$column=='id')?1:null; },
  32. 'input_sanitizer'=>function($action,$database,$table,$column,$type,$value) { return $value===null?null:strip_tags($value); },
  33. 'input_validator'=>function($action,$database,$table,$column,$type,$value,$context) { return ($column=='category_id' && !is_numeric($value))?'must be numeric':true; },
  34. // for tests
  35. 'method' =>$method,
  36. 'request' =>$url['path'],
  37. 'post'=>$data,
  38. 'get' =>$get,
  39. ));
  40. return $this;
  41. }
  42. public function get($url)
  43. {
  44. return $this->action('GET',$url);
  45. }
  46. public function post($url,$data)
  47. {
  48. return $this->action('POST',$url,$data);
  49. }
  50. public function put($url,$data)
  51. {
  52. return $this->action('PUT',$url,$data);
  53. }
  54. public function delete($url)
  55. {
  56. return $this->action('DELETE',$url);
  57. }
  58. public function options($url)
  59. {
  60. return $this->action('OPTIONS',$url);
  61. }
  62. public function expect($output,$error=false)
  63. {
  64. $exception = false;
  65. ob_start();
  66. try {
  67. $this->api->executeCommand();
  68. } catch (\Exception $e) {
  69. $exception = $e->getMessage();
  70. }
  71. $data = ob_get_contents();
  72. ob_end_clean();
  73. if ($exception) $this->test->assertEquals($error, $exception);
  74. else $this->test->assertEquals($output, $data);
  75. return $this;
  76. }
  77. }
  78. class PHP_CRUD_API_Test extends PHPUnit_Framework_TestCase
  79. {
  80. public static function setUpBeforeClass()
  81. {
  82. if (PHP_CRUD_API_Config::$database=='{{test_database}}') {
  83. die("Configure database in 'config.php' before running tests.\n");
  84. }
  85. $dbengine = PHP_CRUD_API_Config::$dbengine;
  86. $hostname = PHP_CRUD_API_Config::$hostname;
  87. $username = PHP_CRUD_API_Config::$username;
  88. $password = PHP_CRUD_API_Config::$password;
  89. $database = PHP_CRUD_API_Config::$database;
  90. $fixture = __DIR__.'/blog_'.strtolower($dbengine).'.sql';
  91. if ($dbengine == 'MySQL') {
  92. $link = mysqli_connect($hostname, $username, $password, $database);
  93. if (mysqli_connect_errno()) {
  94. die("Connect failed: ".mysqli_connect_error()."\n");
  95. }
  96. mysqli_set_charset($link,'utf8');
  97. $i=0;
  98. if (mysqli_multi_query($link, file_get_contents($fixture))) {
  99. do { $i++; mysqli_next_result($link); } while (mysqli_more_results($link));
  100. }
  101. if (mysqli_errno($link)) {
  102. die("Loading '$fixture' failed on statemement #$i with error:\n".mysqli_error($link)."\n");
  103. }
  104. mysqli_close($link);
  105. } elseif ($dbengine == 'SQLServer') {
  106. $connectionInfo = array();
  107. $connectionInfo['UID']=$username;
  108. $connectionInfo['PWD']=$password;
  109. $connectionInfo['Database']=$database;
  110. $connectionInfo['CharacterSet']='UTF-8';
  111. $conn = sqlsrv_connect( $hostname, $connectionInfo);
  112. if (!$conn) {
  113. die("Connect failed: ".print_r( sqlsrv_errors(), true));
  114. }
  115. $queries = preg_split('/\n\s*GO\s*\n/', file_get_contents($fixture));
  116. array_pop($queries);
  117. foreach ($queries as $i=>$query) {
  118. if (!sqlsrv_query($conn, $query)) {
  119. $i++;
  120. die("Loading '$fixture' failed on statemement #$i with error:\n".print_r( sqlsrv_errors(), true)."\n");
  121. }
  122. }
  123. sqlsrv_close($conn);
  124. } elseif ($dbengine == 'PostgreSQL') {
  125. $e = function ($v) { return str_replace(array('\'','\\'),array('\\\'','\\\\'),$v); };
  126. $hostname = $e($hostname);
  127. $database = $e($database);
  128. $username = $e($username);
  129. $password = $e($password);
  130. $conn_string = "host='$hostname' dbname='$database' user='$username' password='$password' options='--client_encoding=UTF8'";
  131. $db = pg_connect($conn_string);
  132. if (!$db) {
  133. die("Connect failed: ".print_r( sqlsrv_errors(), true));
  134. }
  135. $queries = preg_split('/;\s*\n/', file_get_contents($fixture));
  136. array_pop($queries);
  137. foreach ($queries as $i=>$query) {
  138. if (!pg_query($db, $query.';')) {
  139. $i++;
  140. die("Loading '$fixture' failed on statemement #$i with error:\n".print_r( pg_last_error($db), true)."\n");
  141. }
  142. }
  143. pg_close($db);
  144. } elseif ($dbengine == 'SQLite') {
  145. $db = new SQLite3($database);
  146. if (!$db) {
  147. die("Could not open '$database' SQLite database: ".SQLite3::lastErrorMsg().' ('.SQLite3::lastErrorCode().")\n");
  148. }
  149. $queries = preg_split('/;\s*\n/', file_get_contents($fixture));
  150. array_pop($queries);
  151. foreach ($queries as $i=>$query) {
  152. if (!$db->query($query.';')) {
  153. $i++;
  154. die("Loading '$fixture' failed on statemement #$i with error:\n".$db->lastErrorCode().': '.$db->lastErrorMsg()."\n");
  155. }
  156. }
  157. $db->close();
  158. }
  159. }
  160. public function testListPosts()
  161. {
  162. $test = new API($this);
  163. $test->get('/posts');
  164. $test->expect('{"posts":{"columns":["id","user_id","category_id","content"],"records":[["1","1","1","blog started"],["2","1","2","It works!"]]}}');
  165. }
  166. public function testListPostColumns()
  167. {
  168. $test = new API($this);
  169. $test->get('/posts?columns=id,content');
  170. $test->expect('{"posts":{"columns":["id","content"],"records":[["1","blog started"],["2","It works!"]]}}');
  171. }
  172. public function testListPostsWithTransform()
  173. {
  174. $test = new API($this);
  175. $test->get('/posts?transform=1');
  176. $test->expect('{"posts":[{"id":"1","user_id":"1","category_id":"1","content":"blog started"},{"id":"2","user_id":"1","category_id":"2","content":"It works!"}]}');
  177. }
  178. public function testReadPost()
  179. {
  180. $test = new API($this);
  181. $test->get('/posts/2');
  182. $test->expect('{"id":"2","user_id":"1","category_id":"2","content":"It works!"}');
  183. }
  184. public function testReadPostColumns()
  185. {
  186. $test = new API($this);
  187. $test->get('/posts/2?columns=id,content');
  188. $test->expect('{"id":"2","content":"It works!"}');
  189. }
  190. public function testAddPost()
  191. {
  192. $test = new API($this);
  193. $test->post('/posts','{"user_id":"1","category_id":"1","content":"test"}');
  194. $test->expect('3');
  195. }
  196. public function testEditPost()
  197. {
  198. $test = new API($this);
  199. $test->put('/posts/3','{"user_id":"1","category_id":"1","content":"test (edited)"}');
  200. $test->expect('1');
  201. $test->get('/posts/3');
  202. $test->expect('{"id":"3","user_id":"1","category_id":"1","content":"test (edited)"}');
  203. }
  204. public function testEditPostColumnsMissingField()
  205. {
  206. $test = new API($this);
  207. $test->put('/posts/3?columns=id,content','{"content":"test (edited 2)"}');
  208. $test->expect('1');
  209. $test->get('/posts/3');
  210. $test->expect('{"id":"3","user_id":"1","category_id":"1","content":"test (edited 2)"}');
  211. }
  212. public function testEditPostColumnsExtraField()
  213. {
  214. $test = new API($this);
  215. $test->put('/posts/3?columns=id,content','{"user_id":"2","content":"test (edited 3)"}');
  216. $test->expect('1');
  217. $test->get('/posts/3');
  218. $test->expect('{"id":"3","user_id":"1","category_id":"1","content":"test (edited 3)"}');
  219. }
  220. public function testEditPostWithUtf8Content()
  221. {
  222. $utf8 = json_encode('Hello world, Καλημέρα κόσμε, コンニチハ');
  223. $test = new API($this);
  224. $test->put('/posts/2','{"content":'.$utf8.'}');
  225. $test->expect('1');
  226. $test->get('/posts/2');
  227. $test->expect('{"id":"2","user_id":"1","category_id":"2","content":'.$utf8.'}');
  228. }
  229. public function testEditPostWithUtf8ContentWithPost()
  230. {
  231. $utf8 = '€ Hello world, Καλημέρα κόσμε, コンニチハ';
  232. $url_encoded = urlencode($utf8);
  233. $json_encoded = json_encode($utf8);
  234. $test = new API($this);
  235. $test->put('/posts/2','content='.$url_encoded);
  236. $test->expect('1');
  237. $test->get('/posts/2');
  238. $test->expect('{"id":"2","user_id":"1","category_id":"2","content":'.$json_encoded.'}');
  239. }
  240. public function testDeletePost()
  241. {
  242. $test = new API($this);
  243. $test->delete('/posts/3');
  244. $test->expect('1');
  245. $test->get('/posts/3');
  246. $test->expect(false,'Not found (object)');
  247. }
  248. public function testAddPostWithPost()
  249. {
  250. $test = new API($this);
  251. $test->post('/posts','user_id=1&category_id=1&content=test');
  252. $test->expect('4');
  253. }
  254. public function testEditPostWithPost()
  255. {
  256. $test = new API($this);
  257. $test->put('/posts/4','user_id=1&category_id=1&content=test+(edited)');
  258. $test->expect('1');
  259. $test->get('/posts/4');
  260. $test->expect('{"id":"4","user_id":"1","category_id":"1","content":"test (edited)"}');
  261. }
  262. public function testDeletePostWithPost()
  263. {
  264. $test = new API($this);
  265. $test->delete('/posts/4');
  266. $test->expect('1');
  267. $test->get('/posts/4');
  268. $test->expect(false,'Not found (object)');
  269. }
  270. public function testListWithPaginate()
  271. {
  272. $test = new API($this);
  273. for ($i=1;$i<=10;$i++) {
  274. $test->post('/posts','{"user_id":"1","category_id":"1","content":"#'.$i.'"}');
  275. $test->expect(4+$i);
  276. }
  277. $test->get('/posts?page=2,2&order=id');
  278. $test->expect('{"posts":{"columns":["id","user_id","category_id","content"],"records":[["5","1","1","#1"],["6","1","1","#2"]],"results":11}}');
  279. }
  280. public function testListWithPaginateLastPage()
  281. {
  282. $test = new API($this);
  283. $test->get('/posts?page=3,5&order=id');
  284. $test->expect('{"posts":{"columns":["id","user_id","category_id","content"],"records":[["14","1","1","#10"]],"results":11}}');
  285. }
  286. public function testListExampleFromReadme()
  287. {
  288. $test = new API($this);
  289. $test->get('/posts?include=categories,tags,comments&filter=id,eq,1');
  290. $test->expect('{"posts":{"columns":["id","user_id","category_id","content"],"records":[["1","1","1","blog started"]]},"post_tags":{"relations":{"post_id":"posts.id"},"columns":["id","post_id","tag_id"],"records":[["1","1","1"],["2","1","2"]]},"categories":{"relations":{"id":"posts.category_id"},"columns":["id","name","icon"],"records":[["1","anouncement",null]]},"tags":{"relations":{"id":"post_tags.tag_id"},"columns":["id","name"],"records":[["1","funny"],["2","important"]]},"comments":{"relations":{"post_id":"posts.id"},"columns":["id","post_id","message"],"records":[["1","1","great"],["2","1","fantastic"]]}}');
  291. }
  292. public function testListExampleFromReadmeWithTransform()
  293. {
  294. $test = new API($this);
  295. $test->get('/posts?include=categories,tags,comments&filter=id,eq,1&transform=1');
  296. $test->expect('{"posts":[{"id":"1","post_tags":[{"id":"1","post_id":"1","tag_id":"1","tags":[{"id":"1","name":"funny"}]},{"id":"2","post_id":"1","tag_id":"2","tags":[{"id":"2","name":"important"}]}],"comments":[{"id":"1","post_id":"1","message":"great"},{"id":"2","post_id":"1","message":"fantastic"}],"user_id":"1","category_id":"1","categories":[{"id":"1","name":"anouncement","icon":null}],"content":"blog started"}]}');
  297. }
  298. public function testEditCategoryWithBinaryContent()
  299. {
  300. $binary = base64_encode("\0abc\0\n\r\b\0");
  301. $base64url = rtrim(strtr($binary, '+/', '-_'), '=');
  302. $test = new API($this);
  303. $test->put('/categories/2','{"icon":"'.$base64url.'"}');
  304. $test->expect('1');
  305. $test->get('/categories/2');
  306. $test->expect('{"id":"2","name":"article","icon":"'.$binary.'"}');
  307. }
  308. public function testEditCategoryWithNull()
  309. {
  310. $test = new API($this);
  311. $test->put('/categories/2','{"icon":null}');
  312. $test->expect('1');
  313. $test->get('/categories/2');
  314. $test->expect('{"id":"2","name":"article","icon":null}');
  315. }
  316. public function testEditCategoryWithBinaryContentWithPost()
  317. {
  318. $binary = base64_encode("€ \0abc\0\n\r\b\0");
  319. $base64url = rtrim(strtr($binary, '+/', '-_'), '=');
  320. $test = new API($this);
  321. $test->put('/categories/2','icon='.$base64url);
  322. $test->expect('1');
  323. $test->get('/categories/2');
  324. $test->expect('{"id":"2","name":"article","icon":"'.$binary.'"}');
  325. }
  326. public function testEditCategoryWithNullWithPost()
  327. {
  328. $test = new API($this);
  329. $test->put('/categories/2','icon__is_null');
  330. $test->expect('1');
  331. $test->get('/categories/2');
  332. $test->expect('{"id":"2","name":"article","icon":null}');
  333. }
  334. public function testAddPostFailure()
  335. {
  336. $test = new API($this);
  337. $test->post('/posts','{"user_id":"a","category_id":"1","content":"tests"}');
  338. $test->expect('null');
  339. }
  340. public function testOptionsRequest()
  341. {
  342. $test = new API($this);
  343. $test->options('/posts/2');
  344. $test->expect('["Access-Control-Allow-Headers: Content-Type","Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE","Access-Control-Max-Age: 1728000"]',false);
  345. }
  346. public function testHidingPasswordColumn()
  347. {
  348. $test = new API($this);
  349. $test->get('/users?filter=id,eq,1&transform=1');
  350. $test->expect('{"users":[{"id":"1","username":"user1"}]}');
  351. }
  352. public function testValidatorErrorMessage()
  353. {
  354. $test = new API($this);
  355. $test->put('/posts/1','{"category_id":"a"}');
  356. $test->expect(false,'{"category_id":"must be numeric"}');
  357. }
  358. public function testSanitizerToStripTags()
  359. {
  360. $test = new API($this);
  361. $test->put('/categories/2','{"name":"<script>alert();</script>"}');
  362. $test->expect('1');
  363. $test->get('/categories/2');
  364. $test->expect('{"id":"2","name":"alert();","icon":null}');
  365. }
  366. public function testErrorOnInvalidJson()
  367. {
  368. $test = new API($this);
  369. $test->post('/posts','{"}');
  370. $test->expect(false,'Not found (input)');
  371. }
  372. public function testErrorOnDuplicatePrimaryKey()
  373. {
  374. $test = new API($this);
  375. $test->post('/posts','{"id":"1","user_id":"1","category_id":"1","content":"blog started (duplicate)"}');
  376. $test->expect('null');
  377. }
  378. public function testErrorOnFailingForeignKeyConstraint()
  379. {
  380. $test = new API($this);
  381. $test->post('/posts','{"user_id":"3","category_id":"1","content":"fk constraint"}');
  382. $test->expect('null');
  383. }
  384. public function testForJsonpCallbackOnList()
  385. {
  386. $test = new API($this);
  387. $test->get('/posts?filter[]=id,eq,1&callback=test_jsonp_fn&transform=1');
  388. $test->expect('test_jsonp_fn({"posts":[{"id":"1","user_id":"1","category_id":"1","content":"blog started"}]});');
  389. }
  390. public function testMissingIntermediateTable()
  391. {
  392. $test = new API($this);
  393. $test->get('/users?include=posts,tags');
  394. $test->expect('{"users":{"columns":["id","username"],"records":[["1","user1"]]},"posts":{"relations":{"user_id":"users.id"},"columns":["id","user_id","category_id","content"],"records":[["1","1","1","blog started"],["2","1","2","\u20ac Hello world, \u039a\u03b1\u03bb\u03b7\u03bc\u1f73\u03c1\u03b1 \u03ba\u1f79\u03c3\u03bc\u03b5, \u30b3\u30f3\u30cb\u30c1\u30cf"],["5","1","1","#1"],["6","1","1","#2"],["7","1","1","#3"],["8","1","1","#4"],["9","1","1","#5"],["10","1","1","#6"],["11","1","1","#7"],["12","1","1","#8"],["14","1","1","#10"]]},"post_tags":{"relations":{"post_id":"posts.id"},"columns":["id","post_id","tag_id"],"records":[["1","1","1"],["2","1","2"],["3","2","1"],["4","2","2"]]},"tags":{"relations":{"id":"post_tags.tag_id"},"columns":["id","name"],"records":[["1","funny"],["2","important"]]}}');
  395. }
  396. public function testEditUser()
  397. {
  398. $test = new API($this);
  399. $test->put('/users/1','{"password":"testtest"}');
  400. $test->expect('1');
  401. }
  402. public function testEditUserWithId()
  403. {
  404. if (PHP_CRUD_API_Config::$dbengine!='SQLServer') {
  405. $test = new API($this);
  406. $test->put('/users/1','{"id":"2","password":"testtest2"}');
  407. $test->expect('1');
  408. $test->get('/users/1');
  409. $test->expect('{"id":"1","username":"user1","password":"testtest2"}');
  410. }
  411. }
  412. public function testReadOtherUser()
  413. {
  414. $test = new API($this);
  415. $test->get('/users/2');
  416. $test->expect(false,'Not found (object)');
  417. }
  418. public function testEditOtherUser()
  419. {
  420. $test = new API($this);
  421. $test->put('/users/2','{"password":"testtest"}');
  422. $test->expect('0');
  423. }
  424. public function testFilterCategoryOnNullIcon()
  425. {
  426. $test = new API($this);
  427. $test->get('/categories?filter[]=icon,is,null&transform=1');
  428. $test->expect('{"categories":[{"id":"1","name":"anouncement","icon":null},{"id":"2","name":"alert();","icon":null}]}');
  429. }
  430. public function testFilterCategoryOnNotNullIcon()
  431. {
  432. $test = new API($this);
  433. $test->get('/categories?filter[]=icon,no,null&transform=1');
  434. $test->expect('{"categories":[]}');
  435. }
  436. public function testFilterPostsNotIn()
  437. {
  438. $test = new API($this);
  439. $test->get('/posts?filter[]=id,ni,1,2,3,4,7,8,9,10,11,12,13,14&transform=1');
  440. $test->expect('{"posts":[{"id":"5","user_id":"1","category_id":"1","content":"#1"},{"id":"6","user_id":"1","category_id":"1","content":"#2"}]}');
  441. }
  442. public function testColumnsWithTable()
  443. {
  444. $test = new API($this);
  445. $test->get('/posts?columns=posts.content&filter=id,eq,1&transform=1');
  446. $test->expect('{"posts":[{"content":"blog started"}]}');
  447. }
  448. public function testColumnsWithTableWildcard()
  449. {
  450. $test = new API($this);
  451. $test->get('/posts?columns=posts.*&filter=id,eq,1&transform=1');
  452. $test->expect('{"posts":[{"id":"1","user_id":"1","category_id":"1","content":"blog started"}]}');
  453. }
  454. public function testColumnsOnInclude()
  455. {
  456. $test = new API($this);
  457. $test->get('/posts?include=categories&columns=categories.name&filter=id,eq,1&transform=1');
  458. $test->expect('{"posts":[{"category_id":"1","categories":[{"id":"1","name":"anouncement"}]}]}');
  459. }
  460. public function testColumnsOnWrongInclude()
  461. {
  462. $test = new API($this);
  463. $test->get('/posts?include=categories&columns=categories&filter=id,eq,1&transform=1');
  464. $test->expect('{"posts":[{"category_id":"1","categories":[{"id":"1"}]}]}');
  465. }
  466. public function testColumnsOnImplicitJoin()
  467. {
  468. $test = new API($this);
  469. $test->get('/posts?include=tags&columns=posts.id,tags.name&filter=id,eq,1&transform=1');
  470. $test->expect('{"posts":[{"id":"1","post_tags":[{"post_id":"1","tag_id":"1","tags":[{"id":"1","name":"funny"}]},{"post_id":"1","tag_id":"2","tags":[{"id":"2","name":"important"}]}]}]}');
  471. }
  472. }