Asterisk - The Open Source Telephony Project  GIT-master-a24979a
test_http_media_cache.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Matt Jordan
5  *
6  * Matt Jordan <mjordan@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 Tests for the HTTP media cache backend
22  *
23  * \author \verbatim Matt Jordan <mjordan@digium.com> \endverbatim
24  *
25  * \ingroup tests
26  */
27 
28 /*** MODULEINFO
29  <depend>TEST_FRAMEWORK</depend>
30  <depend>curl</depend>
31  <depend>res_http_media_cache</depend>
32  <support_level>core</support_level>
33  ***/
34 
35 #include "asterisk.h"
36 
37 #include <fcntl.h>
38 
39 #include "asterisk/module.h"
40 #include "asterisk/http.h"
41 #include "asterisk/bucket.h"
42 #include "asterisk/test.h"
43 
44 #define CATEGORY "/res/http_media_cache/"
45 
46 #define TEST_URI "test_media_cache"
47 
48 struct test_options {
50  int send_file;
51  struct {
52  int s_maxage;
53  int maxage;
54  int no_cache;
57  struct timeval expires;
58  const char *status_text;
59  const char *etag;
60  const char *content_type;
61 };
62 
63 static struct test_options options;
64 
65 static char server_uri[512];
66 
67 #define VALIDATE_EXPIRES(test, bucket_file, expected, delta) do { \
68  RAII_VAR(struct ast_bucket_metadata *, metadata, ast_bucket_file_metadata_get((bucket_file), "__actual_expires"), ao2_cleanup); \
69  int actual_expires; \
70  ast_test_validate(test, metadata != NULL); \
71  ast_test_validate(test, sscanf(metadata->value, "%d", &actual_expires) == 1); \
72  ast_test_status_update(test, "Checking %d >= %d and %d <= %d\n", \
73  (int) ((expected) + (delta)), actual_expires, \
74  (int) ((expected) - (delta)), actual_expires); \
75  ast_test_validate(test, (((expected) + (delta) >= actual_expires) && ((expected) - (delta) <= actual_expires))); \
76 } while (0)
77 
78 #define VALIDATE_STR_METADATA(test, bucket_file, key, expected) do { \
79  RAII_VAR(struct ast_bucket_metadata *, metadata, ast_bucket_file_metadata_get((bucket_file), (key)), ao2_cleanup); \
80  ast_test_validate(test, metadata != NULL); \
81  ast_test_validate(test, !strcmp(metadata->value, (expected))); \
82 } while (0)
83 
84 #define SET_OR_APPEND_CACHE_CONTROL(str) do { \
85  if (!ast_str_strlen((str))) { \
86  ast_str_set(&(str), 0, "%s", "cache-control: "); \
87  } else { \
88  ast_str_append(&(str), 0, "%s", ", "); \
89  } \
90 } while (0)
91 
92 static int http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers)
93 {
94  char file_name[64] = "/tmp/test-media-cache-XXXXXX";
95  struct ast_str *http_header = ast_str_create(128);
96  struct ast_str *cache_control = ast_str_create(128);
97  int fd = -1;
98  int unmodified = 0;
99  int send_file = options.send_file && method == AST_HTTP_GET;
100 
101  if (!http_header) {
102  goto error;
103  }
104 
105  if (send_file) {
106  char buf[1024];
107 
108  fd = mkstemp(file_name);
109  if (fd == -1) {
110  ast_log(LOG_ERROR, "Unable to open temp file for testing: %s (%d)", strerror(errno), errno);
111  goto error;
112  }
113 
114  memset(buf, 1, sizeof(buf));
115  if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {
116  ast_log(LOG_ERROR, "Failed to write expected number of bytes to pipe\n");
117  close(fd);
118  goto error;
119  }
120  close(fd);
121 
122  fd = open(file_name, 0);
123  if (fd == -1) {
124  ast_log(LOG_ERROR, "Unable to open temp file for testing: %s (%d)", strerror(errno), errno);
125  goto error;
126  }
127  }
128 
130  ast_str_append(&http_header, 0, "Content-Type: %s\r\n", options.content_type);
131  }
132 
134  SET_OR_APPEND_CACHE_CONTROL(cache_control);
135  ast_str_append(&cache_control, 0, "max-age=%d", options.cache_control.maxage);
136  }
137 
139  SET_OR_APPEND_CACHE_CONTROL(cache_control);
140  ast_str_append(&cache_control, 0, "s-maxage=%d", options.cache_control.s_maxage);
141  }
142 
144  SET_OR_APPEND_CACHE_CONTROL(cache_control);
145  ast_str_append(&cache_control, 0, "%s", "no-cache");
146  }
147 
149  SET_OR_APPEND_CACHE_CONTROL(cache_control);
150  ast_str_append(&cache_control, 0, "%s", "must-revalidate");
151  }
152 
153  if (ast_str_strlen(cache_control)) {
154  ast_str_append(&http_header, 0, "%s\r\n", ast_str_buffer(cache_control));
155  }
156 
157  if (options.expires.tv_sec) {
158  struct ast_tm now_time;
159  char tmbuf[64];
160 
161  ast_localtime(&options.expires, &now_time, NULL);
162  ast_strftime(tmbuf, sizeof(tmbuf), "%a, %d %b %Y %T %z", &now_time);
163  ast_str_append(&http_header, 0, "Expires: %s\r\n", tmbuf);
164  }
165 
166  if (!ast_strlen_zero(options.etag)) {
167  struct ast_variable *v;
168 
169  ast_str_append(&http_header, 0, "ETag: %s\r\n", options.etag);
170  for (v = headers; v; v = v->next) {
171  if (!strcasecmp(v->name, "If-None-Match") && !strcasecmp(v->value, options.etag)) {
172  unmodified = 1;
173  break;
174  }
175  }
176  }
177 
178  if (!unmodified) {
179  ast_http_send(ser, method, options.status_code, options.status_text, http_header, NULL, send_file ? fd : 0, 1);
180  } else {
181  ast_http_send(ser, method, 304, "Not Modified", http_header, NULL, 0, 1);
182  }
183 
184  if (send_file) {
185  close(fd);
186  unlink(file_name);
187  }
188 
189  ast_free(cache_control);
190 
191  return 0;
192 
193 error:
194  ast_free(http_header);
195  ast_free(cache_control);
197  ast_http_error(ser, 418, "I'm a Teapot", "Please don't ask me to brew coffee.");
198 
199  return 0;
200 }
201 
202 static struct ast_http_uri test_uri = {
203  .description = "HTTP Media Cache Test URI",
204  .uri = TEST_URI,
205  .callback = http_callback,
206  .has_subtree = 1,
207  .data = NULL,
208  .key = __FILE__,
209 };
210 
211 static int pre_test_cb(struct ast_test_info *info, struct ast_test *test)
212 {
213  memset(&options, 0, sizeof(options));
214 
215  return 0;
216 }
217 
218 static void bucket_file_cleanup(void *obj)
219 {
220  struct ast_bucket_file *bucket_file = obj;
221 
222  if (bucket_file) {
223  ast_bucket_file_delete(bucket_file);
224  ao2_ref(bucket_file, -1);
225  }
226 }
227 
228 AST_TEST_DEFINE(retrieve_content_type)
229 {
230  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
231  char uri[1024];
232 
233  switch (cmd) {
234  case TEST_INIT:
235  info->name = __func__;
236  info->category = CATEGORY;
237  info->summary = "Test retrieval of a resource with a Content-Type header";
238  info->description =
239  "This test covers retrieval of a resource whose URL does not end with\n"
240  "a parseable extension and whose response includes a Content-Type\n"
241  "header that we recognize.";
242  return AST_TEST_NOT_RUN;
243  case TEST_EXECUTE:
244  break;
245  }
246 
247  options.send_file = 1;
248  options.status_code = 200;
249  options.status_text = "OK";
250  options.content_type = "audio/wav";
251 
252  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav?account_id=1234");
253 
254  bucket_file = ast_bucket_file_retrieve(uri);
255  ast_test_validate(test, bucket_file != NULL);
256  ast_test_validate(test, !strcmp(uri, ast_sorcery_object_get_id(bucket_file)));
257  ast_test_validate(test, !ast_strlen_zero(bucket_file->path));
258  VALIDATE_STR_METADATA(test, bucket_file, "ext", ".wav");
259 
260  return AST_TEST_PASS;
261 }
262 
263 AST_TEST_DEFINE(retrieve_parsed_uri)
264 {
265  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
266  char uri[1024];
267 
268  switch (cmd) {
269  case TEST_INIT:
270  info->name = __func__;
271  info->category = CATEGORY;
272  info->summary = "Test retrieval of a resource with a complex URI";
273  info->description =
274  "This test covers retrieval of a resource whose URL does not end with\n"
275  "a parseable extension, but the path portion of the URL does end with\n"
276  "parseable extension.";
277  return AST_TEST_NOT_RUN;
278  case TEST_EXECUTE:
279  break;
280  }
281 
282  options.send_file = 1;
283  options.status_code = 200;
284  options.status_text = "OK";
285 
286  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav?account_id=1234");
287 
288  bucket_file = ast_bucket_file_retrieve(uri);
289  ast_test_validate(test, bucket_file != NULL);
290  ast_test_validate(test, !strcmp(uri, ast_sorcery_object_get_id(bucket_file)));
291  ast_test_validate(test, !ast_strlen_zero(bucket_file->path));
292  VALIDATE_STR_METADATA(test, bucket_file, "ext", ".wav");
293 
294  return AST_TEST_PASS;
295 }
296 
297 AST_TEST_DEFINE(retrieve_cache_control_directives)
298 {
299  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
300  struct timeval now = ast_tvnow();
301  char uri[1024];
302 
303  switch (cmd) {
304  case TEST_INIT:
305  info->name = __func__;
306  info->category = CATEGORY;
307  info->summary = "Test retrieval of a resource with Cache-Control directives that affect staleness";
308  info->description =
309  "This test covers retrieval of a resource with the Cache-Control header,\n"
310  "which specifies no-cache and/or must-revalidate.";
311  return AST_TEST_NOT_RUN;
312  case TEST_EXECUTE:
313  break;
314  }
315 
316  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav");
317 
318  options.send_file = 1;
319  options.status_code = 200;
320  options.status_text = "OK";
321 
322  ast_test_status_update(test, "Testing no-cache...\n");
324  bucket_file = ast_bucket_file_retrieve(uri);
325  ast_test_validate(test, bucket_file != NULL);
326  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 1);
327  bucket_file_cleanup(bucket_file);
328 
329  ast_test_status_update(test, "Testing no-cache with ETag...\n");
331  options.etag = "123456789";
332  bucket_file = ast_bucket_file_retrieve(uri);
333  ast_test_validate(test, bucket_file != NULL);
334  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
335  bucket_file_cleanup(bucket_file);
336 
337  options.etag = NULL;
338 
339  ast_test_status_update(test, "Testing no-cache with max-age...\n");
342  bucket_file = ast_bucket_file_retrieve(uri);
343  ast_test_validate(test, bucket_file != NULL);
344  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
345  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 1);
346  bucket_file_cleanup(bucket_file);
347 
350 
351  ast_test_status_update(test, "Testing must-revalidate...\n");
353  bucket_file = ast_bucket_file_retrieve(uri);
354  ast_test_validate(test, bucket_file != NULL);
355  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 1);
356  bucket_file_cleanup(bucket_file);
357 
358  ast_test_status_update(test, "Testing must-revalidate with ETag...\n");
360  options.etag = "123456789";
361  bucket_file = ast_bucket_file_retrieve(uri);
362  ast_test_validate(test, bucket_file != NULL);
363  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
364  bucket_file_cleanup(bucket_file);
365 
366  options.etag = NULL;
367 
368  ast_test_status_update(test, "Testing must-revalidate with max-age...\n");
371  bucket_file = ast_bucket_file_retrieve(uri);
372  ast_test_validate(test, bucket_file != NULL);
373  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
374  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 1);
375 
376  return AST_TEST_PASS;
377 }
378 
379 AST_TEST_DEFINE(retrieve_cache_control_age)
380 {
381  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
382  struct timeval now = ast_tvnow();
383  char uri[1024];
384 
385  switch (cmd) {
386  case TEST_INIT:
387  info->name = __func__;
388  info->category = CATEGORY;
389  info->summary = "Test retrieval of a resource with age specifiers in Cache-Control";
390  info->description =
391  "This test covers retrieval of a resource with the Cache-Control header,\n"
392  "which specifies max-age and/or s-maxage. The test verifies proper precedence\n"
393  "ordering of the header attributes, along with its relation if the Expires\n"
394  "header is present.";
395  return AST_TEST_NOT_RUN;
396  case TEST_EXECUTE:
397  break;
398  }
399 
400  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav");
401 
402  options.send_file = 1;
403  options.status_code = 200;
404  options.status_text = "OK";
405 
406  ast_test_status_update(test, "Testing max-age...\n");
408  bucket_file = ast_bucket_file_retrieve(uri);
409  ast_test_validate(test, bucket_file != NULL);
410  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
411  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
412  bucket_file_cleanup(bucket_file);
413 
414  ast_test_status_update(test, "Testing s-maxage...\n");
415  now = ast_tvnow();
418  bucket_file = ast_bucket_file_retrieve(uri);
419  ast_test_validate(test, bucket_file != NULL);
420  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
421  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
422  bucket_file_cleanup(bucket_file);
423 
424  ast_test_status_update(test, "Testing max-age and s-maxage...\n");
425  now = ast_tvnow();
428  bucket_file = ast_bucket_file_retrieve(uri);
429  ast_test_validate(test, bucket_file != NULL);
430  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 600, 3);
431  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
432  bucket_file_cleanup(bucket_file);
433 
434  ast_test_status_update(test, "Testing max-age and Expires...\n");
435  now = ast_tvnow();
438  options.expires.tv_sec = now.tv_sec + 3000;
439  bucket_file = ast_bucket_file_retrieve(uri);
440  ast_test_validate(test, bucket_file != NULL);
441  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
442  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
443  bucket_file_cleanup(bucket_file);
444 
445  ast_test_status_update(test, "Testing s-maxage and Expires...\n");
446  now = ast_tvnow();
449  options.expires.tv_sec = now.tv_sec + 3000;
450  bucket_file = ast_bucket_file_retrieve(uri);
451  ast_test_validate(test, bucket_file != NULL);
452  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
453  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
454  bucket_file_cleanup(bucket_file);
455 
456  ast_test_status_update(test, "Testing s-maxage and Expires...\n");
457  now = ast_tvnow();
460  options.expires.tv_sec = now.tv_sec + 3000;
461  bucket_file = ast_bucket_file_retrieve(uri);
462  ast_test_validate(test, bucket_file != NULL);
463  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
464  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
465  bucket_file_cleanup(bucket_file);
466 
467  ast_test_status_update(test, "Testing max-age, s-maxage, and Expires...\n");
468  now = ast_tvnow();
471  options.expires.tv_sec = now.tv_sec + 3000;
472  bucket_file = ast_bucket_file_retrieve(uri);
473  ast_test_validate(test, bucket_file != NULL);
474  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 600, 3);
475  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
476 
477  return AST_TEST_PASS;
478 }
479 
480 AST_TEST_DEFINE(retrieve_etag_expired)
481 {
482  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
483  struct timeval now = ast_tvnow();
484  char uri[1024];
485 
486  switch (cmd) {
487  case TEST_INIT:
488  info->name = __func__;
489  info->category = CATEGORY;
490  info->summary = "Test retrieval of an expired resource with an ETag";
491  info->description =
492  "This test covers a staleness check of a resource with an ETag\n"
493  "that has also expired. It guarantees that even if a resource\n"
494  "is expired, we will still not consider it stale if the resource\n"
495  "has not changed per the ETag value.";
496  return AST_TEST_NOT_RUN;
497  case TEST_EXECUTE:
498  break;
499  }
500 
501  options.send_file = 1;
502  options.status_code = 200;
503  options.status_text = "OK";
504  options.etag = "123456789";
505  options.expires.tv_sec = now.tv_sec - 1;
506 
507  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav");
508 
509  bucket_file = ast_bucket_file_retrieve(uri);
510  ast_test_validate(test, bucket_file != NULL);
511  ast_test_validate(test, !strcmp(uri, ast_sorcery_object_get_id(bucket_file)));
512  ast_test_validate(test, !ast_strlen_zero(bucket_file->path));
513  VALIDATE_STR_METADATA(test, bucket_file, "etag", options.etag);
514  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec - 1, 3);
515 
516  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
517 
518  return AST_TEST_PASS;
519 }
520 
521 AST_TEST_DEFINE(retrieve_expires)
522 {
523  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
524  struct timeval now = ast_tvnow();
525  char uri[1024];
526 
527  switch (cmd) {
528  case TEST_INIT:
529  info->name = __func__;
530  info->category = CATEGORY;
531  info->summary = "Test retrieval with explicit expiration";
532  info->description =
533  "This test covers retrieving a resource that has an Expires.\n"
534  "After retrieval of the resource, staleness is checked. With\n"
535  "a non-expired resource, we expect the resource to not be stale.\n"
536  "When the expiration has occurred, we expect the staleness check\n"
537  "to fail.";
538  return AST_TEST_NOT_RUN;
539  case TEST_EXECUTE:
540  break;
541  }
542 
543  options.send_file = 1;
544  options.status_code = 200;
545  options.status_text = "OK";
546  options.expires.tv_sec = now.tv_sec + 3000;
547 
548  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav");
549 
550  bucket_file = ast_bucket_file_retrieve(uri);
551  ast_test_validate(test, bucket_file != NULL);
552  ast_test_validate(test, !strcmp(uri, ast_sorcery_object_get_id(bucket_file)));
553  ast_test_validate(test, !ast_strlen_zero(bucket_file->path));
554  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 3000, 3);
555 
556  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
557 
558  /* Clean up previous result */
559  bucket_file_cleanup(bucket_file);
560 
561  options.expires.tv_sec = now.tv_sec - 1;
562  bucket_file = ast_bucket_file_retrieve(uri);
563  ast_test_validate(test, bucket_file != NULL);
564  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec - 1, 3);
565 
566  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 1);
567 
568  return AST_TEST_PASS;
569 }
570 
571 AST_TEST_DEFINE(retrieve_etag)
572 {
573  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
574  struct timeval now = ast_tvnow();
575  char uri[1024];
576 
577  switch (cmd) {
578  case TEST_INIT:
579  info->name = __func__;
580  info->category = CATEGORY;
581  info->summary = "Test retrieval with an ETag";
582  info->description =
583  "This test covers retrieving a resource that has an ETag.\n"
584  "After retrieval of the resource, staleness is checked. With\n"
585  "matching ETags, we expect the resource to not be stale. When\n"
586  "the ETag does not match, we expect the resource to be stale.";
587  return AST_TEST_NOT_RUN;
588  case TEST_EXECUTE:
589  break;
590  }
591 
592  options.send_file = 1;
593  options.status_code = 200;
594  options.status_text = "OK";
595  options.etag = "123456789";
596 
597  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav");
598 
599  bucket_file = ast_bucket_file_retrieve(uri);
600  ast_test_validate(test, bucket_file != NULL);
601  ast_test_validate(test, !strcmp(uri, ast_sorcery_object_get_id(bucket_file)));
602  ast_test_validate(test, !ast_strlen_zero(bucket_file->path));
603  VALIDATE_STR_METADATA(test, bucket_file, "etag", options.etag);
604  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec, 3);
605 
606  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 0);
607 
608  options.etag = "99999999";
609  ast_test_validate(test, ast_bucket_file_is_stale(bucket_file) == 1);
610 
611  return AST_TEST_PASS;
612 }
613 
614 AST_TEST_DEFINE(retrieve_nominal)
615 {
616  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
617  struct timeval now = ast_tvnow();
618  char uri[1024];
619 
620  switch (cmd) {
621  case TEST_INIT:
622  info->name = __func__;
623  info->category = CATEGORY;
624  info->summary = "Test nominal retrieval";
625  info->description =
626  "Test nominal retrieval of a resource.";
627  return AST_TEST_NOT_RUN;
628  case TEST_EXECUTE:
629  break;
630  }
631 
632  options.send_file = 1;
633  options.status_code = 200;
634  options.status_text = "OK";
635 
636  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav");
637 
638  bucket_file = ast_bucket_file_retrieve(uri);
639  ast_test_validate(test, bucket_file != NULL);
640  ast_test_validate(test, !strcmp(uri, ast_sorcery_object_get_id(bucket_file)));
641  ast_test_validate(test, !ast_strlen_zero(bucket_file->path));
642  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec, 3);
643 
644  return AST_TEST_PASS;
645 }
646 
647 AST_TEST_DEFINE(create_nominal)
648 {
649  RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
650  struct timeval now = ast_tvnow();
651  char uri[1024];
652 
653  switch (cmd) {
654  case TEST_INIT:
655  info->name = __func__;
656  info->category = CATEGORY;
657  info->summary = "Test nominal creation";
658  info->description =
659  "Test nominal creation of a resource.";
660  return AST_TEST_NOT_RUN;
661  case TEST_EXECUTE:
662  break;
663  }
664 
665  options.send_file = 1;
666  options.status_code = 200;
667  options.status_text = "OK";
668 
669  snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav");
670 
671  bucket_file = ast_bucket_file_alloc(uri);
672  ast_test_validate(test, bucket_file != NULL);
673  ast_test_validate(test, ast_bucket_file_temporary_create(bucket_file) == 0);
674  ast_test_validate(test, ast_bucket_file_create(bucket_file) == 0);
675  VALIDATE_EXPIRES(test, bucket_file, now.tv_sec, 3);
676 
677  return AST_TEST_PASS;
678 }
679 
680 
681 static int process_config(int reload)
682 {
683  struct ast_config *config;
684  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
685  const char *bindaddr;
686  const char *bindport;
687  const char *prefix;
688  const char *enabled;
689 
690  config = ast_config_load("http.conf", config_flags);
692  return -1;
693  } else if (config == CONFIG_STATUS_FILEUNCHANGED) {
694  return 0;
695  }
696 
697  enabled = ast_config_option(config, "general", "enabled");
698  if (!enabled || ast_false(enabled)) {
700  return -1;
701  }
702 
703  /* Construct our Server URI */
704  bindaddr = ast_config_option(config, "general", "bindaddr");
705  if (!bindaddr) {
707  return -1;
708  }
709 
710  bindport = ast_config_option(config, "general", "bindport");
711  if (!bindport) {
712  bindport = "8088";
713  }
714 
715  prefix = ast_config_option(config, "general", "prefix");
716 
717  snprintf(server_uri, sizeof(server_uri), "http://%s:%s%s/%s", bindaddr, bindport, S_OR(prefix, ""), TEST_URI);
718 
720 
721  return 0;
722 }
723 
724 static int reload_module(void)
725 {
726  return process_config(1);
727 }
728 
729 static int load_module(void)
730 {
731  if (process_config(0)) {
733  }
734 
735  if (ast_http_uri_link(&test_uri)) {
737  }
738 
739  AST_TEST_REGISTER(create_nominal);
740 
741  AST_TEST_REGISTER(retrieve_nominal);
742  AST_TEST_REGISTER(retrieve_etag);
743  AST_TEST_REGISTER(retrieve_expires);
744  AST_TEST_REGISTER(retrieve_etag_expired);
745  AST_TEST_REGISTER(retrieve_cache_control_age);
746  AST_TEST_REGISTER(retrieve_cache_control_directives);
747  AST_TEST_REGISTER(retrieve_parsed_uri);
748  AST_TEST_REGISTER(retrieve_content_type);
749 
750  ast_test_register_init(CATEGORY, pre_test_cb);
751 
753 }
754 
755 static int unload_module(void)
756 {
758 
759  AST_TEST_UNREGISTER(create_nominal);
760 
761  AST_TEST_UNREGISTER(retrieve_nominal);
762  AST_TEST_UNREGISTER(retrieve_etag);
763  AST_TEST_UNREGISTER(retrieve_expires);
764  AST_TEST_UNREGISTER(retrieve_etag_expired);
765  AST_TEST_UNREGISTER(retrieve_cache_control_age);
766  AST_TEST_UNREGISTER(retrieve_cache_control_directives);
767  AST_TEST_UNREGISTER(retrieve_parsed_uri);
768  AST_TEST_UNREGISTER(retrieve_content_type);
769 
770  return 0;
771 }
772 
773 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP Media Cache Backend Tests",
774  .support_level = AST_MODULE_SUPPORT_CORE,
775  .load = load_module,
777  .unload = unload_module,
778  .requires = "res_http_media_cache",
779  );
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_log
Definition: astobj2.c:42
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
Bucket File API.
struct ast_bucket_file * ast_bucket_file_alloc(const char *uri)
Allocate a new bucket file.
Definition: bucket.c:663
int ast_bucket_file_temporary_create(struct ast_bucket_file *file)
Common file snapshot creation callback for creating a temporary file.
Definition: bucket.c:899
int ast_bucket_file_create(struct ast_bucket_file *file)
Create a new bucket file in backend storage.
Definition: bucket.c:725
int ast_bucket_file_delete(struct ast_bucket_file *file)
Delete a bucket file from backend storage.
Definition: bucket.c:844
struct ast_bucket_file * ast_bucket_file_retrieve(const char *uri)
Retrieve a bucket file.
Definition: bucket.c:815
int ast_bucket_file_is_stale(struct ast_bucket_file *file)
Retrieve whether or not the backing datastore views the bucket file as stale.
Definition: bucket.c:824
struct ast_sockaddr bindaddr
Definition: chan_ooh323.c:353
static const char config[]
Definition: chan_ooh323.c:111
static int enabled
Definition: dnsmgr.c:91
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static char prefix[MAX_PREFIX]
Definition: http.c:144
Support for Private Asterisk HTTP Servers.
void ast_http_send(struct ast_tcptls_session_instance *ser, enum ast_http_method method, int status_code, const char *status_title, struct ast_str *http_header, struct ast_str *out, int fd, unsigned int static_content)
Generic function for sending HTTP/1.1 response.
Definition: http.c:459
ast_http_method
HTTP Request methods known by Asterisk.
Definition: http.h:58
@ AST_HTTP_GET
Definition: http.h:60
void ast_http_uri_unlink(struct ast_http_uri *urihandler)
Unregister a URI handler.
Definition: http.c:708
void ast_http_request_close_on_completion(struct ast_tcptls_session_instance *ser)
Request the HTTP connection be closed after this HTTP request.
Definition: http.c:840
void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text)
Send HTTP error message and close socket.
Definition: http.c:651
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition: http.c:676
#define ast_config_load(filename, flags)
Load a config file.
@ CONFIG_FLAG_FILEUNCHANGED
#define CONFIG_STATUS_FILEUNCHANGED
#define CONFIG_STATUS_FILEINVALID
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
const char * ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
Retrieve a configuration variable within the configuration set.
Definition: main/config.c:758
#define LOG_ERROR
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2524
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
int errno
Asterisk module definitions.
@ AST_MODFLAG_DEFAULT
Definition: module.h:315
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ 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
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
def info(msg)
static int reload(void)
const char * method
Definition: res_pjsip.c:1130
#define NULL
Definition: resample.c:96
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2312
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1117
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:739
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
int attribute_pure ast_false(const char *val)
Make sure something is false. Determine if a string containing a boolean value is "false"....
Definition: main/utils.c:2114
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:640
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:711
Bucket file structure, contains reference to file and information about it.
Definition: bucket.h:78
Structure used to handle boolean flags.
Definition: utils.h:199
Definition of a URI handler.
Definition: http.h:102
const char * description
Definition: http.h:104
Support for dynamic strings.
Definition: strings.h:604
describes a server instance
Definition: tcptls.h:150
Contains all the initialization information required to store a new test definition.
Definition: test.h:214
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
const char * status_text
struct timeval expires
struct test_options::@530 cache_control
const char * content_type
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_status_update(a, b, c...)
Definition: test.h:129
#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 int process_config(int reload)
static int pre_test_cb(struct ast_test_info *info, struct ast_test *test)
static int http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers)
#define TEST_URI
#define VALIDATE_EXPIRES(test, bucket_file, expected, delta)
#define SET_OR_APPEND_CACHE_CONTROL(str)
static void bucket_file_cleanup(void *obj)
#define CATEGORY
#define VALIDATE_STR_METADATA(test, bucket_file, key, expected)
static int reload_module(void)
static struct test_options options
static int load_module(void)
static int unload_module(void)
AST_TEST_DEFINE(retrieve_content_type)
static struct ast_http_uri test_uri
static char server_uri[512]
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:157
int error(const char *format,...)
Definition: utils/frame.c:999
#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:936