Asterisk - The Open Source Telephony Project  18.5.0
test_ari.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <[email protected]>
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 /*!
20  * \file
21  * \brief Test ARI API.
22  * \author\verbatim David M. Lee, II <[email protected]> \endverbatim
23  *
24  * \ingroup tests
25  */
26 
27 /*** MODULEINFO
28  <depend>TEST_FRAMEWORK</depend>
29  <depend>res_ari</depend>
30  <support_level>core</support_level>
31  ***/
32 
33 #include "asterisk.h"
34 
35 #include "asterisk/module.h"
36 #include "asterisk/test.h"
37 #include "asterisk/ari.h"
38 
39 /*!@{*/
40 
41 /*!
42  * \internal
43  * The following code defines a simple RESTful API for unit testing. The
44  * response encodes the inputs of the invocation. The invocation_count
45  * counter is also incremented.
46  *
47  * - /foo (GET)
48  * - /foo/bar (GET, POST)
49  * - /foo/{bam} (GET)
50  * - /foo/{bam}/bang (GET, POST, DE1LETE)
51  */
52 
53 static int invocation_count;
54 
55 /*!
56  * \internal
57  * Shared code for all handlers
58  */
59 static void handler(const char *name,
60  int response_code,
61  struct ast_variable *get_params,
62  struct ast_variable *path_vars,
63  struct ast_variable *headers,
64  struct ast_json *body,
65  struct ast_ari_response *response)
66 {
67  struct ast_json *message = ast_json_pack("{s: s, s: {}, s: {}, s: {}}",
68  "name", name,
69  "get_params",
70  "path_vars",
71  "headers");
72  struct ast_json *get_params_obj = ast_json_object_get(message, "get_params");
73  struct ast_json *path_vars_obj = ast_json_object_get(message, "path_vars");
74  struct ast_json *headers_obj = ast_json_object_get(message, "headers");
75 
76  for (; get_params != NULL; get_params = get_params->next) {
77  ast_json_object_set(get_params_obj, get_params->name, ast_json_string_create(get_params->value));
78  }
79 
80  for (; path_vars != NULL; path_vars = path_vars->next) {
81  ast_json_object_set(path_vars_obj, path_vars->name, ast_json_string_create(path_vars->value));
82  }
83 
84  for (; headers != NULL; headers = headers->next) {
85  ast_json_object_set(headers_obj, headers->name, ast_json_string_create(headers->value));
86  }
87 
89  response->response_code = response_code;
90  response->message = message;
91 }
92 
93 /*!
94  * \internal
95  * Macro to reduce the handler definition boiler-plate.
96  */
97 #define HANDLER(name, response_code) \
98  static void name(struct ast_tcptls_session_instance *ser, \
99  struct ast_variable *get_params, \
100  struct ast_variable *path_vars, \
101  struct ast_variable *headers, \
102  struct ast_json *body, \
103  struct ast_ari_response *response) \
104  { \
105  handler(#name, response_code, get_params, path_vars, headers, body, response); \
106  }
107 
108 HANDLER(bang_get, 200)
109 HANDLER(bang_post, 200)
110 HANDLER(bang_delete, 204)
111 HANDLER(bar_get, 200)
112 HANDLER(bar_post, 200)
113 HANDLER(bam_get, 200)
114 HANDLER(foo_get, 200)
115 
116 static struct stasis_rest_handlers bang = {
117  .path_segment = "bang",
118  .callbacks = {
119  [AST_HTTP_GET] = bang_get,
120  [AST_HTTP_POST] = bang_post,
121  [AST_HTTP_DELETE] = bang_delete,
122  },
123  .num_children = 0
124 };
125 static struct stasis_rest_handlers bar = {
126  .path_segment = "bar",
127  .callbacks = {
128  [AST_HTTP_GET] = bar_get,
129  [AST_HTTP_POST] = bar_post,
130  },
131  .num_children = 0
132 };
133 static struct stasis_rest_handlers bam = {
134  .path_segment = "bam",
135  .is_wildcard = 1,
136  .callbacks = {
137  [AST_HTTP_GET] = bam_get,
138  },
139  .num_children = 1,
140  .children = { &bang }
141 };
143  .path_segment = "foo",
144  .callbacks = {
145  [AST_HTTP_GET] = foo_get,
146  },
147  .num_children = 3,
148  .children = { &bar, &bam, &bang }
149 };
150 /*!@}*/
151 
152 /*!
153  * \internal
154  * \c ast_ari_response constructor.
155  */
156 static struct ast_ari_response *response_alloc(void)
157 {
158  struct ast_ari_response *resp = ast_calloc(1, sizeof(struct ast_ari_response));
159  resp->headers = ast_str_create(24);
160  return resp;
161 }
162 
163 /*!
164  * \internal
165  * \c ast_ari_response destructor.
166  */
167 static void response_free(struct ast_ari_response *resp)
168 {
169  if (!resp) {
170  return;
171  }
172  ast_free(resp->headers);
173  ast_json_unref(resp->message);
174  ast_free(resp);
175 }
176 
177 /*!
178  * \ internal
179  * Setup test fixture for invocation tests.
180  */
181 static void *setup_invocation_test(void) {
182  int r;
183  invocation_count = 0;
184  r = ast_ari_add_handler(&test_root);
185  ast_assert(r == 0);
186  return &invocation_count;
187 }
188 
189 /*!
190  * \ internal
191  * Tear down test fixture for invocation tests.
192  */
193 static void tear_down_invocation_test(void *ignore) {
194  if (!ignore) {
195  return;
196  }
197  ast_ari_remove_handler(&test_root);
198 }
199 
200 
202 {
203  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
205  struct ast_json *basePathJson;
206  const char *basePath;
207 
208  switch (cmd) {
209  case TEST_INIT:
210  info->name = __func__;
211  info->category = "/res/ari/";
212  info->summary = "Test simple API get.";
213  info->description = "Test ARI binding logic.";
214  return AST_TEST_NOT_RUN;
215  case TEST_EXECUTE:
216  break;
217  }
218 
219  response = response_alloc();
220  headers = ast_variable_new("Host", "stasis.asterisk.org", __FILE__);
221  ast_ari_get_docs("resources.json", "", headers, response);
222  ast_test_validate(test, 200 == response->response_code);
223 
224  /* basePath should be relative to the Host header */
225  basePathJson = ast_json_object_get(response->message, "basePath");
226  ast_test_validate(test, NULL != basePathJson);
227  basePath = ast_json_string_get(basePathJson);
228  ast_test_validate(test, 0 == strcmp("http://stasis.asterisk.org/ari", basePath));
229 
230  return AST_TEST_PASS;
231 }
232 
233 AST_TEST_DEFINE(get_docs_nohost)
234 {
235  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
236  struct ast_variable *headers = NULL;
237  struct ast_json *basePathJson;
238 
239  switch (cmd) {
240  case TEST_INIT:
241  info->name = __func__;
242  info->category = "/res/ari/";
243  info->summary = "Test API get without a Host header";
244  info->description = "Test ARI binding logic.";
245  return AST_TEST_NOT_RUN;
246  case TEST_EXECUTE:
247  break;
248  }
249 
250  response = response_alloc();
251  ast_ari_get_docs("resources.json", "", headers, response);
252  ast_test_validate(test, 200 == response->response_code);
253 
254  /* basePath should be relative to the Host header */
255  basePathJson = ast_json_object_get(response->message, "basePath");
256  ast_test_validate(test, NULL == basePathJson);
257 
258  return AST_TEST_PASS;
259 }
260 
261 AST_TEST_DEFINE(get_docs_notfound)
262 {
263  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
264  struct ast_variable *headers = NULL;
265 
266  switch (cmd) {
267  case TEST_INIT:
268  info->name = __func__;
269  info->category = "/res/ari/";
270  info->summary = "Test API get for invalid resource";
271  info->description = "Test ARI binding logic.";
272  return AST_TEST_NOT_RUN;
273  case TEST_EXECUTE:
274  break;
275  }
276 
277  response = response_alloc();
278  ast_ari_get_docs("i-am-not-a-resource.json", "", headers, response);
279  ast_test_validate(test, 404 == response->response_code);
280 
281  return AST_TEST_PASS;
282 }
283 
284 AST_TEST_DEFINE(get_docs_hackerz)
285 {
286  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
287  struct ast_variable *headers = NULL;
288 
289  switch (cmd) {
290  case TEST_INIT:
291  info->name = __func__;
292  info->category = "/res/ari/";
293  info->summary = "Test API get for a file outside the rest-api path";
294  info->description = "Test ARI binding logic.";
295  return AST_TEST_NOT_RUN;
296  case TEST_EXECUTE:
297  break;
298  }
299 
300  response = response_alloc();
301  ast_ari_get_docs("../../../../sbin/asterisk", "", headers, response);
302  ast_test_validate(test, 404 == response->response_code);
303 
304  return AST_TEST_PASS;
305 }
306 
307 AST_TEST_DEFINE(invoke_get)
308 {
309  RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
310  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
311  RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
312  struct ast_variable *get_params = NULL;
313  struct ast_variable *headers = NULL;
314 
315  switch (cmd) {
316  case TEST_INIT:
317  info->name = __func__;
318  info->category = "/res/ari/";
319  info->summary = "Test simple GET of an HTTP resource.";
320  info->description = "Test ARI binding logic.";
321  return AST_TEST_NOT_RUN;
322  case TEST_EXECUTE:
323  break;
324  }
325 
326  fixture = setup_invocation_test();
327  response = response_alloc();
328  get_params = ast_variable_new("get1", "get-one", __FILE__);
329  ast_assert(get_params != NULL);
330  get_params->next = ast_variable_new("get2", "get-two", __FILE__);
331  ast_assert(get_params->next != NULL);
332 
333  headers = ast_variable_new("head1", "head-one", __FILE__);
334  ast_assert(headers != NULL);
335  headers->next = ast_variable_new("head2", "head-two", __FILE__);
336  ast_assert(headers->next != NULL);
337 
338  expected = ast_json_pack("{s: s, s: {s: s, s: s}, s: {s: s, s: s}, s: {}}",
339  "name", "foo_get",
340  "get_params",
341  "get1", "get-one",
342  "get2", "get-two",
343  "headers",
344  "head1", "head-one",
345  "head2", "head-two",
346  "path_vars");
347 
348  ast_ari_invoke(NULL, "foo", AST_HTTP_GET, get_params, headers,
349  ast_json_null(), response);
350 
351  ast_test_validate(test, 1 == invocation_count);
352  ast_test_validate(test, 200 == response->response_code);
353  ast_test_validate(test, ast_json_equal(expected, response->message));
354 
355  return AST_TEST_PASS;
356 }
357 
358 AST_TEST_DEFINE(invoke_wildcard)
359 {
360  RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
361  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
362  RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
363  struct ast_variable *get_params = NULL;
364  struct ast_variable *headers = NULL;
365 
366  switch (cmd) {
367  case TEST_INIT:
368  info->name = __func__;
369  info->category = "/res/ari/";
370  info->summary = "Test GET of a wildcard resource.";
371  info->description = "Test ARI binding logic.";
372  return AST_TEST_NOT_RUN;
373  case TEST_EXECUTE:
374  break;
375  }
376 
377  fixture = setup_invocation_test();
378  response = response_alloc();
379  expected = ast_json_pack("{s: s, s: {}, s: {}, s: {s: s}}",
380  "name", "bam_get",
381  "get_params",
382  "headers",
383  "path_vars",
384  "bam", "foshizzle");
385 
386  ast_ari_invoke(NULL, "foo/foshizzle", AST_HTTP_GET, get_params, headers,
387  ast_json_null(), response);
388 
389  ast_test_validate(test, 1 == invocation_count);
390  ast_test_validate(test, 200 == response->response_code);
391  ast_test_validate(test, ast_json_equal(expected, response->message));
392 
393  return AST_TEST_PASS;
394 }
395 
396 AST_TEST_DEFINE(invoke_delete)
397 {
398  RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
399  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
400  RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
401  struct ast_variable *get_params = NULL;
402  struct ast_variable *headers = NULL;
403 
404  switch (cmd) {
405  case TEST_INIT:
406  info->name = __func__;
407  info->category = "/res/ari/";
408  info->summary = "Test DELETE of an HTTP resource.";
409  info->description = "Test ARI binding logic.";
410  return AST_TEST_NOT_RUN;
411  case TEST_EXECUTE:
412  break;
413  }
414 
415  fixture = setup_invocation_test();
416  response = response_alloc();
417  expected = ast_json_pack("{s: s, s: {}, s: {}, s: {s: s}}",
418  "name", "bang_delete",
419  "get_params",
420  "headers",
421  "path_vars",
422  "bam", "foshizzle");
423 
424  ast_ari_invoke(NULL, "foo/foshizzle/bang", AST_HTTP_DELETE, get_params, headers,
425  ast_json_null(), response);
426 
427  ast_test_validate(test, 1 == invocation_count);
428  ast_test_validate(test, 204 == response->response_code);
429  ast_test_validate(test, ast_json_equal(expected, response->message));
430 
431  return AST_TEST_PASS;
432 }
433 
434 AST_TEST_DEFINE(invoke_post)
435 {
436  RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
437  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
438  RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
439  struct ast_variable *get_params = NULL;
440  struct ast_variable *headers = NULL;
441 
442  switch (cmd) {
443  case TEST_INIT:
444  info->name = __func__;
445  info->category = "/res/ari/";
446  info->summary = "Test POST of an HTTP resource.";
447  info->description = "Test ARI binding logic.";
448  return AST_TEST_NOT_RUN;
449  case TEST_EXECUTE:
450  break;
451  }
452 
453  fixture = setup_invocation_test();
454  response = response_alloc();
455  get_params = ast_variable_new("get1", "get-one", __FILE__);
456  ast_assert(get_params != NULL);
457  get_params->next = ast_variable_new("get2", "get-two", __FILE__);
458  ast_assert(get_params->next != NULL);
459 
460  headers = ast_variable_new("head1", "head-one", __FILE__);
461  ast_assert(headers != NULL);
462  headers->next = ast_variable_new("head2", "head-two", __FILE__);
463  ast_assert(headers->next != NULL);
464 
465  expected = ast_json_pack("{s: s, s: {s: s, s: s}, s: {s: s, s: s}, s: {}}",
466  "name", "bar_post",
467  "get_params",
468  "get1", "get-one",
469  "get2", "get-two",
470  "headers",
471  "head1", "head-one",
472  "head2", "head-two",
473  "path_vars");
474 
475  ast_ari_invoke(NULL, "foo/bar", AST_HTTP_POST, get_params, headers,
476  ast_json_null(), response);
477 
478  ast_test_validate(test, 1 == invocation_count);
479  ast_test_validate(test, 200 == response->response_code);
480  ast_test_validate(test, ast_json_equal(expected, response->message));
481 
482  return AST_TEST_PASS;
483 }
484 
485 AST_TEST_DEFINE(invoke_bad_post)
486 {
487  RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
488  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
489  struct ast_variable *get_params = NULL;
490  struct ast_variable *headers = NULL;
491 
492  switch (cmd) {
493  case TEST_INIT:
494  info->name = __func__;
495  info->category = "/res/ari/";
496  info->summary = "Test POST on a resource that doesn't support it.";
497  info->description = "Test ARI binding logic.";
498  return AST_TEST_NOT_RUN;
499  case TEST_EXECUTE:
500  break;
501  }
502 
503  fixture = setup_invocation_test();
504  response = response_alloc();
505  ast_ari_invoke(NULL, "foo", AST_HTTP_POST, get_params, headers,
506  ast_json_null(), response);
507 
508  ast_test_validate(test, 0 == invocation_count);
509  ast_test_validate(test, 405 == response->response_code);
510 
511  return AST_TEST_PASS;
512 }
513 
514 AST_TEST_DEFINE(invoke_not_found)
515 {
516  RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
517  RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
518  struct ast_variable *get_params = NULL;
519  struct ast_variable *headers = NULL;
520 
521  switch (cmd) {
522  case TEST_INIT:
523  info->name = __func__;
524  info->category = "/res/ari/";
525  info->summary = "Test GET on a resource that does not exist.";
526  info->description = "Test ARI binding logic.";
527  return AST_TEST_NOT_RUN;
528  case TEST_EXECUTE:
529  break;
530  }
531 
532  fixture = setup_invocation_test();
533  response = response_alloc();
534  ast_ari_invoke(NULL, "foo/fizzle/i-am-not-a-resource", AST_HTTP_GET, get_params, headers,
535  ast_json_null(), response);
536 
537  ast_test_validate(test, 0 == invocation_count);
538  ast_test_validate(test, 404 == response->response_code);
539 
540  return AST_TEST_PASS;
541 }
542 
543 static int unload_module(void)
544 {
545  AST_TEST_UNREGISTER(get_docs);
546  AST_TEST_UNREGISTER(get_docs_nohost);
547  AST_TEST_UNREGISTER(get_docs_notfound);
548  AST_TEST_UNREGISTER(get_docs_hackerz);
549  AST_TEST_UNREGISTER(invoke_get);
550  AST_TEST_UNREGISTER(invoke_wildcard);
551  AST_TEST_UNREGISTER(invoke_delete);
552  AST_TEST_UNREGISTER(invoke_post);
553  AST_TEST_UNREGISTER(invoke_bad_post);
554  AST_TEST_UNREGISTER(invoke_not_found);
555  return 0;
556 }
557 
558 static int load_module(void)
559 {
560  AST_TEST_REGISTER(get_docs);
561  AST_TEST_REGISTER(get_docs_nohost);
562  AST_TEST_REGISTER(get_docs_notfound);
563  AST_TEST_REGISTER(get_docs_hackerz);
564  AST_TEST_REGISTER(invoke_get);
565  AST_TEST_REGISTER(invoke_wildcard);
566  AST_TEST_REGISTER(invoke_delete);
567  AST_TEST_REGISTER(invoke_post);
568  AST_TEST_REGISTER(invoke_bad_post);
569  AST_TEST_REGISTER(invoke_not_found);
571 }
572 
574  .support_level = AST_MODULE_SUPPORT_CORE,
575  .load = load_module,
576  .unload = unload_module,
577  .requires = "res_ari",
578 );
struct ast_variable * next
static int unload_module(void)
Definition: test_ari.c:543
Asterisk main include file. File version handling, generic pbx functions.
struct ast_str * headers
Definition: ari.h:95
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:591
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1263
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
void ast_ari_get_docs(const char *uri, const char *prefix, struct ast_variable *headers, struct ast_ari_response *response)
Definition: res_ari.c:598
static struct ast_ari_response * response_alloc(void)
Definition: test_ari.c:156
Structure for variables, used for configurations and for channel variables.
static struct stasis_rest_handlers bar
Definition: test_ari.c:125
Test Framework API.
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define HANDLER(name, response_code)
Definition: test_ari.c:97
Asterisk RESTful API hooks.
#define ast_assert(a)
Definition: utils.h:695
#define NULL
Definition: resample.c:96
static void tear_down_invocation_test(void *ignore)
Definition: test_ari.c:193
void ast_ari_invoke(struct ast_tcptls_session_instance *ser, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
Definition: res_ari.c:491
int ast_json_object_set(struct ast_json *object, const char *key, struct ast_json *value)
Set a field in a JSON object.
Definition: json.c:404
int response_code
Definition: ari.h:98
struct ast_json * ast_json_null(void)
Get the JSON null value.
Definition: json.c:248
static void * setup_invocation_test(void)
Definition: test_ari.c:181
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:911
struct ast_json * ast_json_string_create(const char *value)
Construct a JSON string from value.
Definition: json.c:268
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:273
#define ast_variable_new(name, value, filename)
static int load_module(void)
Definition: test_ari.c:558
int ast_ari_add_handler(struct stasis_rest_handlers *handler)
Definition: res_ari.c:179
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
static void response_free(struct ast_ari_response *resp)
Definition: test_ari.c:167
static struct stasis_rest_handlers bang
Definition: test_ari.c:116
def info(msg)
def ignore(key=None, val=None, section=None, pjsip=None, nmapped=None, type='endpoint')
Definition: sip_to_pjsip.py:48
int ast_ari_remove_handler(struct stasis_rest_handlers *handler)
Definition: res_ari.c:202
static const char name[]
Definition: cdr_mysql.c:74
#define ast_free(a)
Definition: astmm.h:182
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
static int invocation_count
Definition: test_ari.c:53
static struct stasis_rest_handlers test_root
Definition: test_ari.c:142
struct ast_json * message
Definition: ari.h:93
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
AST_TEST_DEFINE(get_docs)
Definition: test_ari.c:201
const char * path_segment
Definition: ari.h:70
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:397
int ast_json_equal(const struct ast_json *lhs, const struct ast_json *rhs)
Compare two JSON objects.
Definition: json.c:347
static void handler(const char *name, int response_code, struct ast_variable *get_params, struct ast_variable *path_vars, struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
Definition: test_ari.c:59
Abstract JSON element (object, array, string, int, ...).
static struct stasis_rest_handlers bam
Definition: test_ari.c:133
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
Handler for a single RESTful path segment.
Definition: ari.h:68
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620