Asterisk - The Open Source Telephony Project GIT-master-d856a3e
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 <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/*!
20 * \file
21 * \brief Test ARI API.
22 * \author\verbatim David M. Lee, II <dlee@digium.com> \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
54
55/*!
56 * \internal
57 * Shared code for all handlers
58 */
59static 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
108HANDLER(bang_get, 200)
109HANDLER(bang_post, 200)
110HANDLER(bang_delete, 204)
111HANDLER(bar_get, 200)
112HANDLER(bar_post, 200)
113HANDLER(bam_get, 200)
114HANDLER(foo_get, 200)
115
116static 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};
125static 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};
133static 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 */
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 */
167static 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 */
181static void *setup_invocation_test(void) {
182 int r;
185 ast_assert(r == 0);
186 return &invocation_count;
187}
188
189/*!
190 * \ internal
191 * Tear down test fixture for invocation tests.
192 */
194 if (!ignore) {
195 return;
196 }
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
233AST_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
261AST_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
284AST_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
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
358AST_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
396AST_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
434AST_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
485AST_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
514AST_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
543static 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
558static 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",
Asterisk RESTful API hooks.
int ast_ari_remove_handler(struct stasis_rest_handlers *handler)
Definition: res_ari.c:202
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
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
int ast_ari_add_handler(struct stasis_rest_handlers *handler)
Definition: res_ari.c:179
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
static const char name[]
Definition: format_mp3.c:68
@ AST_HTTP_DELETE
Definition: http.h:64
@ AST_HTTP_POST
Definition: http.h:61
@ AST_HTTP_GET
Definition: http.h:60
#define ast_variable_new(name, value, filename)
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
struct ast_json * ast_json_string_create(const char *value)
Construct a JSON string from value.
Definition: json.c:278
struct ast_json * ast_json_null(void)
Get the JSON null value.
Definition: json.c:248
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
int ast_json_equal(const struct ast_json *lhs, const struct ast_json *rhs)
Compare two JSON objects.
Definition: json.c:357
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:612
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:414
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:283
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:407
Asterisk module definitions.
@ AST_MODFLAG_DEFAULT
Definition: module.h:329
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODULE_SUPPORT_CORE
Definition: module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
def ignore(key=None, val=None, section=None, pjsip=None, nmapped=None, type='endpoint')
Definition: sip_to_pjsip.py:48
def info(msg)
#define NULL
Definition: resample.c:96
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
struct ast_str * headers
Definition: ari.h:96
struct ast_json * message
Definition: ari.h:94
int response_code
Definition: ari.h:99
Abstract JSON element (object, array, string, int, ...).
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
Handler for a single RESTful path segment.
Definition: ari.h:69
const char * path_segment
Definition: ari.h:71
Test Framework API.
@ TEST_INIT
Definition: test.h:200
@ TEST_EXECUTE
Definition: test.h:201
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
@ AST_TEST_PASS
Definition: test.h:195
@ AST_TEST_NOT_RUN
Definition: test.h:194
static void response_free(struct ast_ari_response *resp)
Definition: test_ari.c:167
#define HANDLER(name, response_code)
Definition: test_ari.c:97
AST_TEST_DEFINE(get_docs)
Definition: test_ari.c:201
static void * setup_invocation_test(void)
Definition: test_ari.c:181
static struct stasis_rest_handlers test_root
Definition: test_ari.c:142
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
static struct ast_ari_response * response_alloc(void)
Definition: test_ari.c:156
static struct stasis_rest_handlers bar
Definition: test_ari.c:125
static int invocation_count
Definition: test_ari.c:53
static struct stasis_rest_handlers bam
Definition: test_ari.c:133
static int load_module(void)
Definition: test_ari.c:558
static int unload_module(void)
Definition: test_ari.c:543
static void tear_down_invocation_test(void *ignore)
Definition: test_ari.c:193
static struct stasis_rest_handlers bang
Definition: test_ari.c:116
#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:941
#define ast_assert(a)
Definition: utils.h:739