[svn-commits] mmichelson: branch 10-digiumphones r361748 - in /branches/10-digiumphones: ap...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Mon Apr 9 16:32:21 CDT 2012


Author: mmichelson
Date: Mon Apr  9 16:32:17 2012
New Revision: 361748

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=361748
Log:
Fix bugs in voicemail APIs and add unit tests.


Added:
    branches/10-digiumphones/tests/test_voicemail_api.c   (with props)
Modified:
    branches/10-digiumphones/apps/app_voicemail.c
    branches/10-digiumphones/apps/app_voicemail.exports.in
    branches/10-digiumphones/include/asterisk/app_voicemail.h
    branches/10-digiumphones/main/manager.c

Modified: branches/10-digiumphones/apps/app_voicemail.c
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/apps/app_voicemail.c?view=diff&rev=361748&r1=361747&r2=361748
==============================================================================
--- branches/10-digiumphones/apps/app_voicemail.c (original)
+++ branches/10-digiumphones/apps/app_voicemail.c Mon Apr  9 16:32:17 2012
@@ -2511,7 +2511,7 @@
  *
  * \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 @@
 	}
 	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;
 	}
@@ -5336,7 +5336,7 @@
  *
  * \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, 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);
@@ -5348,6 +5348,8 @@
 
 	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";
 	}
@@ -6391,7 +6393,7 @@
 							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);
 						}
 					}
@@ -7712,7 +7714,7 @@
 					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);
@@ -14121,6 +14123,39 @@
 	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
  *
@@ -14255,6 +14290,11 @@
 	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))) {
@@ -14386,6 +14426,51 @@
 	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;
@@ -14413,17 +14498,36 @@
 	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;
 	}
 
@@ -14450,20 +14554,30 @@
 
 	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;
@@ -14472,7 +14586,7 @@
 			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;
@@ -14515,11 +14629,29 @@
 {
 	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));
@@ -14545,13 +14677,13 @@
 
 	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;
 		}
@@ -14590,14 +14722,30 @@
 {
 	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;
@@ -14621,13 +14769,17 @@
 
 	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;
 	}
 
@@ -14676,6 +14828,26 @@
 	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));
@@ -14685,69 +14857,60 @@
 	}
 
 	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;
 	}
-
-	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;
-
-		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;
+	open = 1;
+
+	vms.curmsg = atoi(msg_num);
+	if (vms.curmsg > vms.lastmsg || vms.curmsg < 0) {
+		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);
 
 #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);
-		}
+	/*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);
-	}
+	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) {

Modified: branches/10-digiumphones/apps/app_voicemail.exports.in
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/apps/app_voicemail.exports.in?view=diff&rev=361748&r1=361747&r2=361748
==============================================================================
--- branches/10-digiumphones/apps/app_voicemail.exports.in (original)
+++ branches/10-digiumphones/apps/app_voicemail.exports.in Mon Apr  9 16:32:17 2012
@@ -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:
 		*;
 };

Modified: branches/10-digiumphones/include/asterisk/app_voicemail.h
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/include/asterisk/app_voicemail.h?view=diff&rev=361748&r1=361747&r2=361748
==============================================================================
--- branches/10-digiumphones/include/asterisk/app_voicemail.h (original)
+++ branches/10-digiumphones/include/asterisk/app_voicemail.h Mon Apr  9 16:32:17 2012
@@ -1,7 +1,7 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 2011-2012, Digium, Inc.
+ * Copyright (C) 2011, Digium, Inc.
  *
  * David Vossel <dvossel at digium.com>
  *
@@ -190,5 +190,25 @@
  * \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

Modified: branches/10-digiumphones/main/manager.c
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/main/manager.c?view=diff&rev=361748&r1=361747&r2=361748
==============================================================================
--- branches/10-digiumphones/main/manager.c (original)
+++ branches/10-digiumphones/main/manager.c Mon Apr  9 16:32:17 2012
@@ -2404,6 +2404,7 @@
 	struct ast_sockaddr addr;
 
 	if (ast_strlen_zero(username)) {	/* missing username */
+		ast_log(LOG_NOTICE, "Missing username?\n");
 		return -1;
 	}
 
@@ -2444,8 +2445,10 @@
 		}
 	} else if (user->secret) {
 		if (!strcmp(password, user->secret)) {
+			ast_log(LOG_NOTICE, "Seems to have passed...\n");
 			error = 0;
 		} else {
+			ast_log(LOG_NOTICE, "They didn't equal? %s != %s ?\n", password, user->secret);
 			report_inval_password(s, username);
 		}
 	}

Added: branches/10-digiumphones/tests/test_voicemail_api.c
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/tests/test_voicemail_api.c?view=auto&rev=361748
==============================================================================
--- branches/10-digiumphones/tests/test_voicemail_api.c (added)
+++ branches/10-digiumphones/tests/test_voicemail_api.c Mon Apr  9 16:32:17 2012
@@ -1,0 +1,1415 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Matt Jordan
+ *
+ * Matt Jordan <mjordan at digium.com>
+ *
+ * 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 <mjordan at digium.com> \endverbatim
+ *
+ * Tests for the publicly exposed Voicemail API
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/stat.h>
+
+#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 */

[... 942 lines stripped ...]



More information about the svn-commits mailing list