test_ari.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2013, Digium, Inc.
  5. *
  6. * David M. Lee, II <dlee@digium.com>
  7. *
  8. * See http://www.asterisk.org for more information about
  9. * the Asterisk project. Please do not directly contact
  10. * any of the maintainers of this project for assistance;
  11. * the project provides a web site, mailing lists and IRC
  12. * channels for your use.
  13. *
  14. * This program is free software, distributed under the terms of
  15. * the GNU General Public License Version 2. See the LICENSE file
  16. * at the top of the source tree.
  17. */
  18. /*!
  19. * \file \brief Test ARI API.
  20. * \author\verbatim David M. Lee, II <dlee@digium.com> \endverbatim
  21. *
  22. * \ingroup tests
  23. */
  24. /*** MODULEINFO
  25. <depend>TEST_FRAMEWORK</depend>
  26. <depend>res_ari</depend>
  27. <support_level>core</support_level>
  28. ***/
  29. #include "asterisk.h"
  30. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  31. #include "asterisk/module.h"
  32. #include "asterisk/test.h"
  33. #include "asterisk/ari.h"
  34. /*!@{*/
  35. /*!
  36. * \internal
  37. * The following code defines a simple RESTful API for unit testing. The
  38. * response encodes the inputs of the invocation. The invocation_count
  39. * counter is also incremented.
  40. *
  41. * - /foo (GET)
  42. * - /foo/bar (GET, POST)
  43. * - /foo/{bam} (GET)
  44. * - /foo/{bam}/bang (GET, POST, DE1LETE)
  45. */
  46. static int invocation_count;
  47. /*!
  48. * \internal
  49. * Shared code for all handlers
  50. */
  51. static void handler(const char *name,
  52. int response_code,
  53. struct ast_variable *get_params,
  54. struct ast_variable *path_vars,
  55. struct ast_variable *headers,
  56. struct ast_json *body,
  57. struct ast_ari_response *response)
  58. {
  59. struct ast_json *message = ast_json_pack("{s: s, s: {}, s: {}, s: {}}",
  60. "name", name,
  61. "get_params",
  62. "path_vars",
  63. "headers");
  64. struct ast_json *get_params_obj = ast_json_object_get(message, "get_params");
  65. struct ast_json *path_vars_obj = ast_json_object_get(message, "path_vars");
  66. struct ast_json *headers_obj = ast_json_object_get(message, "headers");
  67. for (; get_params != NULL; get_params = get_params->next) {
  68. ast_json_object_set(get_params_obj, get_params->name, ast_json_string_create(get_params->value));
  69. }
  70. for (; path_vars != NULL; path_vars = path_vars->next) {
  71. ast_json_object_set(path_vars_obj, path_vars->name, ast_json_string_create(path_vars->value));
  72. }
  73. for (; headers != NULL; headers = headers->next) {
  74. ast_json_object_set(headers_obj, headers->name, ast_json_string_create(headers->value));
  75. }
  76. ++invocation_count;
  77. response->response_code = response_code;
  78. response->message = message;
  79. }
  80. /*!
  81. * \internal
  82. * Macro to reduce the handler definition boiler-plate.
  83. */
  84. #define HANDLER(name, response_code) \
  85. static void name(struct ast_tcptls_session_instance *ser, \
  86. struct ast_variable *get_params, \
  87. struct ast_variable *path_vars, \
  88. struct ast_variable *headers, \
  89. struct ast_json *body, \
  90. struct ast_ari_response *response) \
  91. { \
  92. handler(#name, response_code, get_params, path_vars, headers, body, response); \
  93. }
  94. HANDLER(bang_get, 200)
  95. HANDLER(bang_post, 200)
  96. HANDLER(bang_delete, 204)
  97. HANDLER(bar_get, 200)
  98. HANDLER(bar_post, 200)
  99. HANDLER(bam_get, 200)
  100. HANDLER(foo_get, 200)
  101. static struct stasis_rest_handlers bang = {
  102. .path_segment = "bang",
  103. .callbacks = {
  104. [AST_HTTP_GET] = bang_get,
  105. [AST_HTTP_POST] = bang_post,
  106. [AST_HTTP_DELETE] = bang_delete,
  107. },
  108. .num_children = 0
  109. };
  110. static struct stasis_rest_handlers bar = {
  111. .path_segment = "bar",
  112. .callbacks = {
  113. [AST_HTTP_GET] = bar_get,
  114. [AST_HTTP_POST] = bar_post,
  115. },
  116. .num_children = 0
  117. };
  118. static struct stasis_rest_handlers bam = {
  119. .path_segment = "bam",
  120. .is_wildcard = 1,
  121. .callbacks = {
  122. [AST_HTTP_GET] = bam_get,
  123. },
  124. .num_children = 1,
  125. .children = { &bang }
  126. };
  127. static struct stasis_rest_handlers test_root = {
  128. .path_segment = "foo",
  129. .callbacks = {
  130. [AST_HTTP_GET] = foo_get,
  131. },
  132. .num_children = 3,
  133. .children = { &bar, &bam, &bang }
  134. };
  135. /*!@}*/
  136. /*!
  137. * \internal
  138. * \c ast_ari_response constructor.
  139. */
  140. static struct ast_ari_response *response_alloc(void)
  141. {
  142. struct ast_ari_response *resp = ast_calloc(1, sizeof(struct ast_ari_response));
  143. resp->headers = ast_str_create(24);
  144. return resp;
  145. }
  146. /*!
  147. * \internal
  148. * \c ast_ari_response destructor.
  149. */
  150. static void response_free(struct ast_ari_response *resp)
  151. {
  152. if (!resp) {
  153. return;
  154. }
  155. ast_free(resp->headers);
  156. ast_json_unref(resp->message);
  157. ast_free(resp);
  158. }
  159. /*!
  160. * \ internal
  161. * Setup test fixture for invocation tests.
  162. */
  163. static void *setup_invocation_test(void) {
  164. int r;
  165. invocation_count = 0;
  166. r = ast_ari_add_handler(&test_root);
  167. ast_assert(r == 0);
  168. return &invocation_count;
  169. }
  170. /*!
  171. * \ internal
  172. * Tear down test fixture for invocation tests.
  173. */
  174. static void tear_down_invocation_test(void *ignore) {
  175. if (!ignore) {
  176. return;
  177. }
  178. ast_ari_remove_handler(&test_root);
  179. }
  180. AST_TEST_DEFINE(get_docs)
  181. {
  182. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  183. RAII_VAR(struct ast_variable *, headers, NULL, ast_variables_destroy);
  184. struct ast_json *basePathJson;
  185. const char *basePath;
  186. switch (cmd) {
  187. case TEST_INIT:
  188. info->name = __func__;
  189. info->category = "/res/ari/";
  190. info->summary = "Test simple API get.";
  191. info->description = "Test ARI binding logic.";
  192. return AST_TEST_NOT_RUN;
  193. case TEST_EXECUTE:
  194. break;
  195. }
  196. response = response_alloc();
  197. headers = ast_variable_new("Host", "stasis.asterisk.org", __FILE__);
  198. ast_ari_get_docs("resources.json", "", headers, response);
  199. ast_test_validate(test, 200 == response->response_code);
  200. /* basePath should be relative to the Host header */
  201. basePathJson = ast_json_object_get(response->message, "basePath");
  202. ast_test_validate(test, NULL != basePathJson);
  203. basePath = ast_json_string_get(basePathJson);
  204. ast_test_validate(test, 0 == strcmp("http://stasis.asterisk.org/ari", basePath));
  205. return AST_TEST_PASS;
  206. }
  207. AST_TEST_DEFINE(get_docs_nohost)
  208. {
  209. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  210. struct ast_variable *headers = NULL;
  211. struct ast_json *basePathJson;
  212. switch (cmd) {
  213. case TEST_INIT:
  214. info->name = __func__;
  215. info->category = "/res/ari/";
  216. info->summary = "Test API get without a Host header";
  217. info->description = "Test ARI binding logic.";
  218. return AST_TEST_NOT_RUN;
  219. case TEST_EXECUTE:
  220. break;
  221. }
  222. response = response_alloc();
  223. ast_ari_get_docs("resources.json", "", headers, response);
  224. ast_test_validate(test, 200 == response->response_code);
  225. /* basePath should be relative to the Host header */
  226. basePathJson = ast_json_object_get(response->message, "basePath");
  227. ast_test_validate(test, NULL == basePathJson);
  228. return AST_TEST_PASS;
  229. }
  230. AST_TEST_DEFINE(get_docs_notfound)
  231. {
  232. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  233. struct ast_variable *headers = NULL;
  234. switch (cmd) {
  235. case TEST_INIT:
  236. info->name = __func__;
  237. info->category = "/res/ari/";
  238. info->summary = "Test API get for invalid resource";
  239. info->description = "Test ARI binding logic.";
  240. return AST_TEST_NOT_RUN;
  241. case TEST_EXECUTE:
  242. break;
  243. }
  244. response = response_alloc();
  245. ast_ari_get_docs("i-am-not-a-resource.json", "", headers, response);
  246. ast_test_validate(test, 404 == response->response_code);
  247. return AST_TEST_PASS;
  248. }
  249. AST_TEST_DEFINE(get_docs_hackerz)
  250. {
  251. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  252. struct ast_variable *headers = NULL;
  253. switch (cmd) {
  254. case TEST_INIT:
  255. info->name = __func__;
  256. info->category = "/res/ari/";
  257. info->summary = "Test API get for a file outside the rest-api path";
  258. info->description = "Test ARI binding logic.";
  259. return AST_TEST_NOT_RUN;
  260. case TEST_EXECUTE:
  261. break;
  262. }
  263. response = response_alloc();
  264. ast_ari_get_docs("../../../../sbin/asterisk", "", headers, response);
  265. ast_test_validate(test, 404 == response->response_code);
  266. return AST_TEST_PASS;
  267. }
  268. AST_TEST_DEFINE(invoke_get)
  269. {
  270. RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
  271. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  272. RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
  273. struct ast_variable *get_params = NULL;
  274. struct ast_variable *headers = NULL;
  275. switch (cmd) {
  276. case TEST_INIT:
  277. info->name = __func__;
  278. info->category = "/res/ari/";
  279. info->summary = "Test simple GET of an HTTP resource.";
  280. info->description = "Test ARI binding logic.";
  281. return AST_TEST_NOT_RUN;
  282. case TEST_EXECUTE:
  283. break;
  284. }
  285. fixture = setup_invocation_test();
  286. response = response_alloc();
  287. get_params = ast_variable_new("get1", "get-one", __FILE__);
  288. ast_assert(get_params != NULL);
  289. get_params->next = ast_variable_new("get2", "get-two", __FILE__);
  290. ast_assert(get_params->next != NULL);
  291. headers = ast_variable_new("head1", "head-one", __FILE__);
  292. ast_assert(headers != NULL);
  293. headers->next = ast_variable_new("head2", "head-two", __FILE__);
  294. ast_assert(headers->next != NULL);
  295. expected = ast_json_pack("{s: s, s: {s: s, s: s}, s: {s: s, s: s}, s: {}}",
  296. "name", "foo_get",
  297. "get_params",
  298. "get1", "get-one",
  299. "get2", "get-two",
  300. "headers",
  301. "head1", "head-one",
  302. "head2", "head-two",
  303. "path_vars");
  304. ast_ari_invoke(NULL, "foo", AST_HTTP_GET, get_params, headers,
  305. ast_json_null(), response);
  306. ast_test_validate(test, 1 == invocation_count);
  307. ast_test_validate(test, 200 == response->response_code);
  308. ast_test_validate(test, ast_json_equal(expected, response->message));
  309. return AST_TEST_PASS;
  310. }
  311. AST_TEST_DEFINE(invoke_wildcard)
  312. {
  313. RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
  314. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  315. RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
  316. struct ast_variable *get_params = NULL;
  317. struct ast_variable *headers = NULL;
  318. switch (cmd) {
  319. case TEST_INIT:
  320. info->name = __func__;
  321. info->category = "/res/ari/";
  322. info->summary = "Test GET of a wildcard resource.";
  323. info->description = "Test ARI binding logic.";
  324. return AST_TEST_NOT_RUN;
  325. case TEST_EXECUTE:
  326. break;
  327. }
  328. fixture = setup_invocation_test();
  329. response = response_alloc();
  330. expected = ast_json_pack("{s: s, s: {}, s: {}, s: {s: s}}",
  331. "name", "bam_get",
  332. "get_params",
  333. "headers",
  334. "path_vars",
  335. "bam", "foshizzle");
  336. ast_ari_invoke(NULL, "foo/foshizzle", AST_HTTP_GET, get_params, headers,
  337. ast_json_null(), response);
  338. ast_test_validate(test, 1 == invocation_count);
  339. ast_test_validate(test, 200 == response->response_code);
  340. ast_test_validate(test, ast_json_equal(expected, response->message));
  341. return AST_TEST_PASS;
  342. }
  343. AST_TEST_DEFINE(invoke_delete)
  344. {
  345. RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
  346. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  347. RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
  348. struct ast_variable *get_params = NULL;
  349. struct ast_variable *headers = NULL;
  350. switch (cmd) {
  351. case TEST_INIT:
  352. info->name = __func__;
  353. info->category = "/res/ari/";
  354. info->summary = "Test DELETE of an HTTP resource.";
  355. info->description = "Test ARI binding logic.";
  356. return AST_TEST_NOT_RUN;
  357. case TEST_EXECUTE:
  358. break;
  359. }
  360. fixture = setup_invocation_test();
  361. response = response_alloc();
  362. expected = ast_json_pack("{s: s, s: {}, s: {}, s: {s: s}}",
  363. "name", "bang_delete",
  364. "get_params",
  365. "headers",
  366. "path_vars",
  367. "bam", "foshizzle");
  368. ast_ari_invoke(NULL, "foo/foshizzle/bang", AST_HTTP_DELETE, get_params, headers,
  369. ast_json_null(), response);
  370. ast_test_validate(test, 1 == invocation_count);
  371. ast_test_validate(test, 204 == response->response_code);
  372. ast_test_validate(test, ast_json_equal(expected, response->message));
  373. return AST_TEST_PASS;
  374. }
  375. AST_TEST_DEFINE(invoke_post)
  376. {
  377. RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
  378. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  379. RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
  380. struct ast_variable *get_params = NULL;
  381. struct ast_variable *headers = NULL;
  382. switch (cmd) {
  383. case TEST_INIT:
  384. info->name = __func__;
  385. info->category = "/res/ari/";
  386. info->summary = "Test POST of an HTTP resource.";
  387. info->description = "Test ARI binding logic.";
  388. return AST_TEST_NOT_RUN;
  389. case TEST_EXECUTE:
  390. break;
  391. }
  392. fixture = setup_invocation_test();
  393. response = response_alloc();
  394. get_params = ast_variable_new("get1", "get-one", __FILE__);
  395. ast_assert(get_params != NULL);
  396. get_params->next = ast_variable_new("get2", "get-two", __FILE__);
  397. ast_assert(get_params->next != NULL);
  398. headers = ast_variable_new("head1", "head-one", __FILE__);
  399. ast_assert(headers != NULL);
  400. headers->next = ast_variable_new("head2", "head-two", __FILE__);
  401. ast_assert(headers->next != NULL);
  402. expected = ast_json_pack("{s: s, s: {s: s, s: s}, s: {s: s, s: s}, s: {}}",
  403. "name", "bar_post",
  404. "get_params",
  405. "get1", "get-one",
  406. "get2", "get-two",
  407. "headers",
  408. "head1", "head-one",
  409. "head2", "head-two",
  410. "path_vars");
  411. ast_ari_invoke(NULL, "foo/bar", AST_HTTP_POST, get_params, headers,
  412. ast_json_null(), response);
  413. ast_test_validate(test, 1 == invocation_count);
  414. ast_test_validate(test, 200 == response->response_code);
  415. ast_test_validate(test, ast_json_equal(expected, response->message));
  416. return AST_TEST_PASS;
  417. }
  418. AST_TEST_DEFINE(invoke_bad_post)
  419. {
  420. RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
  421. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  422. struct ast_variable *get_params = NULL;
  423. struct ast_variable *headers = NULL;
  424. switch (cmd) {
  425. case TEST_INIT:
  426. info->name = __func__;
  427. info->category = "/res/ari/";
  428. info->summary = "Test POST on a resource that doesn't support it.";
  429. info->description = "Test ARI binding logic.";
  430. return AST_TEST_NOT_RUN;
  431. case TEST_EXECUTE:
  432. break;
  433. }
  434. fixture = setup_invocation_test();
  435. response = response_alloc();
  436. ast_ari_invoke(NULL, "foo", AST_HTTP_POST, get_params, headers,
  437. ast_json_null(), response);
  438. ast_test_validate(test, 0 == invocation_count);
  439. ast_test_validate(test, 405 == response->response_code);
  440. return AST_TEST_PASS;
  441. }
  442. AST_TEST_DEFINE(invoke_not_found)
  443. {
  444. RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
  445. RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
  446. struct ast_variable *get_params = NULL;
  447. struct ast_variable *headers = NULL;
  448. switch (cmd) {
  449. case TEST_INIT:
  450. info->name = __func__;
  451. info->category = "/res/ari/";
  452. info->summary = "Test GET on a resource that does not exist.";
  453. info->description = "Test ARI binding logic.";
  454. return AST_TEST_NOT_RUN;
  455. case TEST_EXECUTE:
  456. break;
  457. }
  458. fixture = setup_invocation_test();
  459. response = response_alloc();
  460. ast_ari_invoke(NULL, "foo/fizzle/i-am-not-a-resource", AST_HTTP_GET, get_params, headers,
  461. ast_json_null(), response);
  462. ast_test_validate(test, 0 == invocation_count);
  463. ast_test_validate(test, 404 == response->response_code);
  464. return AST_TEST_PASS;
  465. }
  466. static int unload_module(void)
  467. {
  468. AST_TEST_UNREGISTER(get_docs);
  469. AST_TEST_UNREGISTER(get_docs_nohost);
  470. AST_TEST_UNREGISTER(get_docs_notfound);
  471. AST_TEST_UNREGISTER(get_docs_hackerz);
  472. AST_TEST_UNREGISTER(invoke_get);
  473. AST_TEST_UNREGISTER(invoke_wildcard);
  474. AST_TEST_UNREGISTER(invoke_delete);
  475. AST_TEST_UNREGISTER(invoke_post);
  476. AST_TEST_UNREGISTER(invoke_bad_post);
  477. AST_TEST_UNREGISTER(invoke_not_found);
  478. return 0;
  479. }
  480. static int load_module(void)
  481. {
  482. AST_TEST_REGISTER(get_docs);
  483. AST_TEST_REGISTER(get_docs_nohost);
  484. AST_TEST_REGISTER(get_docs_notfound);
  485. AST_TEST_REGISTER(get_docs_hackerz);
  486. AST_TEST_REGISTER(invoke_get);
  487. AST_TEST_REGISTER(invoke_wildcard);
  488. AST_TEST_REGISTER(invoke_delete);
  489. AST_TEST_REGISTER(invoke_post);
  490. AST_TEST_REGISTER(invoke_bad_post);
  491. AST_TEST_REGISTER(invoke_not_found);
  492. return AST_MODULE_LOAD_SUCCESS;
  493. }
  494. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ARI testing",
  495. .support_level = AST_MODULE_SUPPORT_CORE,
  496. .load = load_module,
  497. .unload = unload_module,
  498. );