diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index b6fde02b1e..20185fdc9f 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -2511,7 +2511,7 @@ static int has_voicemail(const char *mailbox, const char *folder) * * \return zero on success, -1 on error. */ -static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag) +static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag, const char *dest_folder) { struct vm_state *sendvms = NULL, *destvms = NULL; char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/ @@ -2529,7 +2529,7 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i } snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]); ast_mutex_lock(&sendvms->lock); - if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) { + if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, dest_folder)) == T)) { ast_mutex_unlock(&sendvms->lock); return 0; } @@ -5343,7 +5343,7 @@ static int has_voicemail(const char *mailbox, const char *folder) * * \return zero on success, -1 on error. */ -static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag) +static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag, const char *dest_folder) { char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX]; const char *frombox = mbox(vmu, imbox); @@ -5355,6 +5355,8 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) { /* If urgent, copy to Urgent folder */ userfolder = "Urgent"; + } else if (!ast_strlen_zero(dest_folder)) { + userfolder = dest_folder; } else { userfolder = "INBOX"; } @@ -6394,7 +6396,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ cntx++; } if ((recip = find_user(&recipu, cntx, exten))) { - copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag); + copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag, NULL); free_user(recip); } } @@ -7715,7 +7717,7 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st vmstmp.fn, vmstmp.introfn, fmt, duration, attach_user_voicemail, chan, NULL, urgent_str); #else - copy_msg_result = copy_message(chan, sender, 0, curmsg, duration, vmtmp, fmt, dir, urgent_str); + copy_msg_result = copy_message(chan, sender, 0, curmsg, duration, vmtmp, fmt, dir, urgent_str, NULL); #endif saved_messages++; AST_LIST_REMOVE_CURRENT(list); @@ -14126,6 +14128,39 @@ static struct ast_vm_msg_snapshot *vm_msg_snapshot_destroy(struct ast_vm_msg_sna return NULL; } +#ifdef TEST_FRAMEWORK + +int ast_vm_test_destroy_user(const char *context, const char *mailbox) +{ + struct ast_vm_user *vmu; + + AST_LIST_LOCK(&users); + AST_LIST_TRAVERSE_SAFE_BEGIN(&users, vmu, list) { + if (!strncmp(context, vmu->context, sizeof(context)) + && !strncmp(mailbox, vmu->mailbox, sizeof(mailbox))) { + AST_LIST_REMOVE_CURRENT(list); + ast_free(vmu); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&users); + return 0; +} + +int ast_vm_test_create_user(const char *context, const char *mailbox) +{ + struct ast_vm_user *vmu; + + if (!(vmu = find_or_create(context, mailbox))) { + return -1; + } + populate_defaults(vmu); + return 0; +} + +#endif + /*! * \brief Create and store off all the msgs in an open mailbox * @@ -14260,6 +14295,11 @@ struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_create(const char *mailb int inbox_index = 0; int old_index = 1; + if (ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, "Cannot create a mailbox snapshot since no mailbox was specified\n"); + return NULL; + } + memset(&vmus, 0, sizeof(vmus)); if (!(ast_strlen_zero(folder))) { @@ -14391,6 +14431,51 @@ struct ast_str *vm_mailbox_snapshot_str(const char *mailbox, const char *context return str; } +/*! + * \brief common bounds checking and existence check for Voicemail API functions. + * + * \details + * This is called by ast_vm_msg_move, ast_vm_msg_remove, and ast_vm_msg_forward to + * ensure that data passed in are valid. This tests the following: + * + * 1. No negative indexes are given. + * 2. No index greater than the highest message index for the folder is given. + * 3. All message indexes given point to messages that exist. + * + * \param vms The voicemail state corresponding to an open mailbox + * \param msg_ids An array of message identifiers + * \param num_msgs The number of identifiers in msg_ids + * + * \retval -1 Failure + * \retval 0 Success + */ +static int message_range_and_existence_check(struct vm_state *vms, int *msg_ids, size_t num_msgs) +{ + int i; + int res = 0; + for (i = 0; i < num_msgs; ++i) { + int cur_msg = msg_ids[i]; + if (cur_msg < 0) { + ast_log(LOG_WARNING, "Message has negative index\n"); + res = -1; + break; + } + if (vms->lastmsg < cur_msg) { + ast_log(LOG_WARNING, "Message %d is out of range. Last message is %d\n", cur_msg, vms->lastmsg); + res = -1; + break; + } + make_file(vms->fn, sizeof(vms->fn), vms->curdir, cur_msg); + if (!EXISTS(vms->curdir, cur_msg, vms->fn, NULL)) { + ast_log(LOG_WARNING, "Message %d does not exist.\n", cur_msg); + res = -1; + break; + } + } + + return res; +} + static void notify_new_state(struct ast_vm_user *vmu) { int new = 0, old = 0, urgent = 0; @@ -14418,17 +14503,36 @@ int ast_vm_msg_forward(const char *from_mailbox, struct ast_config *msg_cfg; struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; char filename[PATH_MAX]; - int from_folder_index = get_folder_by_name(from_folder); - int to_folder_index = get_folder_by_name(to_folder); + int from_folder_index; int open = 0; int res = 0; int i; + if (ast_strlen_zero(from_mailbox) || ast_strlen_zero(to_mailbox)) { + ast_log(LOG_WARNING, "Cannot forward message because either the from or to mailbox was not specified\n"); + return -1; + } + + if (!num_msgs) { + ast_log(LOG_WARNING, "Invalid number of messages specified to forward: %zu\n", num_msgs); + return -1; + } + + if (ast_strlen_zero(from_folder) || ast_strlen_zero(to_folder)) { + ast_log(LOG_WARNING, "Cannot forward message because the from_folder or to_folder was not specified\n"); + return -1; + } + memset(&vmus, 0, sizeof(vmus)); memset(&to_vmus, 0, sizeof(to_vmus)); memset(&from_vms, 0, sizeof(from_vms)); - if (to_folder_index == -1 || from_folder_index == -1) { + from_folder_index = get_folder_by_name(from_folder); + if (from_folder_index == -1) { + return -1; + } + + if (get_folder_by_name(to_folder) == -1) { return -1; } @@ -14455,20 +14559,30 @@ int ast_vm_msg_forward(const char *from_mailbox, open = 1; + if ((from_vms.lastmsg + 1) < num_msgs) { + ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", from_folder, num_msgs); + res = -1; + goto vm_forward_cleanup; + } + + if ((res = message_range_and_existence_check(&from_vms, msg_ids, num_msgs) < 0)) { + goto vm_forward_cleanup; + } + + /* Now we actually forward the messages */ for (i = 0; i < num_msgs; i++) { int cur_msg = msg_ids[i]; int duration = 0; const char *value; - if (cur_msg >= 0 && from_vms.lastmsg < cur_msg) { - /* msg does not exist */ - ast_log(LOG_WARNING, "msg %d does not exist to forward. Last msg is %d\n", cur_msg, from_vms.lastmsg); - continue; - } make_file(from_vms.fn, sizeof(from_vms.fn), from_vms.curdir, cur_msg); snprintf(filename, sizeof(filename), "%s.txt", from_vms.fn); RETRIEVE(from_vms.curdir, cur_msg, vmu->mailbox, vmu->context); msg_cfg = ast_config_load(filename, config_flags); + /* XXX This likely will not fail since we previously ensured that the + * message we are looking for exists. However, there still could be some + * circumstance where this fails, so atomicity is not guaranteed. + */ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) { DISPOSE(from_vms.curdir, cur_msg); continue; @@ -14477,7 +14591,7 @@ int ast_vm_msg_forward(const char *from_mailbox, duration = atoi(value); } - copy_message(NULL, vmu, from_folder_index, cur_msg, duration, to_vmu, vmfmts, from_vms.curdir, ""); + copy_message(NULL, vmu, from_folder_index, cur_msg, duration, to_vmu, vmfmts, from_vms.curdir, "", to_folder); if (delete_old) { from_vms.deleted[cur_msg] = 1; @@ -14520,12 +14634,30 @@ int ast_vm_msg_move(const char *mailbox, { struct vm_state vms; struct ast_vm_user *vmu = NULL, vmus; - int old_folder_index = get_folder_by_name(oldfolder); - int new_folder_index = get_folder_by_name(newfolder); + int old_folder_index; + int new_folder_index; int open = 0; int res = 0; int i; + if (ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, "Cannot move message because no mailbox was specified\n"); + return -1; + } + + if (!num_msgs) { + ast_log(LOG_WARNING, "Invalid number of messages specified to move: %zu\n", num_msgs); + return -1; + } + + if (ast_strlen_zero(oldfolder) || ast_strlen_zero(newfolder)) { + ast_log(LOG_WARNING, "Cannot move message because either oldfolder or newfolder was not specified\n"); + return -1; + } + + old_folder_index = get_folder_by_name(oldfolder); + new_folder_index = get_folder_by_name(newfolder); + memset(&vmus, 0, sizeof(vmus)); memset(&vms, 0, sizeof(vms)); @@ -14550,13 +14682,13 @@ int ast_vm_msg_move(const char *mailbox, open = 1; - for (i = 0; i < num_msgs; i++) { - if (vms.lastmsg < old_msg_nums[i]) { - /* msg does not exist */ - res = -1; - goto vm_move_cleanup; - } - if (save_to_folder(vmu, &vms, old_msg_nums[i], new_folder_index, (new_msg_nums + i))) { + if ((res = message_range_and_existence_check(&vms, old_msg_nums, num_msgs)) < 0) { + goto vm_move_cleanup; + } + + /* Now actually move the message */ + for (i = 0; i < num_msgs; ++i) { + if (save_to_folder(vmu, &vms, old_msg_nums[i], new_folder_index, new_msg_nums ? (new_msg_nums + i) : NULL)) { res = -1; goto vm_move_cleanup; } @@ -14595,14 +14727,30 @@ int ast_vm_msg_remove(const char *mailbox, { struct vm_state vms; struct ast_vm_user *vmu = NULL, vmus; - int folder_index = get_folder_by_name(folder); + int folder_index; int open = 0; int res = 0; int i; + if (ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, "Cannot remove message because no mailbox was specified\n"); + return -1; + } + + if (!num_msgs) { + ast_log(LOG_WARNING, "Invalid number of messages specified to remove: %zu\n", num_msgs); + return -1; + } + + if (ast_strlen_zero(folder)) { + ast_log(LOG_WARNING, "Cannot remove message because no folder was specified\n"); + return -1; + } + memset(&vmus, 0, sizeof(vmus)); memset(&vms, 0, sizeof(vms)); + folder_index = get_folder_by_name(folder); if (folder_index == -1) { ast_log(LOG_WARNING, "Could not remove msgs from unknown folder %s\n", folder); return -1; @@ -14626,13 +14774,17 @@ int ast_vm_msg_remove(const char *mailbox, open = 1; + if ((vms.lastmsg + 1) < num_msgs) { + ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", folder, num_msgs); + res = -1; + goto vm_remove_cleanup; + } + + if ((res = message_range_and_existence_check(&vms, msgs, num_msgs)) < 0) { + goto vm_remove_cleanup; + } + for (i = 0; i < num_msgs; i++) { - if (vms.lastmsg < msgs[i]) { - /* msg does not exist */ - ast_log(AST_LOG_ERROR, "Could not remove msg %d from folder %s because it does not exist.\n", msgs[i], folder); - res = -1; - goto vm_remove_cleanup; - } vms.deleted[msgs[i]] = 1; } @@ -14681,6 +14833,26 @@ int ast_vm_msg_play(struct ast_channel *chan, int res = 0; int open = 0; int i; + char filename[PATH_MAX]; + struct ast_config *msg_cfg; + struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; + int duration = 0; + const char *value; + + if (ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, "Cannot play message because no mailbox was specified\n"); + return -1; + } + + if (ast_strlen_zero(folder)) { + ast_log(LOG_WARNING, "Cannot play message because no folder was specified\n"); + return -1; + } + + if (ast_strlen_zero(msg_num)) { + ast_log(LOG_WARNING, "Cannot play message because no message number was specified\n"); + return -1; + } memset(&vmus, 0, sizeof(vmus)); memset(&vms, 0, sizeof(vms)); @@ -14690,69 +14862,60 @@ int ast_vm_msg_play(struct ast_channel *chan, } if (!(vmu = find_user(&vmus, context, mailbox))) { + return -1; + } + + i = get_folder_by_name(folder); + ast_copy_string(vms.username, mailbox, sizeof(vms.username)); + vms.lastmsg = -1; + if ((res = open_mailbox(&vms, vmu, i)) < 0) { + ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox); + goto play2_msg_cleanup; + } + open = 1; + + vms.curmsg = atoi(msg_num); + if (vms.curmsg > vms.lastmsg || vms.curmsg < 0) { + res = -1; goto play2_msg_cleanup; } - if (!ast_strlen_zero(msg_num) && !ast_strlen_zero(folder)) { - char filename[PATH_MAX]; - struct ast_config *msg_cfg; - struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; - int duration = 0; - const char *value; + /* Find the msg */ + make_file(vms.fn, sizeof(vms.fn), vms.curdir, vms.curmsg); + snprintf(filename, sizeof(filename), "%s.txt", vms.fn); + RETRIEVE(vms.curdir, vms.curmsg, vmu->mailbox, vmu->context); - i = get_folder_by_name(folder); - ast_copy_string(vms.username, mailbox, sizeof(vms.username)); - vms.lastmsg = -1; - if ((res = open_mailbox(&vms, vmu, i)) < 0) { - ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox); - res = -1; - goto play2_msg_cleanup; - } - open = 1; - - vms.curmsg = atoi(msg_num); - if (vms.curmsg > vms.lastmsg) { - res = -1; - goto play2_msg_cleanup; - } - - /* Find the msg */ - make_file(vms.fn, sizeof(vms.fn), vms.curdir, vms.curmsg); - snprintf(filename, sizeof(filename), "%s.txt", vms.fn); - RETRIEVE(vms.curdir, vms.curmsg, vmu->mailbox, vmu->context); - - msg_cfg = ast_config_load(filename, config_flags); - if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) { - DISPOSE(vms.curdir, vms.curmsg); - res = -1; - goto play2_msg_cleanup; - } - if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) { - duration = atoi(value); - } - ast_config_destroy(msg_cfg); - - vms.heard[vms.curmsg] = 1; + msg_cfg = ast_config_load(filename, config_flags); + if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) { + DISPOSE(vms.curdir, vms.curmsg); + res = -1; + goto play2_msg_cleanup; + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) { + duration = atoi(value); + } + ast_config_destroy(msg_cfg); #ifdef IMAP_STORAGE - /*IMAP storage stores any prepended message from a forward - * as a separate file from the rest of the message - */ - if (!ast_strlen_zero(vms.introfn) && ast_fileexists(vms.introfn, NULL, NULL) > 0) { - wait_file(chan, &vms, vms.introfn); - } -#endif - if (cb) { - cb(chan, vms.fn, duration); - } else if ((wait_file(chan, &vms, vms.fn)) < 0) { - ast_log(AST_LOG_WARNING, "Playback of message %s failed\n", vms.fn); - } else { - res = 0; - } - - /* cleanup configs and msg */ - DISPOSE(vms.curdir, vms.curmsg); + /*IMAP storage stores any prepended message from a forward + * as a separate file from the rest of the message + */ + if (!ast_strlen_zero(vms.introfn) && ast_fileexists(vms.introfn, NULL, NULL) > 0) { + wait_file(chan, &vms, vms.introfn); } +#endif + if (cb) { + cb(chan, vms.fn, duration); + } else if ((wait_file(chan, &vms, vms.fn)) < 0) { + ast_log(AST_LOG_WARNING, "Playback of message %s failed\n", vms.fn); + } else { + res = 0; + } + + vms.heard[vms.curmsg] = 1; + + /* cleanup configs and msg */ + DISPOSE(vms.curdir, vms.curmsg); play2_msg_cleanup: if (vmu && open) { diff --git a/apps/app_voicemail.exports.in b/apps/app_voicemail.exports.in index 6efe168e97..e66bb359c8 100644 --- a/apps/app_voicemail.exports.in +++ b/apps/app_voicemail.exports.in @@ -22,6 +22,8 @@ LINKER_SYMBOL_PREFIXast_vm_msg_forward; LINKER_SYMBOL_PREFIXast_vm_index_to_foldername; LINKER_SYMBOL_PREFIXast_vm_msg_play; + LINKER_SYMBOL_PREFIXast_vm_test_create_user; + LINKER_SYMBOL_PREFIXast_vm_test_destroy_user; local: *; }; diff --git a/include/asterisk/app_voicemail.h b/include/asterisk/app_voicemail.h index 3550b020de..11b1df4307 100644 --- a/include/asterisk/app_voicemail.h +++ b/include/asterisk/app_voicemail.h @@ -190,4 +190,25 @@ int ast_vm_msg_play(struct ast_channel *chan, * \retval other The name of the mailbox */ const char *ast_vm_index_to_foldername(unsigned int index); + +#ifdef TEST_FRAMEWORK +/*! + * \brief Add a user to the voicemail system for test purposes + * \param context The context of the mailbox + * \param mailbox The mailbox for the user + * \retval 0 success + * \retval other failure + */ +int ast_vm_test_create_user(const char *context, const char *mailbox); + +/*! + * \brief Dispose of a user. This should be used to destroy a user that was + * previously created using ast_vm_test_create_user + * \param context The context of the mailbox + * \param mailbox The mailbox for the user to destroy + */ +int ast_vm_test_destroy_user(const char *context, const char *mailbox); + +#endif + #endif diff --git a/tests/test_voicemail_api.c b/tests/test_voicemail_api.c new file mode 100644 index 0000000000..a0311143bf --- /dev/null +++ b/tests/test_voicemail_api.c @@ -0,0 +1,1415 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Matt Jordan + * + * Matt Jordan + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief Skeleton Test + * + * \author\verbatim Matt Jordan \endverbatim + * + * Tests for the publicly exposed Voicemail API + * \ingroup tests + */ + +/*** MODULEINFO + TEST_FRAMEWORK + core + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include + +#include "asterisk/utils.h" +#include "asterisk/module.h" +#include "asterisk/test.h" +#include "asterisk/paths.h" +#include "asterisk/channel.h" +#include "asterisk/app.h" +#include "asterisk/app_voicemail.h" + +/*! \internal \brief Permissions to set on the voicemail directories we create + * - taken from app_voicemail */ +#define VOICEMAIL_DIR_MODE 0777 + +/*! \internal \brief Permissions to set on the voicemail files we create + * - taken from app_voicemail */ +#define VOICEMAIL_FILE_MODE 0666 + +/*! \internal \brief The number of mock snapshot objects we use for tests */ +#define TOTAL_SNAPSHOTS 4 + +/*! \internal \brief Create and populate the mock message objects and create the + * envelope files on the file system */ +#define VM_API_TEST_SETUP do { \ + if (test_vm_api_test_setup()) { \ + VM_API_TEST_CLEANUP; \ + ast_test_status_update(test, "Failed to set up necessary mock objects for voicemail API test\n"); \ + return AST_TEST_FAIL; \ + } else { \ + int i = 0; \ + for (; i < TOTAL_SNAPSHOTS; i++) { \ + ast_test_status_update(test, "Created message in %s/%s with ID %s\n", \ + test_snapshots[i]->exten, test_snapshots[i]->folder_name, test_snapshots[i]->msg_id); \ + } \ +} } while (0) + +/*! \internal \brief Safely cleanup after a test run. This should be called both when a + * test fails and when it passes */ +#define VM_API_TEST_CLEANUP test_vm_api_test_teardown() + +/*! \internal \brief Safely cleanup a snapshot and a test run. Note that it assumes + * that the mailbox snapshot object is test_mbox_snapshot */ +#define VM_API_SNAPSHOT_TEST_CLEANUP \ + if (test_mbox_snapshot) { \ + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \ + } \ + VM_API_TEST_CLEANUP; \ + +/*! \internal \brief Verify the expected result from two string values obtained + * from a mailbox snapshot. Note that it assumes the mailbox snapshot + * object is test_mbox_snapshot + */ +#define VM_API_STRING_FIELD_VERIFY(expected, actual) do { \ + if (strncmp((expected), (actual), sizeof((expected)))) { \ + ast_test_status_update(test, "Test failed for parameter %s: Expected [%s], Actual [%s]\n", #actual, expected, actual); \ + VM_API_SNAPSHOT_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + +/*! \internal \brief Verify the expected result from two integer values. Note + * that it assumes the mailbox snapshot object is test_mbox_snapshot */ +#define VM_API_INT_VERIFY(expected, actual) do { \ + if ((expected) != (actual)) { \ + ast_test_status_update(test, "Test failed for parameter %s: Expected [%d], Actual [%d]\n", #actual, expected, actual); \ + VM_API_SNAPSHOT_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + +/*! \internal \brief Verify that a mailbox snapshot contains the expected message + * snapshot, in the correct position, with the expected values. Note + * that it assumes the mailbox snapshot object is test_mbox_snapshot + */ +#define VM_API_SNAPSHOT_MSG_VERIFY(expected, actual, expected_folder, expected_index) do { \ + struct ast_vm_msg_snapshot *msg; \ + int found = 0; \ + int counter = 0; \ + AST_LIST_TRAVERSE(&((actual)->snapshots[get_folder_by_name(expected_folder)]), msg, msg) { \ + if (!(strcmp(msg->msg_id, (expected)->msg_id))) { \ + ast_test_status_update(test, "Found message %s in snapshot\n", msg->msg_id); \ + found = 1; \ + if ((expected_index) != counter) { \ + ast_test_status_update(test, "Expected message %s at index %d; Actual [%d]\n", \ + (expected)->msg_id, (expected_index), counter); \ + VM_API_SNAPSHOT_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } \ + VM_API_STRING_FIELD_VERIFY((expected)->callerid, msg->callerid); \ + VM_API_STRING_FIELD_VERIFY((expected)->callerchan, msg->callerchan); \ + VM_API_STRING_FIELD_VERIFY((expected)->exten, msg->exten); \ + VM_API_STRING_FIELD_VERIFY((expected)->origdate, msg->origdate); \ + VM_API_STRING_FIELD_VERIFY((expected)->origtime, msg->origtime); \ + VM_API_STRING_FIELD_VERIFY((expected)->duration, msg->duration); \ + VM_API_STRING_FIELD_VERIFY((expected)->folder_name, msg->folder_name); \ + /* We are currently not going to check folder_dir, since its never written out. */ \ + /* VM_API_STRING_FIELD_VERIFY((expected)->folder_dir, msg->folder_dir); \ */ \ + VM_API_STRING_FIELD_VERIFY((expected)->flag, msg->flag); \ + VM_API_INT_VERIFY((expected)->msg_number, msg->msg_number); \ + break; \ + } \ + ++counter; \ + } \ + if (!found) { \ + ast_test_status_update(test, "Test failed for message snapshot %s: not found in mailbox snapshot\n", (expected)->msg_id); \ + VM_API_SNAPSHOT_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ +} } while (0) + + +/*! \internal \brief Create a message snapshot, failing the test if the snapshot could not be created. + * This requires having a snapshot named test_mbox_snapshot. + */ +#define VM_API_SNAPSHOT_CREATE(mailbox, context, folder, desc, sort, old_and_inbox) do { \ + if (!(test_mbox_snapshot = ast_vm_mailbox_snapshot_create( \ + (mailbox), (context), (folder), (desc), (sort), (old_and_inbox)))) { \ + ast_test_status_update(test, "Failed to create voicemail mailbox snapshot\n"); \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + +/*! \internal \brief Create a message snapshot, failing the test if the snapshot could be created. + * This is used to test off nominal conditions. + * This requires having a snapshot named test_mbox_snapshot. + */ +#define VM_API_SNAPSHOT_OFF_NOMINAL_TEST(mailbox, context, folder, desc, sort, old_and_inbox) do { \ + if ((test_mbox_snapshot = ast_vm_mailbox_snapshot_create( \ + (mailbox), (context), (folder), (desc), (sort), (old_and_inbox)))) { \ + ast_test_status_update(test, "Created mailbox snapshot when none was expected\n"); \ + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + +/*! \internal \brief Move a voicemail message, failing the test if the message could not be moved */ +#define VM_API_MOVE_MESSAGE(mailbox, context, number_of_messages, source, message_numbers_in, dest, message_numbers_out) do { \ + if (ast_vm_msg_move((mailbox), (context), (number_of_messages), (source), (message_numbers_in), (dest), (message_numbers_out))) { \ + ast_test_status_update(test, "Failed to move message %s@%s from %s to %s\n", \ + (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (source) ? (source) : "(NULL)", (dest) ? (dest) : "(NULL)"); \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + + /*! \internal \brief Attempt to move a voicemail message, failing the test if the message could be moved */ +#define VM_API_MOVE_MESSAGE_OFF_NOMINAL(mailbox, context, number_of_messages, source, message_numbers_in, dest, message_numbers_out) do { \ + if (!ast_vm_msg_move((mailbox), (context), (number_of_messages), (source), (message_numbers_in), (dest), (message_numbers_out))) { \ + ast_test_status_update(test, "Succeeded to move message %s@%s from %s to %s when we really shouldn't\n", \ + (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (source) ? (source) : "(NULL)", (dest) ? (dest) : "(NULL)"); \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + +/*! \internal \brief Remove a message, failing the test if the method failed or if the message is still present. */ +#define VM_API_REMOVE_MESSAGE(mailbox, context, number_of_messages, folder, message_numbers_in) do { \ + if (ast_vm_msg_remove((mailbox), (context), (number_of_messages), (folder), (message_numbers_in))) { \ + ast_test_status_update(test, "Failed to remove message from mailbox %s@%s, folder %s", \ + (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (folder) ? (folder) : "(NULL)"); \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } \ + VM_API_SNAPSHOT_CREATE((mailbox), (context), (folder), 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); \ + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 0); \ + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \ +} while (0) + +/*! \internal \brief Remove a message, failing the test if the method succeeds */ +#define VM_API_REMOVE_MESSAGE_OFF_NOMINAL(mailbox, context, number_of_messages, folder, message_numbers_in) do { \ + if (!ast_vm_msg_remove((mailbox), (context), (number_of_messages), (folder), (message_numbers_in))) { \ + ast_test_status_update(test, "Succeeded in removing message from mailbox %s@%s, folder %s, when expected result was failure\n", \ + (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (folder) ? (folder) : "(NULL)"); \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + +/*! \internal \brief Forward a message, failing the test if the message could not be forwarded */ +# define VM_API_FORWARD_MESSAGE(from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder, number_of_messages, message_numbers_in, delete_old) do { \ + if (ast_vm_msg_forward((from_mailbox), (from_context), (from_folder), (to_mailbox), (to_context), (to_folder), (number_of_messages), (message_numbers_in), (delete_old))) { \ + ast_test_status_update(test, "Failed to forward message from %s@%s [%s] to %s@%s [%s]\n", \ + (from_mailbox) ? (from_mailbox) : "(NULL)", (from_context) ? (from_context) : "(NULL)", (from_folder) ? (from_folder) : "(NULL)", \ + (to_mailbox) ? (to_mailbox) : "(NULL)", (to_context) ? (to_context) : "(NULL)", (to_folder) ? (to_folder) : "(NULL)"); \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + + /*! \internal \brief Forward a message, failing the test if the message was successfully forwarded */ +#define VM_API_FORWARD_MESSAGE_OFF_NOMINAL(from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder, number_of_messages, message_numbers_in, delete_old) do { \ + if (!ast_vm_msg_forward((from_mailbox), (from_context), (from_folder), (to_mailbox), (to_context), (to_folder), (number_of_messages), (message_numbers_in), (delete_old))) { \ + ast_test_status_update(test, "Succeeded in forwarding message from %s@%s [%s] to %s@%s [%s] when expected result was fail\n", \ + (from_mailbox) ? (from_mailbox) : "(NULL)", (from_context) ? (from_context) : "(NULL)", (from_folder) ? (from_folder) : "(NULL)", \ + (to_mailbox) ? (to_mailbox) : "(NULL)", (to_context) ? (to_context) : "(NULL)", (to_folder) ? (to_folder) : "(NULL)"); \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + +/*! \internal \brief Playback a message on a channel or callback function. Note that the channel name must be test_channel. + * Fail the test if the message could not be played. */ +#define VM_API_PLAYBACK_MESSAGE(channel, mailbox, context, folder, message, callback_fn) do { \ + if (ast_vm_msg_play((channel), (mailbox), (context), (folder), (message), (callback_fn))) { \ + ast_test_status_update(test, "Failed nominal playback message test\n"); \ + if (test_channel) { \ + ast_hangup(test_channel); \ + } \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + +/*! \internal \brief Playback a message on a channel or callback function. Note that the channel name must be test_channel. + * Fail the test if the message is successfully played */ +#define VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(channel, mailbox, context, folder, message, callback_fn) do { \ + if (!ast_vm_msg_play((channel), (mailbox), (context), (folder), (message), (callback_fn))) { \ + ast_test_status_update(test, "Succeeded in playing back of message when expected result was to fail\n"); \ + if (test_channel) { \ + ast_hangup(test_channel); \ + } \ + VM_API_TEST_CLEANUP; \ + return AST_TEST_FAIL; \ + } } while (0) + + +/*! \internal \brief Possible names of folders. Taken from app_voicemail */ +static const char * const mailbox_folders[] = { + "INBOX", + "Old", + "Work", + "Family", + "Friends", + "Cust1", + "Cust2", + "Cust3", + "Cust4", + "Cust5", + "Deleted", + "Urgent", +}; + +/*! \internal \brief Message snapshots representing the messages that are used by the various tests */ +static struct ast_vm_msg_snapshot *test_snapshots[TOTAL_SNAPSHOTS]; + +/*! \internal \brief Tracks whether or not we entered into the message playback callback function */ +static int global_entered_playback_callback = 0; + +/*! \internal \brief Get a folder index by its name */ +static int get_folder_by_name(const char *folder) +{ + size_t i; + + for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) { + if (strcasecmp(folder, mailbox_folders[i]) == 0) { + return i; + } + } + + return -1; +} + +/*! \internal \brief Get a mock snapshot object + * \param context The mailbox context + * \param exten The mailbox extension + * \param callerid The caller ID of the person leaving the message + * \returns an ast_vm_msg_snapshot object on success + * \returns NULL on error + */ +static struct ast_vm_msg_snapshot *test_vm_api_create_mock_snapshot(const char *context, const char *exten, const char *callerid) +{ + char msg_id_hash[AST_MAX_CONTEXT + AST_MAX_EXTENSION + sizeof(callerid) + 1]; + char msg_id_buf[256]; + struct ast_vm_msg_snapshot *snapshot; + + snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", exten, context, callerid); + snprintf(msg_id_buf, sizeof(msg_id_buf), "%ld-%d", (long)time(NULL), ast_str_hash(msg_id_hash)); + + if ((snapshot = ast_calloc(1, sizeof(*snapshot)))) { + ast_string_field_init(snapshot, 128); + ast_string_field_set(snapshot, msg_id, msg_id_buf); + ast_string_field_set(snapshot, exten, exten); + ast_string_field_set(snapshot, callerid, callerid); + } + return snapshot; +} + +/*! \internal \brief Make a voicemail mailbox folder based on the values provided in a message snapshot + * \param snapshot The snapshot containing the information to create the folder from + * \returns 0 on success + * \returns 1 on failure + */ +static int test_vm_api_create_voicemail_folder(struct ast_vm_msg_snapshot *snapshot) +{ + mode_t mode = VOICEMAIL_DIR_MODE; + int res; + + if ((res = ast_mkdir(snapshot->folder_dir, mode))) { + ast_log(AST_LOG_ERROR, "ast_mkdir '%s' failed: %s\n", snapshot->folder_dir, strerror(res)); + return 1; + } + return 0; +} + +/*! \internal \brief Create the voicemail files specified by a snapshot + * \param context The context of the mailbox + * \param mailbox The actual mailbox + * \param snapshot The message snapshot object containing the relevant envelope data + * \note This will symbolic link the sound file 'beep.gsm' to act as the 'sound' portion of the voicemail. + * Certain actions in app_voicemail will fail if an actual sound file does not exist + * \returns 0 on success + * \returns 1 on any failure + */ +static int test_vm_api_create_voicemail_files(const char *context, const char *mailbox, struct ast_vm_msg_snapshot *snapshot) +{ + FILE *msg_file; + char folder_path[PATH_MAX]; + char msg_path[PATH_MAX]; + char snd_path[PATH_MAX]; + char beep_path[PATH_MAX]; + + /* Note that we create both the text and a dummy sound file here. Without + * the sound file, a number of the voicemail operations 'silently' fail, as it + * does not believe that an actual voicemail exists + */ + snprintf(folder_path, sizeof(folder_path), "%s/voicemail/%s/%s/%s", + ast_config_AST_SPOOL_DIR, context, mailbox, snapshot->folder_name); + ast_string_field_set(snapshot, folder_dir, folder_path); + snprintf(msg_path, sizeof(msg_path), "%s/msg%04d.txt", + snapshot->folder_dir, snapshot->msg_number); + snprintf(snd_path, sizeof(snd_path), "%s/msg%04d.gsm", + snapshot->folder_dir, snapshot->msg_number); + snprintf(beep_path, sizeof(beep_path), "%s/sounds/en/beep.gsm", ast_config_AST_VAR_DIR); + + if (test_vm_api_create_voicemail_folder(snapshot)) { + return 1; + } + + if (ast_lock_path(snapshot->folder_dir) == AST_LOCK_FAILURE) { + ast_log(AST_LOG_ERROR, "Unable to lock directory %s\n", snapshot->folder_dir); + return 1; + } + + if (symlink(beep_path, snd_path)) { + ast_unlock_path(snapshot->folder_dir); + ast_log(AST_LOG_ERROR, "Failed to create a symbolic link from %s to %s: %s\n", + beep_path, snd_path, strerror(errno)); + return 1; + } + + if (!(msg_file = fopen(msg_path, "w"))) { + /* Attempt to remove the sound file */ + unlink(snd_path); + ast_unlock_path(snapshot->folder_dir); + ast_log(AST_LOG_ERROR, "Failed to open %s for writing\n", msg_path); + return 1; + } + + fprintf(msg_file, ";\n; Message Information file\n;\n" + "[message]\n" + "origmailbox=%s\n" + "context=%s\n" + "macrocontext=%s\n" + "exten=%s\n" + "rdnis=%s\n" + "priority=%d\n" + "callerchan=%s\n" + "callerid=%s\n" + "origdate=%s\n" + "origtime=%s\n" + "category=%s\n" + "msg_id=%s\n" + "flag=%s\n" + "duration=%s\n", + mailbox, + context, + "", + snapshot->exten, + "unknown", + 1, + snapshot->callerchan, + snapshot->callerid, + snapshot->origdate, + snapshot->origtime, + "", + snapshot->msg_id, + snapshot->flag, + snapshot->duration); + fclose(msg_file); + + if (chmod(msg_path, VOICEMAIL_FILE_MODE) < 0) { + ast_unlock_path(snapshot->folder_dir); + ast_log(AST_LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", msg_path, strerror(errno)); + return 1; + } + ast_unlock_path(snapshot->folder_dir); + + return 0; +} + +/*! \internal \brief Destroy the voicemail on the file system associated with a snapshot + * \param snapshot The snapshot describing the voicemail + */ +static void test_vm_api_remove_voicemail(struct ast_vm_msg_snapshot *snapshot) +{ + char msg_path[PATH_MAX]; + char snd_path[PATH_MAX]; + char folder_path[PATH_MAX]; + + if (!snapshot) { + return; + } + + if (ast_strlen_zero(snapshot->folder_dir)) { + snprintf(folder_path, sizeof(folder_path), "%s/voicemail/%s/%s/%s", + ast_config_AST_SPOOL_DIR, "default", snapshot->exten, snapshot->folder_name); + ast_string_field_set(snapshot, folder_dir, folder_path); + } + + snprintf(msg_path, sizeof(msg_path), "%s/msg%04d.txt", + snapshot->folder_dir, snapshot->msg_number); + snprintf(snd_path, sizeof(snd_path), "%s/msg%04d.gsm", + snapshot->folder_dir, snapshot->msg_number); + unlink(msg_path); + unlink(snd_path); + + return; +} + +/*! \internal \brief Destroy the voicemails associated with a mailbox snapshot + * \param mailbox The actual mailbox name + * \param mailbox_snapshot The mailbox snapshot containing the voicemails to destroy + * \note Its necessary to specify not just the snapshot, but the mailbox itself. The + * message snapshots contained in the snapshot may have originated from a different mailbox + * then the one we're destroying, which means that we can't determine the files to delete + * without knowing the actual mailbox they exist in. + */ +static void test_vm_api_destroy_mailbox_voicemails(const char *mailbox, struct ast_vm_mailbox_snapshot *mailbox_snapshot) +{ + struct ast_vm_msg_snapshot *msg; + int i; + + for (i = 0; i < 12; ++i) { + AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[i], msg, msg) { + ast_string_field_set(msg, exten, mailbox); + test_vm_api_remove_voicemail(msg); + } + } +} + +/*! \internal \brief Use snapshots to remove all messages in the mailboxes */ +static void test_vm_api_remove_all_messages(void) +{ + struct ast_vm_mailbox_snapshot *mailbox_snapshot; + + /* Take a snapshot of each mailbox and remove the contents. Note that we need to use + * snapshots of the mailboxes in addition to our tracked test snapshots, as there's a good chance + * we've created copies of the snapshots */ + if ((mailbox_snapshot = ast_vm_mailbox_snapshot_create("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0))) { + test_vm_api_destroy_mailbox_voicemails("test_vm_api_1234", mailbox_snapshot); + mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot); + } else { + ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot - could not remove test messages for test_vm_api_1234\n"); + } + if ((mailbox_snapshot = ast_vm_mailbox_snapshot_create("test_vm_api_2345", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0))) { + test_vm_api_destroy_mailbox_voicemails("test_vm_api_2345", mailbox_snapshot); + mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot); + } else { + ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot - could not remove test messages for test_vm_api_2345\n"); + } +} + +/*! \internal \brief Set up the necessary voicemails for a unit test run + * \note + * This creates 4 voicemails, stores them on the file system, and creates snapshot objects + * representing them for expected/actual value comparisons in the array test_snapshots. + * + * test_snapshots[0] => in test_vm_1234@default, folder INBOX, message 0 + * test_snapshots[1] => in test_vm_1234@default, folder Old, message 0 + * test_snapshots[2] => in test_vm_2345@default, folder INBOX, message 0 + * test_snapshots[3] => in test_vm_2345@default, folder Old, message 1 + * + * \returns 0 on success + * \returns 1 on failure + */ +static int test_vm_api_test_setup(void) +{ + int i, res = 0; + struct ast_vm_msg_snapshot *msg_one = NULL; + struct ast_vm_msg_snapshot *msg_two = NULL; + struct ast_vm_msg_snapshot *msg_three = NULL; + struct ast_vm_msg_snapshot *msg_four = NULL; + + /* Make the four sample voicemails */ + if ( !((msg_one = test_vm_api_create_mock_snapshot("default", "test_vm_api_1234", "\"Phil\" <2000>"))) + || !((msg_two = test_vm_api_create_mock_snapshot("default", "test_vm_api_1234", "\"Noel\" <8000>"))) + || !((msg_three = test_vm_api_create_mock_snapshot("default", "test_vm_api_2345", "\"Phil\" <2000>"))) + || !((msg_four = test_vm_api_create_mock_snapshot("default", "test_vm_api_2345", "\"Bill\" <3000>")))) { + ast_log(AST_LOG_ERROR, "Failed to create mock snapshots for test\n"); + ast_free(msg_one); + ast_free(msg_two); + ast_free(msg_three); + ast_free(msg_four); + return 1; + } + + /* Create the voicemail users */ + if (ast_vm_test_create_user("default", "test_vm_api_1234") + || ast_vm_test_create_user("default", "test_vm_api_2345")) { + ast_log(AST_LOG_ERROR, "Failed to create test voicemail users\n"); + ast_free(msg_one); + ast_free(msg_two); + ast_free(msg_three); + ast_free(msg_four); + /* Note that the cleanup macro will ensure that any test user that + * was successfully created is removed + */ + return 1; + } + + /* Now that the users exist from the perspective of the voicemail + * application, attempt to remove any existing voicemails + */ + test_vm_api_remove_all_messages(); + + /* Set the basic properties on each */ + ast_string_field_set(msg_one, callerchan, "SIP/2000-00000000"); + ast_string_field_set(msg_one, origdate, "Mon Mar 19 04:14:21 PM UTC 2012"); + ast_string_field_set(msg_one, origtime, "1332173661"); + ast_string_field_set(msg_one, duration, "8"); + ast_string_field_set(msg_one, folder_name, "Old"); + msg_one->msg_number = 0; + test_snapshots[0] = msg_one; + + ast_string_field_set(msg_two, callerchan, "SIP/8000-00000001"); + ast_string_field_set(msg_two, origdate, "Mon Mar 19 06:16:13 PM UTC 2012"); + ast_string_field_set(msg_two, origtime, "1332180973"); + ast_string_field_set(msg_two, duration, "24"); + ast_string_field_set(msg_two, folder_name, "INBOX"); + msg_two->msg_number = 0; + test_snapshots[1] = msg_two; + + ast_string_field_set(msg_three, callerchan, "IAX/2000-000000a3"); + ast_string_field_set(msg_three, origdate, "Thu Mar 22 23:13:03 PM UTC 2012"); + ast_string_field_set(msg_three, origtime, "1332181251"); + ast_string_field_set(msg_three, duration, "25"); + ast_string_field_set(msg_three, folder_name, "INBOX"); + msg_three->msg_number = 0; + test_snapshots[2] = msg_three; + + ast_string_field_set(msg_four, callerchan, "DAHDI/3000-00000010"); + ast_string_field_set(msg_four, origdate, "Fri Mar 23 03:01:03 AM UTC 2012"); + ast_string_field_set(msg_four, origtime, "1332181362"); + ast_string_field_set(msg_four, duration, "13"); + ast_string_field_set(msg_four, folder_name, "INBOX"); + msg_three->msg_number = 1; + test_snapshots[3] = msg_four; + + /* Store the messages */ + for (i = 0; i < TOTAL_SNAPSHOTS; ++i) { + if (test_vm_api_create_voicemail_files("default", test_snapshots[i]->exten, test_snapshots[i])) { + /* On a failure, the test_vm_api_test_teardown method will remove and + * unlink any created files. Since we failed to create the file, clean + * up the object here instead */ + ast_log(AST_LOG_ERROR, "Failed to store voicemail %s/%s\n", + "default", test_snapshots[i]->exten); + ast_free(test_snapshots[i]); + test_snapshots[i] = NULL; + res = 1; + } + } + + return res; +} + +static void test_vm_api_test_teardown(void) +{ + int i; + + /* Remove our test message snapshots */ + for (i = 0; i < TOTAL_SNAPSHOTS; ++i) { + test_vm_api_remove_voicemail(test_snapshots[i]); + ast_free(test_snapshots[i]); + test_snapshots[i] = NULL; + } + + test_vm_api_remove_all_messages(); + + /* Remove the test users */ + ast_vm_test_destroy_user("default", "test_vm_api_1234"); + ast_vm_test_destroy_user("default", "test_vm_api_2345"); +} + +/*! \internal \brief Update the test snapshots with a new mailbox snapshot + * \param mailbox_snapshot The new mailbox shapshot to update the test snapshots with + */ +static void test_vm_api_update_test_snapshots(struct ast_vm_mailbox_snapshot *mailbox_snapshot) +{ + int i, j; + char folder_path[PATH_MAX]; + struct ast_vm_msg_snapshot *msg; + + for (i = 0; i < TOTAL_SNAPSHOTS; ++i) { + for (j = 0; j < 12; ++j) { + AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[j], msg, msg) { + if (!strcmp(msg->msg_id, test_snapshots[i]->msg_id)) { + ast_string_field_set(test_snapshots[i], callerid, msg->callerid); + ast_string_field_set(test_snapshots[i], callerchan, msg->callerchan); + ast_string_field_set(test_snapshots[i], exten, msg->exten); + ast_string_field_set(test_snapshots[i], origdate, msg->origdate); + ast_string_field_set(test_snapshots[i], origtime, msg->origtime); + ast_string_field_set(test_snapshots[i], duration, msg->duration); + ast_string_field_set(test_snapshots[i], folder_name, msg->folder_name); + /* TODO: because the folder_dir isn't updated in a snapshot, this will + * always be NULL. We need to recreate the folder path here + ast_string_field_set(test_snapshots[i], folder_dir, msg->folder_dir); + */ + snprintf(folder_path, sizeof(folder_path), "%s/voicemail/%s/%s/%s", + ast_config_AST_SPOOL_DIR, "default", test_snapshots[i]->exten, test_snapshots[i]->folder_name); + ast_string_field_set(test_snapshots[i], folder_dir, folder_path); + ast_string_field_set(test_snapshots[i], flag, msg->flag); + test_snapshots[i]->msg_number = msg->msg_number; + } + } + } + } +} + +/*! \internal \brief A callback function for message playback + * \param chan The channel the file would be played back on + * \param file The file to play back + * \param duration The duration of the file + * \note This sets global_entered_playback_callback to 1 if the parameters + * passed to the callback are minimally valid + */ +static void message_playback_callback_fn(struct ast_channel *chan, const char *file, int duration) +{ + if ((chan) && !ast_strlen_zero(file) && duration > 0) { + global_entered_playback_callback = 1; + } else { + ast_log(AST_LOG_WARNING, "Entered into message playback callback function with invalid parameters\n"); + } +} + +/*! \internal \brief Dummy channel write function for mock_channel_tech */ +static int test_vm_api_mock_channel_write(struct ast_channel *chan, struct ast_frame *frame) +{ + return 0; +} + +/*! \internal \brief Dummy channel read function for mock_channel_tech */ +static struct ast_frame *test_vm_api_mock_channel_read(struct ast_channel *chan) +{ + return &ast_null_frame; +} + +/*! \internal \brief A dummy channel technology */ +static const struct ast_channel_tech mock_channel_tech = { + .write = test_vm_api_mock_channel_write, + .read = test_vm_api_mock_channel_read, +}; + +/*! \internal \brief Create a dummy channel suitable for 'playing back' gsm sound files on + * \returns a channel on success + * \returns NULL on failure + */ +static struct ast_channel *test_vm_api_create_mock_channel(void) +{ + struct ast_channel *mock_channel; + + if (!(mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, 0, 0, "TestChannel"))) { + return NULL; + } + + mock_channel->nativeformats = AST_FORMAT_GSM; + mock_channel->writeformat = AST_FORMAT_GSM; + mock_channel->rawwriteformat = AST_FORMAT_GSM; + mock_channel->readformat = AST_FORMAT_GSM; + mock_channel->rawreadformat = AST_FORMAT_GSM; + mock_channel->tech = &mock_channel_tech; + + return mock_channel; +} + +AST_TEST_DEFINE(voicemail_api_nominal_snapshot) +{ + struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = "nominal_snapshot"; + info->category = "/main/voicemail_api/"; + info->summary = "Nominal mailbox snapshot tests"; + info->description = + "Test retrieving mailbox snapshots"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + ast_test_status_update(test, "Test retrieving message 1 from INBOX of test_vm_1234\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(1, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0); + ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test retrieving message 0 from Old of test_vm_1234\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(1, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0); + ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test retrieving message 0, 1 from Old and INBOX of test_vm_1234 ordered by time\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 1); + VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 0); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 1); + + ast_test_status_update(test, "Test retrieving message 1, 0 from Old and INBOX of test_vm_1234 ordered by time desc\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 1, AST_VM_SNAPSHOT_SORT_BY_TIME, 1); + VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 1); + ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test retrieving message 0, 1 from Old and INBOX of test_vm_1234 ordered by id\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_ID, 1); + VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 1); + ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test retrieving message 1, 0 from Old and INBOX of test_vm_1234 ordered by id desc\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 1, AST_VM_SNAPSHOT_SORT_BY_ID, 1); + VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 0); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 1); + ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234 ordered by id\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0); + VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0); + ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234 ordered by time\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0); + ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234, default context ordered by time\n"); + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", NULL, NULL, 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0); + VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0); + ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_off_nominal_snapshot) +{ + struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = "off_nominal_snapshot"; + info->category = "/main/voicemail_api/"; + info->summary = "Off nominal mailbox snapshot tests"; + info->description = + "Test off nominal requests for mailbox snapshots. This includes" + " testing the following:\n" + " * Access to non-exisstent mailbox\n" + " * Access to NULL mailbox\n" + " * Access to non-existent context\n" + " * Access to non-existent folder\n" + " * Access to NULL folder\n" + " * Invalid sort identifier\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + ast_test_status_update(test, "Test access to non-existent mailbox test_vm_api_3456\n"); + VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_3456", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + + ast_test_status_update(test, "Test access to null mailbox\n"); + VM_API_SNAPSHOT_OFF_NOMINAL_TEST(NULL, "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + + ast_test_status_update(test, "Test access non-existent context test_vm_api_defunct\n"); + VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_1234", "test_vm_api_defunct", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + + ast_test_status_update(test, "Test non-existent folder test_vm_api_platypus\n"); + VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_1234", "default", "test_vm_api_platypus", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_nominal_move) +{ + struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL; + int single_msg_nums[] = { 0, }; + int multi_msg_nums[] = { 0, 1, }; + int output_msg_nums[] = { -1, -1, }; + + switch (cmd) { + case TEST_INIT: + info->name = "nominal_move"; + info->category = "/main/voicemail_api/"; + info->summary = "Nominal move voicemail tests"; + info->description = + "Test nominal requests to move a voicemail to a different" + " folder. This includes moving messages given a context," + " given a NULL context, and moving multiple messages"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + ast_test_status_update(test, "Test move of test_vm_api_1234 message from INBOX to Family\n"); + VM_API_MOVE_MESSAGE("test_vm_api_1234", "default", 1, "INBOX", single_msg_nums, "Family", output_msg_nums); + VM_API_INT_VERIFY(output_msg_nums[0], 0); + + ast_test_status_update(test, "Test move of test_vm_api_1234 message from Old to Family\n"); + VM_API_MOVE_MESSAGE("test_vm_api_1234", NULL, 1, "Old", single_msg_nums, "Family", output_msg_nums); + + /* Take a snapshot and update the test snapshots for verification */ + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + test_vm_api_update_test_snapshots(test_mbox_snapshot); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + VM_API_INT_VERIFY(output_msg_nums[0], 1); + VM_API_STRING_FIELD_VERIFY(test_snapshots[0]->folder_name, "Family"); + VM_API_STRING_FIELD_VERIFY(test_snapshots[1]->folder_name, "Family"); + VM_API_INT_VERIFY(test_snapshots[1]->msg_number, 0); + VM_API_INT_VERIFY(test_snapshots[0]->msg_number, 1); + + /* Move both of the 2345 messages to Family */ + ast_test_status_update(test, "Test move of test_vm_api_2345 messages from Inbox to Family\n"); + VM_API_MOVE_MESSAGE("test_vm_api_2345", "default", 2, "INBOX", multi_msg_nums, "Family", output_msg_nums); + + /* Take a snapshot and update the test snapshots for verification */ + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + test_vm_api_update_test_snapshots(test_mbox_snapshot); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + VM_API_INT_VERIFY(output_msg_nums[0], 0); + VM_API_INT_VERIFY(output_msg_nums[1], 1); + VM_API_STRING_FIELD_VERIFY(test_snapshots[2]->folder_name, "Family"); + VM_API_STRING_FIELD_VERIFY(test_snapshots[3]->folder_name, "Family"); + + ast_test_status_update(test, "Test move of test_vm_api_2345 message from Family to INBOX\n"); + VM_API_MOVE_MESSAGE("test_vm_api_2345", "default", 2, "Family", multi_msg_nums, "INBOX", NULL); + + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + test_vm_api_update_test_snapshots(test_mbox_snapshot); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + VM_API_STRING_FIELD_VERIFY(test_snapshots[2]->folder_name, "INBOX"); + VM_API_STRING_FIELD_VERIFY(test_snapshots[3]->folder_name, "INBOX"); + + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_off_nominal_move) +{ + int single_msg_nums[] = { 0, }; + int multi_msg_nums[] = { 0, 1, 2, 3}; + + switch (cmd) { + case TEST_INIT: + info->name = "off_nominal_move"; + info->category = "/main/voicemail_api/"; + info->summary = "Off nominal mailbox message move tests"; + info->description = + "Test nominal requests to move a voicemail to a different" + " folder. This includes testing the following:\n" + " * Moving to a non-existent mailbox\n" + " * Moving to a NULL mailbox\n" + " * Moving to a non-existent context\n" + " * Moving to/from non-existent folder\n" + " * Moving to/from NULL folder\n" + " * Invalid message identifier(s)\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + ast_test_status_update(test, "Test move attempt for invalid mailbox test_vm_3456\n"); + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", 1, "INBOX", single_msg_nums, "Family", NULL); + + VM_API_MOVE_MESSAGE_OFF_NOMINAL(NULL, "default", 1, "INBOX", single_msg_nums, "Family", NULL); + + ast_test_status_update(test, "Test move attempt for invalid context test_vm_api_defunct\n"); + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "test_vm_api_defunct", 1, "INBOX", single_msg_nums, "Family", NULL); + + ast_test_status_update(test, "Test move attempt to invalid folder\n"); + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", single_msg_nums, "SPAMALOT", NULL); + + ast_test_status_update(test, "Test move attempt from invalid folder\n"); + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "MEATINACAN", single_msg_nums, "Family", NULL); + + ast_test_status_update(test, "Test move attempt to NULL folder\n"); + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", single_msg_nums, NULL, NULL); + + ast_test_status_update(test, "Test move attempt from NULL folder\n"); + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, NULL, single_msg_nums, "Family", NULL); + + ast_test_status_update(test, "Test move attempt with non-existent message number\n"); + single_msg_nums[0] = 6; + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", single_msg_nums, "Family", NULL); + + ast_test_status_update(test, "Test move attempt with invalid message number\n"); + single_msg_nums[0] = -4; + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", single_msg_nums, "Family", NULL); + + ast_test_status_update(test, "Test move attempt with 0 number of messages\n"); + single_msg_nums[0] = 0; + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 0, "INBOX", single_msg_nums, "Family", NULL); + + ast_test_status_update(test, "Test move attempt with invalid number of messages\n"); + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", -30, "INBOX", single_msg_nums, "Family", NULL); + + ast_test_status_update(test, "Test move attempt with non-existent multiple messages, where some messages exist\n"); + VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 4, "INBOX", multi_msg_nums, "Family", NULL); + + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_nominal_remove) +{ + struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL; + int single_msg_nums[] = { 0, }; + int multi_msg_nums[] = { 0, 1, }; + + switch (cmd) { + case TEST_INIT: + info->name = "nominal_remove"; + info->category = "/main/voicemail_api/"; + info->summary = "Nominal mailbox remove message tests"; + info->description = + "Tests removing messages from voicemail folders. Includes" + " both removing messages one at a time, and in a set"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + ast_test_status_update(test, "Test removing a single message from INBOX\n"); + VM_API_REMOVE_MESSAGE("test_vm_api_1234", "default", 1, "INBOX", single_msg_nums); + + ast_test_status_update(test, "Test removing a single message from Old\n"); + VM_API_REMOVE_MESSAGE("test_vm_api_1234", "default", 1, "Old", single_msg_nums); + + ast_test_status_update(test, "Test removing multiple messages from INBOX\n"); + VM_API_REMOVE_MESSAGE("test_vm_api_2345", "default", 2, "INBOX", multi_msg_nums); + + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_off_nominal_remove) +{ + int single_msg_nums[] = { 0, }; + int multi_msg_nums[] = { 0, 1, }; + int empty_msg_nums[] = { }; + + switch (cmd) { + case TEST_INIT: + info->name = "off_nominal_remove"; + info->category = "/main/voicemail_api/"; + info->summary = "Off nominal mailbox message removal tests"; + info->description = + "Test off nominal requests for removing messages from " + "a mailbox. This includes:\n" + " * Removing messages with an invalid mailbox\n" + " * Removing messages from a NULL mailbox\n" + " * Removing messages from an invalid context\n" + " * Removing messages from an invalid folder\n" + " * Removing messages from a NULL folder\n" + " * Removing messages with bad identifiers\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + ast_test_status_update(test, "Test removing a single message with an invalid mailbox\n"); + VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", 1, "INBOX", single_msg_nums); + + ast_test_status_update(test, "Test removing a single message with a NULL mailbox\n"); + VM_API_REMOVE_MESSAGE_OFF_NOMINAL(NULL, "default", 1, "INBOX", single_msg_nums); + + ast_test_status_update(test, "Test removing a single message with an invalid context\n"); + VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "defunct", 1, "INBOX", single_msg_nums); + + ast_test_status_update(test, "Test removing a single message with an invalid folder\n"); + VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "SPAMINACAN", single_msg_nums); + + ast_test_status_update(test, "Test removing a single message with a NULL folder\n"); + VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, NULL, single_msg_nums); + + ast_test_status_update(test, "Test removing a single message with an invalid message number\n"); + single_msg_nums[0] = 6; + VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", single_msg_nums); + + ast_test_status_update(test, "Test removing multiple messages with a single invalid message number\n"); + multi_msg_nums[1] = 6; + VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", 2, "INBOX", multi_msg_nums); + + ast_test_status_update(test, "Test removing no messages with no message numbers\n"); + VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 0, "INBOX", empty_msg_nums); + + ast_test_status_update(test, "Test removing multiple messages with an invalid size specifier\n"); + VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", -30, "INBOX", multi_msg_nums); + + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_nominal_forward) +{ + struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL; + int single_msg_nums[] = { 0, }; + int multi_msg_nums[] = { 0, 1, }; + + switch (cmd) { + case TEST_INIT: + info->name = "nominal_forward"; + info->category = "/main/voicemail_api/"; + info->summary = "Nominal message forward tests"; + info->description = + "Tests the nominal cases of forwarding messages" + " between mailboxes"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX\n"); + VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, single_msg_nums, 0); + + /* Make sure we didn't delete the message */ + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + /* We should now have a total of 3 messages in test_vm_api_2345 INBOX */ + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 3); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX with default context to test_vm_api_2345 INBOX\n"); + VM_API_FORWARD_MESSAGE("test_vm_api_1234", NULL, "INBOX", "test_vm_api_2345", "default", "INBOX", 1, single_msg_nums, 0); + + /* Make sure we didn't delete the message */ + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + /* We should now have a total of 4 messages in test_vm_api_2345 INBOX */ + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 4); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX with default context\n"); + VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", NULL, "INBOX", 1, single_msg_nums, 0); + + /* Make sure we didn't delete the message */ + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + /* We should now have a total of 5 messages in test_vm_api_2345 INBOX */ + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 5); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX, deleting original\n"); + VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", NULL, "INBOX", 1, single_msg_nums, 1); + + /* Make sure we deleted the message */ + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 0); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + /* We should now have a total of 6 messages in test_vm_api_2345 INBOX */ + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 6); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test forwarding 2 messages from test_vm_api_2345 INBOX to test_vm_api_1234 INBOX"); + VM_API_FORWARD_MESSAGE("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "INBOX", 2, multi_msg_nums, 0); + + /* Make sure we didn't delete the messages */ + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 6); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + /* We should now have a total of 2 messages in test_vm_api_1234 INBOX */ + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + ast_test_status_update(test, "Test forwarding 2 messages from test_vm_api_2345 INBOX to test_vm_api_1234 Family, deleting original\n"); + VM_API_FORWARD_MESSAGE("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "Family", 2, multi_msg_nums, 1); + /* Make sure we deleted the messages */ + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 4); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + /* We should now have a total of 2 messages in test_vm_api_1234 Family */ + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_off_nominal_forward) +{ + int single_msg_nums[] = { 0, }; + int multi_msg_nums[] = { 0, 1, 2, 3}; + + int empty_msg_nums[] = { }; + + switch (cmd) { + case TEST_INIT: + info->name = "off_nominal_forward"; + info->category = "/main/voicemail_api/"; + info->summary = "Off nominal message forwarding tests"; + info->description = + "Test off nominal forwarding of messages. This includes:\n" + " * Invalid/NULL from mailbox\n" + " * Invalid from context\n" + " * Invalid/NULL from folder\n" + " * Invalid/NULL to mailbox\n" + " * Invalid to context\n" + " * Invalid/NULL to folder\n" + " * Invalid message numbers\n" + " * Invalid number of messages\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + ast_test_status_update(test, "Test forwarding from an invalid mailbox\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding from a NULL mailbox\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL(NULL, "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding from an invalid context\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "defunct", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding from an invalid folder\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "POTTEDMEAT", "test_vm_api_2345", "default", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding from a NULL folder\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", NULL, "test_vm_api_2345", "default", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding to an invalid mailbox\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_3456", "default", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding to a NULL mailbox\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", NULL, "default", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding to an invalid context\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "defunct", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding to an invalid folder\n"); + + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "POTTEDMEAT", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding to a NULL folder\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", NULL, 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding when no messages are select\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 0, empty_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding a message that doesn't exist\n"); + single_msg_nums[0] = 6; + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, single_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding multiple messages, where some messages don't exist\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "INBOX", 4, multi_msg_nums, 0); + + ast_test_status_update(test, "Test forwarding a message with an invalid size specifier\n"); + VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", -30, single_msg_nums, 0); + + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_nominal_msg_playback) +{ + struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL; + struct ast_channel *test_channel; + + switch (cmd) { + case TEST_INIT: + info->name = "nominal_msg_playback"; + info->category = "/main/voicemail_api/"; + info->summary = "Nominal message playback"; + info->description = + "Tests playing back a message on a provided" + " channel or callback function\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + if (!(test_channel = test_vm_api_create_mock_channel())) { + ast_log(AST_LOG_ERROR, "Failed to create mock channel for testing\n"); + VM_API_TEST_CLEANUP; + return AST_TEST_FAIL; + } + + ast_test_status_update(test, "Playing back message from test_vm_api_1234 to mock channel\n"); + VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_1234", "default", "INBOX", "0", NULL); + + ast_test_status_update(test, "Playing back message from test_vm_api_2345 to callback function\n"); + VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_2345", "default", "INBOX", "0", &message_playback_callback_fn); + VM_API_INT_VERIFY(global_entered_playback_callback, 1); + global_entered_playback_callback = 0; + + ast_test_status_update(test, "Playing back message from test_vm_api_2345 to callback function with default context\n"); + VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_2345", NULL, "INBOX", "0", &message_playback_callback_fn); + VM_API_INT_VERIFY(global_entered_playback_callback, 1); + global_entered_playback_callback = 0; + + VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); + VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2); + test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); + + if (test_channel) { + ast_hangup(test_channel); + } + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(voicemail_api_off_nominal_msg_playback) +{ + struct ast_channel *test_channel; + + switch (cmd) { + case TEST_INIT: + info->name = "off_nominal_msg_playback"; + info->category = "/main/voicemail_api/"; + info->summary = "Off nominal message playback"; + info->description = + "Tests off nominal conditions in playing back a " + "message. This includes:\n" + " * Invalid/NULL mailbox\n" + " * Invalid context\n" + " * Invalid/NULL folder\n" + " * Invalid message identifiers\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + VM_API_TEST_SETUP; + + if (!(test_channel = test_vm_api_create_mock_channel())) { + ast_log(AST_LOG_ERROR, "Failed to create mock channel for testing\n"); + VM_API_TEST_CLEANUP; + return AST_TEST_FAIL; + } + + ast_test_status_update(test, "Playing back message from invalid mailbox\n"); + VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_3456", "default", "INBOX", "0", NULL); + + ast_test_status_update(test, "Playing back message from NULL mailbox\n"); + VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, NULL, "default", "INBOX", "0", NULL); + + ast_test_status_update(test, "Playing back message from invalid context\n"); + VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "defunct", "INBOX", "0", NULL); + + ast_test_status_update(test, "Playing back message from invalid folder\n"); + VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "BACON", "0", NULL); + + ast_test_status_update(test, "Playing back message from NULL folder\n"); + VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", NULL, "0", NULL); + + ast_test_status_update(test, "Playing back message with invalid message specifier\n"); + VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "INBOX", "6", NULL); + + ast_test_status_update(test, "Playing back message with NULL message specifier\n"); + VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "INBOX", NULL, NULL); + if (test_channel) { + ast_hangup(test_channel); + } + VM_API_TEST_CLEANUP; + + return AST_TEST_PASS; +} + +static int unload_module(void) +{ + /* Snapshot tests */ + AST_TEST_UNREGISTER(voicemail_api_nominal_snapshot); + AST_TEST_UNREGISTER(voicemail_api_off_nominal_snapshot); + + /* Move Tests */ + AST_TEST_UNREGISTER(voicemail_api_nominal_move); + AST_TEST_UNREGISTER(voicemail_api_off_nominal_move); + + /* Remove Tests */ + AST_TEST_UNREGISTER(voicemail_api_nominal_remove); + AST_TEST_UNREGISTER(voicemail_api_off_nominal_remove); + + /* Forward Tests */ + AST_TEST_UNREGISTER(voicemail_api_nominal_forward); + AST_TEST_UNREGISTER(voicemail_api_off_nominal_forward); + + /* Message Playback Tests */ + AST_TEST_UNREGISTER(voicemail_api_nominal_msg_playback); + AST_TEST_UNREGISTER(voicemail_api_off_nominal_msg_playback); + return 0; +} + +static int load_module(void) +{ + /* Snapshot tests */ + AST_TEST_REGISTER(voicemail_api_nominal_snapshot); + AST_TEST_REGISTER(voicemail_api_off_nominal_snapshot); + + /* Move Tests */ + AST_TEST_REGISTER(voicemail_api_nominal_move); + AST_TEST_REGISTER(voicemail_api_off_nominal_move); + + /* Remove Tests */ + AST_TEST_REGISTER(voicemail_api_nominal_remove); + AST_TEST_REGISTER(voicemail_api_off_nominal_remove); + + /* Forward Tests */ + AST_TEST_REGISTER(voicemail_api_nominal_forward); + AST_TEST_REGISTER(voicemail_api_off_nominal_forward); + + /* Message Playback Tests */ + AST_TEST_REGISTER(voicemail_api_nominal_msg_playback); + AST_TEST_REGISTER(voicemail_api_off_nominal_msg_playback); + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Core Voicemail API Tests");