summaryrefslogtreecommitdiff
path: root/tests/test_cdr.c
diff options
context:
space:
mode:
authorMatthew Jordan <mjordan@digium.com>2013-06-17 03:00:38 +0000
committerMatthew Jordan <mjordan@digium.com>2013-06-17 03:00:38 +0000
commit6258bbe7bd1885ac5dec095ed0c4490c83a99f44 (patch)
treeff2794f730ca55903a09b9fe7f73f45169a71386 /tests/test_cdr.c
parent67e35c7b4748c3cef954820a2b182e2a5edf8d98 (diff)
Update Asterisk's CDRs for the new bridging framework
This patch is the initial push to update Asterisk's CDR engine for the new bridging framework. This patch guts the existing CDR engine and builds the new on top of messages coming across Stasis. As changes in channel state and bridge state are detected, CDRs are built and dispatched accordingly. This fundamentally changes CDRs in a few ways. (1) CDRs are now *very* reflective of the actual state of channels and bridges. This means CDRs track well with what an actual channel is doing - which is useful in transfer scenarios (which were previously difficult to pin down). It does, however, mean that CDRs cannot be 'fooled'. Previous behavior in Asterisk allowed for CDR applications, channels, and other properties to be spoofed in parts of the code - this no longer works. (2) CDRs have defined behavior in multi-party scenarios. This behavior will not be what everyone wants, but it is a defined behavior and as such, it is predictable. (3) The CDR manipulation functions and applications have been overhauled. Major changes have been made to ResetCDR and ForkCDR in particular. Many of the options for these two applications no longer made any sense with the new framework and the (slightly) more immutable nature of CDRs. There are a plethora of other changes. For a full description of CDR behavior, see the CDR specification on the Asterisk wiki. (closes issue ASTERISK-21196) Review: https://reviewboard.asterisk.org/r/2486/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@391947 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'tests/test_cdr.c')
-rw-r--r--tests/test_cdr.c2413
1 files changed, 2413 insertions, 0 deletions
diff --git a/tests/test_cdr.c b/tests/test_cdr.c
new file mode 100644
index 000000000..c9621a450
--- /dev/null
+++ b/tests/test_cdr.c
@@ -0,0 +1,2413 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Matt Jordan <mjordan@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 CDR unit tests
+ *
+ * \author Matt Jordan <mjordan@digium.com>
+ *
+ */
+
+/*** MODULEINFO
+ <depend>TEST_FRAMEWORK</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <math.h>
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/cdr.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/chanvars.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/time.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
+
+#define EPSILON 0.001
+
+#define TEST_CATEGORY "/main/cdr/"
+
+#define MOCK_CDR_BACKEND "mock_cdr_backend"
+
+#define CHANNEL_TECH_NAME "CDRTestChannel"
+
+/*! \brief A placeholder for Asterisk's 'real' CDR configuration */
+static struct ast_cdr_config *saved_config;
+
+/*! \brief A configuration suitable for 'normal' CDRs */
+static struct ast_cdr_config debug_cdr_config = {
+ .settings.flags = CDR_ENABLED | CDR_DEBUG,
+};
+
+/*! \brief A configuration suitable for CDRs with unanswered records */
+static struct ast_cdr_config unanswered_cdr_config = {
+ .settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG,
+};
+
+/*! \brief A configuration suitable for CDRs with congestion enabled */
+static struct ast_cdr_config congestion_cdr_config = {
+ .settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG | CDR_CONGESTION,
+};
+
+/*! \brief Macro to swap a configuration out from the CDR engine. This should be
+ * used at the beginning of each test to set the needed configuration for that
+ * test.
+ */
+#define SWAP_CONFIG(ao2_config, template) do { \
+ *(ao2_config) = (template); \
+ ast_cdr_set_config((ao2_config)); \
+ } while (0)
+
+/*! \brief A linked list of received CDR entries from the engine */
+static AST_LIST_HEAD(, test_cdr_entry) actual_cdr_entries = AST_LIST_HEAD_INIT_VALUE;
+
+/*! \brief The Mock CDR backend condition wait */
+static ast_cond_t mock_cdr_cond;
+
+/*! \brief A channel technology used for the unit tests */
+static struct ast_channel_tech test_cdr_chan_tech = {
+ .type = CHANNEL_TECH_NAME,
+ .description = "Mock channel technology for CDR tests",
+};
+
+struct test_cdr_entry {
+ struct ast_cdr *cdr;
+ AST_LIST_ENTRY(test_cdr_entry) list;
+};
+
+/*! \brief The number of CDRs the mock backend has received */
+static int global_mock_cdr_count;
+
+/*! \internal
+ * \brief Callback function for the mock CDR backend
+ *
+ * This function 'processes' a dispatched CDR record by adding it to the
+ * \ref actual_cdr_entries list. When a test completes, it can verify the
+ * expected records against this list of actual CDRs created by the engine.
+ *
+ * \param cdr The public CDR object created by the engine
+ *
+ * \retval -1 on error
+ * \retval 0 on success
+ */
+static int mock_cdr_backend_cb(struct ast_cdr *cdr)
+{
+ struct ast_cdr *cdr_copy, *cdr_prev = NULL;
+ struct ast_cdr *mock_cdr = NULL;
+ struct test_cdr_entry *cdr_wrapper;
+
+ cdr_wrapper = ast_calloc(1, sizeof(*cdr_wrapper));
+ if (!cdr_wrapper) {
+ return -1;
+ }
+
+ for (; cdr; cdr = cdr->next) {
+ struct ast_var_t *var_entry, *var_copy;
+
+ cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
+ if (!cdr_copy) {
+ return -1;
+ }
+ *cdr_copy = *cdr;
+ cdr_copy->varshead.first = NULL;
+ cdr_copy->varshead.last = NULL;
+ cdr_copy->next = NULL;
+
+ AST_LIST_TRAVERSE(&cdr->varshead, var_entry, entries) {
+ var_copy = ast_var_assign(var_entry->name, var_entry->value);
+ if (!var_copy) {
+ return -1;
+ }
+ AST_LIST_INSERT_TAIL(&cdr_copy->varshead, var_copy, entries);
+ }
+
+ if (!mock_cdr) {
+ mock_cdr = cdr_copy;
+ }
+ if (cdr_prev) {
+ cdr_prev->next = cdr_copy;
+ }
+ cdr_prev = cdr_copy;
+ }
+ cdr_wrapper->cdr = mock_cdr;
+
+ AST_LIST_LOCK(&actual_cdr_entries);
+ AST_LIST_INSERT_TAIL(&actual_cdr_entries, cdr_wrapper, list);
+ global_mock_cdr_count++;
+ ast_cond_signal(&mock_cdr_cond);
+ AST_LIST_UNLOCK(&actual_cdr_entries);
+
+ return 0;
+}
+
+/*! \internal
+ * \brief Remove all entries from \ref actual_cdr_entries
+ */
+static void clear_mock_cdr_backend(void)
+{
+ struct test_cdr_entry *cdr_wrapper;
+
+ AST_LIST_LOCK(&actual_cdr_entries);
+ while ((cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list))) {
+ ast_cdr_free(cdr_wrapper->cdr);
+ ast_free(cdr_wrapper);
+ }
+ global_mock_cdr_count = 0;
+ AST_LIST_UNLOCK(&actual_cdr_entries);
+}
+
+/*! \brief Verify a string field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_STRING_FIELD(field, actual, expected) do { \
+ if (strcmp((actual)->field, (expected)->field)) { \
+ ast_test_status_update(test, "Field %s failed: actual %s, expected %s\n", #field, (actual)->field, (expected)->field); \
+ ast_test_set_result(test, AST_TEST_FAIL); \
+ res = AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \brief Verify a numeric field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_NUMERIC_FIELD(field, actual, expected) do { \
+ if ((actual)->field != (expected)->field) { \
+ ast_test_status_update(test, "Field %s failed: actual %ld, expected %ld\n", #field, (long)(actual)->field, (long)(expected)->field); \
+ ast_test_set_result(test, AST_TEST_FAIL); \
+ res = AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \brief Verify a time field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_TIME_VALUE(field, actual) do { \
+ if (ast_tvzero((actual)->field)) { \
+ ast_test_status_update(test, "Field %s failed: should not be 0\n", #field); \
+ ast_test_set_result(test, AST_TEST_FAIL); \
+ res = AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \brief Alice's Caller ID */
+#define ALICE_CALLERID { .id.name.str = "Alice", .id.name.valid = 1, .id.number.str = "100", .id.number.valid = 1, }
+
+/*! \brief Bob's Caller ID */
+#define BOB_CALLERID { .id.name.str = "Bob", .id.name.valid = 1, .id.number.str = "200", .id.number.valid = 1, }
+
+/*! \brief Charlie's Caller ID */
+#define CHARLIE_CALLERID { .id.name.str = "Charlie", .id.name.valid = 1, .id.number.str = "300", .id.number.valid = 1, }
+
+/*! \brief David's Caller ID */
+#define DAVID_CALLERID { .id.name.str = "David", .id.name.valid = 1, .id.number.str = "400", .id.number.valid = 1, }
+
+/*! \brief Copy the linkedid and uniqueid from a channel to an expected CDR */
+#define COPY_IDS(channel_var, expected_record) do { \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Alice, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_ALICE_CHANNEL(channel_var, caller_id, expected_record) do { \
+ (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice"); \
+ ast_channel_set_caller((channel_var), (caller_id), NULL); \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Bob, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_BOB_CHANNEL(channel_var, caller_id, expected_record) do { \
+ (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob"); \
+ ast_channel_set_caller((channel_var), (caller_id), NULL); \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Charlie, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_CHARLIE_CHANNEL(channel_var, caller_id, expected_record) do { \
+ (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "300", "Charlie", "300", "300", "default", NULL, 0, CHANNEL_TECH_NAME "/Charlie"); \
+ ast_channel_set_caller((channel_var), (caller_id), NULL); \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Charlie, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_DAVID_CHANNEL(channel_var, caller_id, expected_record) do { \
+ (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "400", "David", "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David"); \
+ ast_channel_set_caller((channel_var), (caller_id), NULL); \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Emulate a channel entering into an application */
+#define EMULATE_APP_DATA(channel, priority, application, data) do { \
+ if ((priority) > 0) { \
+ ast_channel_priority_set((channel), (priority)); \
+ } \
+ ast_channel_appl_set((channel), (application)); \
+ ast_channel_data_set((channel), (data)); \
+ ast_channel_publish_snapshot((channel)); \
+ } while (0)
+
+/*! \brief Hang up a test channel safely */
+#define HANGUP_CHANNEL(channel, cause) do { \
+ ast_channel_hangupcause_set((channel), (cause)); \
+ if (!ast_hangup((channel))) { \
+ channel = NULL; \
+ } } while (0)
+
+static enum ast_test_result_state verify_mock_cdr_record(struct ast_test *test, struct ast_cdr *expected, int record)
+{
+ struct ast_cdr *actual = NULL;
+ struct test_cdr_entry *cdr_wrapper;
+ int count = 0;
+ struct timeval wait_now = ast_tvnow();
+ struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 5, .tv_nsec = wait_now.tv_usec * 1000 };
+ enum ast_test_result_state res = AST_TEST_PASS;
+
+ while (count < record) {
+ AST_LIST_LOCK(&actual_cdr_entries);
+ if (global_mock_cdr_count < record) {
+ ast_cond_timedwait(&mock_cdr_cond, &actual_cdr_entries.lock, &wait_time);
+ }
+ cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list);
+ AST_LIST_UNLOCK(&actual_cdr_entries);
+
+ if (!cdr_wrapper) {
+ ast_test_status_update(test, "Unable to find actual CDR record at %d\n", count);
+ return AST_TEST_FAIL;
+ }
+ actual = cdr_wrapper->cdr;
+
+ if (!expected && actual) {
+ ast_test_status_update(test, "CDRs recorded where no record expected\n");
+ return AST_TEST_FAIL;
+ }
+
+ VERIFY_STRING_FIELD(accountcode, actual, expected);
+ VERIFY_NUMERIC_FIELD(amaflags, actual, expected);
+ VERIFY_STRING_FIELD(channel, actual, expected);
+ VERIFY_STRING_FIELD(clid, actual, expected);
+ VERIFY_STRING_FIELD(dcontext, actual, expected);
+ VERIFY_NUMERIC_FIELD(disposition, actual, expected);
+ VERIFY_STRING_FIELD(dst, actual, expected);
+ VERIFY_STRING_FIELD(dstchannel, actual, expected);
+ VERIFY_STRING_FIELD(lastapp, actual, expected);
+ VERIFY_STRING_FIELD(lastdata, actual, expected);
+ VERIFY_STRING_FIELD(linkedid, actual, expected);
+ VERIFY_STRING_FIELD(peeraccount, actual, expected);
+ VERIFY_STRING_FIELD(src, actual, expected);
+ VERIFY_STRING_FIELD(uniqueid, actual, expected);
+ VERIFY_STRING_FIELD(userfield, actual, expected);
+ VERIFY_TIME_VALUE(start, actual);
+ VERIFY_TIME_VALUE(end, actual);
+ /* Note: there's no way we can really calculate a duration or
+ * billsec - the unit tests are too short. However, if billsec is
+ * non-zero in the expected, then make sure we have an answer time
+ */
+ if (expected->billsec) {
+ VERIFY_TIME_VALUE(answer, actual);
+ }
+ ast_test_debug(test, "Finished expected record %s, %s\n",
+ expected->channel, S_OR(expected->dstchannel, "<none>"));
+ expected = expected->next;
+ ++count;
+ }
+ return res;
+}
+
+static void safe_channel_release(struct ast_channel *chan)
+{
+ if (!chan) {
+ return;
+ }
+ ast_channel_release(chan);
+}
+
+AST_TEST_DEFINE(test_cdr_channel_creation)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test that a CDR is created when a channel is created";
+ info->description =
+ "Test that a CDR is created when a channel is created";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, (&caller), &expected);
+
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_unanswered_inbound_call)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .lastdata = "1",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test inbound unanswered calls";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "inbound to Asterisk, executes some dialplan, but\n"
+ "is never answered.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ EMULATE_APP_DATA(chan, 1, "Wait", "1");
+
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_unanswered_outbound_call)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = {
+ .id.name.str = "",
+ .id.name.valid = 1,
+ .id.number.str = "",
+ .id.number.valid = 1, };
+ struct ast_cdr expected = {
+ .clid = "\"\" <>",
+ .dst = "s",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "AppDial",
+ .lastdata = "(Outgoing Line)",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test outbound unanswered calls";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "outbound to Asterisk but is never answered.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ ast_channel_exten_set(chan, "s");
+ ast_channel_context_set(chan, "default");
+ ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED);
+ EMULATE_APP_DATA(chan, 0, "AppDial", "(Outgoing Line)");
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_party)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = "",
+ .lastapp = "VoiceMailMain",
+ .lastdata = "1",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, but only involves a single channel\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ EMULATE_APP_DATA(chan, 1, "Answer", "");
+ ast_setstate(chan, AST_STATE_UP);
+ EMULATE_APP_DATA(chan, 2, "VoiceMailMain", "1");
+
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_bridge)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ EMULATE_APP_DATA(chan, 1, "Answer", "");
+ ast_setstate(chan, AST_STATE_UP);
+ EMULATE_APP_DATA(chan, 2, "Bridge", "");
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan);
+
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_bridge_continue)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge_one, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_bridge *, bridge_two, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ struct ast_cdr expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .next = &expected_two,
+ };
+
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected_one);
+ COPY_IDS(chan, &expected_two);
+
+ EMULATE_APP_DATA(chan, 1, "Answer", "");
+ ast_setstate(chan, AST_STATE_UP);
+ EMULATE_APP_DATA(chan, 2, "Bridge", "");
+
+ bridge_one = ast_bridge_basic_new();
+ ast_test_validate(test, bridge_one != NULL);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge_one, chan, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan);
+
+ EMULATE_APP_DATA(chan, 3, "Wait", "");
+
+ /* And then it hangs up */
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected_one, 2);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_twoparty_bridge_a)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller_alice = ALICE_CALLERID;
+ struct ast_party_caller caller_bob = BOB_CALLERID;
+ struct ast_cdr bob_expected = {
+ .clid = "\"Bob\" <200>",
+ .src = "200",
+ .dst = "200",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ };
+ struct ast_cdr alice_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &bob_expected,
+ };
+
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it. In this scenario, the\n"
+ "Party A should answer the bridge first.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected);
+
+ CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+ ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+
+ EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+ ast_setstate(chan_alice, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+
+ ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+ ast_setstate(chan_bob, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+
+ ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan_alice);
+ ast_bridge_depart(chan_bob);
+
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_twoparty_bridge_b)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller_alice = ALICE_CALLERID;
+ struct ast_party_caller caller_bob = BOB_CALLERID;
+ struct ast_cdr bob_expected = {
+ .clid = "\"Bob\" <200>",
+ .src = "200",
+ .dst = "200",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ };
+ struct ast_cdr alice_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &bob_expected,
+ };
+
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it. In this scenario, the\n"
+ "Party B should answer the bridge first.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected);
+
+ CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+ ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+
+ EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+ ast_setstate(chan_alice, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+
+ EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+ ast_setstate(chan_bob, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan_alice);
+ ast_bridge_depart(chan_bob);
+
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_multiparty_bridge)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller_alice = ALICE_CALLERID;
+ struct ast_party_caller caller_bob = BOB_CALLERID;
+ struct ast_party_caller caller_charlie = CHARLIE_CALLERID;
+ struct ast_cdr charlie_expected = {
+ .clid = "\"Charlie\" <300>",
+ .src = "300",
+ .dst = "300",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "300",
+ };
+ struct ast_cdr bob_expected = {
+ .clid = "\"Bob\" <200>",
+ .src = "200",
+ .dst = "200",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .dstchannel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ .peeraccount = "300",
+ .next = &charlie_expected,
+ };
+ struct ast_cdr alice_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "300",
+ .next = &bob_expected,
+ };
+ struct ast_cdr alice_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &alice_expected_two,
+ };
+
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a multi-party bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it. A total of three\n"
+ "parties perform this action.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected_one);
+ COPY_IDS(chan_alice, &alice_expected_two);
+ CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+ ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+ CREATE_CHARLIE_CHANNEL(chan_charlie, &caller_charlie, &charlie_expected);
+ ast_copy_string(charlie_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected.linkedid));
+
+ EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+ ast_setstate(chan_alice, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+
+ EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+ ast_setstate(chan_bob, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ EMULATE_APP_DATA(chan_charlie, 1, "Answer", "");
+ ast_setstate(chan_charlie, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_charlie, 2, "Bridge", "");
+ ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan_alice);
+ ast_bridge_depart(chan_bob);
+ ast_bridge_depart(chan_charlie);
+
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected_one, 4);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_unanswered)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial that isn't answered";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation that isn't answered\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", "CDRTestChannel/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "NOANSWER");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ANSWER);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+
+AST_TEST_DEFINE(test_cdr_dial_busy)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_BUSY,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial that results in a busy";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation to an endpoint that's busy\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "BUSY");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_BUSY);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_BUSY);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_congestion)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_CONGESTION,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial that results in congestion";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation to an endpoint that's congested\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, congestion_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CONGESTION");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_CONGESTION);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_CONGESTION);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_unavailable)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_FAILED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial that results in unavailable";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation to an endpoint that's unavailable\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CHANUNAVAIL");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ROUTE_DESTINATION);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ROUTE_DESTINATION);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_caller_cancel)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial where the caller cancels";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation to an endpoint but then decides\n"
+ "to hang up, cancelling the dial\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CANCEL");
+
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_parallel_failed)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr bob_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ struct ast_cdr charlie_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_BUSY,
+ .accountcode = "100",
+ .peeraccount = "300",
+ };
+ struct ast_cdr david_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/David",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_CONGESTION,
+ .accountcode = "100",
+ .peeraccount = "400",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_cdr *expected = &bob_expected;
+ bob_expected.next = &charlie_expected;
+ charlie_expected.next = &david_expected;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test a parallel dial where all channels fail to answer";
+ info->description =
+ "This tests dialing three parties: Bob, Charlie, David. Charlie\n"
+ "returns BUSY; David returns CONGESTION; Bob fails to answer and\n"
+ "Alice hangs up. Three records are created for Alice as a result.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, congestion_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &bob_expected);
+ COPY_IDS(chan_caller, &charlie_expected);
+ COPY_IDS(chan_caller, &david_expected);
+
+ /* Channel enters Dial app */
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David");
+
+ /* Outbound channels are created */
+ chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)");
+
+ chan_charlie = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "300", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Charlie");
+ ast_set_flag(ast_channel_flags(chan_charlie), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_charlie, 0, "AppDial", "(Outgoing Line)");
+
+ chan_david = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "400", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/David");
+ ast_set_flag(ast_channel_flags(chan_david), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_david, 0, "AppDial", "(Outgoing Line)");
+
+ /* Dial starts */
+ ast_channel_publish_dial(chan_caller, chan_bob, "Bob", NULL);
+ ast_channel_publish_dial(chan_caller, chan_charlie, "Charlie", NULL);
+ ast_channel_publish_dial(chan_caller, chan_david, "David", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+
+ /* Charlie is busy */
+ ast_channel_publish_dial(chan_caller, chan_charlie, NULL, "BUSY");
+ HANGUP_CHANNEL(chan_charlie, AST_CAUSE_BUSY);
+
+ /* David is congested */
+ ast_channel_publish_dial(chan_caller, chan_david, NULL, "CONGESTION");
+ HANGUP_CHANNEL(chan_david, AST_CAUSE_CONGESTION);
+
+ /* Bob is canceled */
+ ast_channel_publish_dial(chan_caller, chan_bob, NULL, "CANCEL");
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+ /* Alice hangs up */
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, expected, 3);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_no_bridge)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr bob_expected_one = {
+ .clid = "\"\" <>",
+ .src = "",
+ .dst = "s",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Wait",
+ .lastdata = "1",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ };
+ struct ast_cdr alice_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .lastdata = "1",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .next = &bob_expected_one,
+ };
+ struct ast_cdr alice_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &alice_expected_two,
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test dialing, answering, and not going into a bridge.";
+ info->description =
+ "This is a weird one, but theoretically possible. You can perform\n"
+ "a dial, then bounce both channels to different priorities and\n"
+ "never have them enter a bridge together. Ew. This makes sure that\n"
+ "when we answer, we get a CDR, it gets ended at that point, and\n"
+ "that it gets finalized appropriately. We should get three CDRs in\n"
+ "the end - one for the dial, and one for each CDR as they continued\n"
+ "on.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &alice_expected_one);
+ COPY_IDS(chan_caller, &alice_expected_two);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ COPY_IDS(chan_callee, &bob_expected_one);
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+ ast_channel_state_set(chan_caller, AST_STATE_UP);
+ ast_clear_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+ EMULATE_APP_DATA(chan_caller, 2, "Wait", "1");
+ EMULATE_APP_DATA(chan_callee, 1, "Wait", "1");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected_one, 3);
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_twoparty_bridge_a)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test dialing, answering, and going into a 2-party bridge";
+ info->description =
+ "The most 'basic' of scenarios\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+ ast_channel_state_set(chan_caller, AST_STATE_UP);
+ ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0);
+ ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan_caller);
+ ast_bridge_depart(chan_callee);
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_twoparty_bridge_b)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test dialing, answering, and going into a 2-party bridge";
+ info->description =
+ "The most 'basic' of scenarios\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+ ast_channel_state_set(chan_caller, AST_STATE_UP);
+ ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_depart(chan_caller);
+ ast_bridge_depart(chan_callee);
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_multiparty)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_party_caller alice_caller = ALICE_CALLERID;
+ struct ast_party_caller charlie_caller = CHARLIE_CALLERID;
+ struct ast_cdr charlie_expected_two = {
+ .clid = "\"Charlie\" <300>",
+ .src = "300",
+ .dst = "300",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Charlie",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/David",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "300",
+ .peeraccount = "200",
+ };
+ struct ast_cdr charlie_expected_one = {
+ .clid = "\"Charlie\" <300>",
+ .src = "300",
+ .dst = "300",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Charlie",
+ .dstchannel = CHANNEL_TECH_NAME "/David",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/David",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "300",
+ .peeraccount = "400",
+ .next = &charlie_expected_two,
+ };
+ struct ast_cdr alice_expected_three = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/David",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "400",
+ .next = &charlie_expected_one,
+ };
+ struct ast_cdr alice_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "300",
+ .next = &alice_expected_three,
+ };
+ struct ast_cdr alice_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &alice_expected_two,
+ };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test dialing, answering, and going into a multi-party bridge";
+ info->description =
+ "A little tricky to get to do, but possible with some redirects.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_alice, &alice_caller, &alice_expected_one);
+ COPY_IDS(chan_alice, &alice_expected_two);
+ COPY_IDS(chan_alice, &alice_expected_three);
+
+ EMULATE_APP_DATA(chan_alice, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)");
+
+ CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller, &charlie_expected_one);
+ EMULATE_APP_DATA(chan_charlie, 1, "Dial", CHANNEL_TECH_NAME "/David");
+ ast_copy_string(charlie_expected_one.uniqueid, ast_channel_uniqueid(chan_charlie), sizeof(charlie_expected_one.uniqueid));
+ ast_copy_string(charlie_expected_one.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected_one.linkedid));
+ ast_copy_string(charlie_expected_two.uniqueid, ast_channel_uniqueid(chan_charlie), sizeof(charlie_expected_two.uniqueid));
+ ast_copy_string(charlie_expected_two.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected_two.linkedid));
+
+ chan_david = ast_channel_alloc(0, AST_STATE_DOWN, "400", "David", "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David");
+ ast_set_flag(ast_channel_flags(chan_david), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_david, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_alice, chan_bob, "Bob", NULL);
+ ast_channel_state_set(chan_alice, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_charlie, chan_david, "David", NULL);
+ ast_channel_state_set(chan_charlie, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_alice, chan_bob, NULL, "ANSWER");
+ ast_channel_publish_dial(chan_charlie, chan_david, NULL, "ANSWER");
+
+ ast_channel_state_set(chan_alice, AST_STATE_UP);
+ ast_channel_state_set(chan_bob, AST_STATE_UP);
+ ast_channel_state_set(chan_charlie, AST_STATE_UP);
+ ast_channel_state_set(chan_david, AST_STATE_UP);
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0));
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_david, NULL, NULL, 0));
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0));
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0));
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_depart(chan_alice));
+ ast_test_validate(test, 0 == ast_bridge_depart(chan_bob));
+ ast_test_validate(test, 0 == ast_bridge_depart(chan_charlie));
+ ast_test_validate(test, 0 == ast_bridge_depart(chan_david));
+
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected_one, 5);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_park)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller bob_caller = BOB_CALLERID;
+ struct ast_party_caller alice_caller = ALICE_CALLERID;
+ struct ast_cdr bob_expected = {
+ .clid = "\"Bob\" <200>",
+ .src = "200",
+ .dst = "200",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Park",
+ .lastdata = "701",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ };
+ struct ast_cdr alice_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Park",
+ .lastdata = "700",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .next = &bob_expected,
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering Park";
+ info->description =
+ "Test the properties of a CDR for calls that are\n"
+ "answered, enters Park, and leaves it.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan_alice, &alice_caller, &alice_expected);
+ CREATE_BOB_CHANNEL(chan_bob, &bob_caller, &bob_expected);
+
+ EMULATE_APP_DATA(chan_alice, 1, "Park", "700");
+ ast_setstate(chan_alice, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_bob, 1, "Park", "701");
+ ast_setstate(chan_bob, AST_STATE_UP);
+
+ bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_HOLDING,
+ AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
+ | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+ ast_test_validate(test, bridge != NULL);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_depart(chan_alice);
+ ast_bridge_depart(chan_bob);
+
+ /* And then it hangs up */
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+ return result;
+}
+
+
+AST_TEST_DEFINE(test_cdr_fields)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ char varbuffer[128];
+ int int_buffer;
+ double db_buffer;
+ struct timespec to_sleep = {2, 0};
+ struct ast_flags fork_options = { 0, };
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr original = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .lastdata = "10",
+ .billsec = 0,
+ .amaflags = AST_AMA_OMIT,
+ .disposition = AST_CDR_FAILED,
+ .accountcode = "XXX",
+ .userfield = "yackity",
+ };
+ struct ast_cdr fork_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .lastdata = "10",
+ .billsec = 0,
+ .amaflags = AST_AMA_OMIT,
+ .disposition = AST_CDR_FAILED,
+ .accountcode = "XXX",
+ .userfield = "yackity",
+ };
+ struct ast_cdr fork_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Answer",
+ .billsec = 0,
+ .amaflags = AST_AMA_OMIT,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "ZZZ",
+ .userfield = "schmackity",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_cdr *expected = &original;
+ original.next = &fork_expected_one;
+ fork_expected_one.next = &fork_expected_two;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test field access CDRs";
+ info->description =
+ "This tests setting/retrieving data on CDR records.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &original);
+ ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid));
+ ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid));
+ ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid));
+ ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid));
+
+ /* Channel enters Wait app */
+ ast_channel_appl_set(chan, "Wait");
+ ast_channel_data_set(chan, "10");
+ ast_channel_priority_set(chan, 1);
+ ast_channel_publish_snapshot(chan);
+
+ /* Set properties on the channel that propagate to the CDR */
+ ast_channel_amaflags_set(chan, AST_AMA_OMIT);
+ ast_channel_accountcode_set(chan, "XXX");
+
+ /* Wait one second so we get a duration. */
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_cdr_setuserfield(ast_channel_name(chan), "foobar");
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0);
+
+ /* Verify that we can't set read-only fields or other fields directly */
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "clid", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "src", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dst", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dcontext", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "channel", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dstchannel", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastapp", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastdata", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "start", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "answer", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "end", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "duration", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "billsec", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "disposition", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "amaflags", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "accountcode", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "uniqueid", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "linkedid", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "userfield", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "sequence", "junk") != 0);
+
+ /* Verify the values */
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "userfield", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "foobar") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "record_1") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "amaflags", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%d", &int_buffer);
+ ast_test_validate(test, int_buffer == AST_AMA_OMIT);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "accountcode", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "XXX") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "clid", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "\"Alice\" <100>") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "src", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "100") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dst", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "100") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dcontext", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "default") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "channel", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, CHANNEL_TECH_NAME "/Alice") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dstchannel", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastapp", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "Wait") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastdata", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "10") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) > 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) < EPSILON);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "end", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) < EPSILON);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "duration", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) > 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "billsec", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) < EPSILON);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "disposition", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%d", &int_buffer);
+ ast_test_validate(test, int_buffer == AST_CDR_NULL);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "uniqueid", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, ast_channel_uniqueid(chan)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "linkedid", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, ast_channel_linkedid(chan)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "sequence", varbuffer, sizeof(varbuffer)) == 0);
+
+ /* Fork the CDR, and check that we change the properties on both CDRs. */
+ ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Change some properties */
+ ast_cdr_setuserfield(ast_channel_name(chan), "yackity");
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1b") == 0);
+
+ /* Fork the CDR again, finalizing all current CDRs */
+ ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS | AST_CDR_FLAG_FINALIZE);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Channel enters Answer app */
+ ast_channel_appl_set(chan, "Answer");
+ ast_channel_data_set(chan, "");
+ ast_channel_priority_set(chan, 1);
+ ast_channel_publish_snapshot(chan);
+ ast_setstate(chan, AST_STATE_UP);
+
+ /* Set properties on the last record */
+ ast_channel_accountcode_set(chan, "ZZZ");
+ ast_cdr_setuserfield(ast_channel_name(chan), "schmackity");
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0);
+
+ /* Hang up and verify */
+ ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+ if (!ast_hangup(chan)) {
+ chan = NULL;
+ }
+ result = verify_mock_cdr_record(test, expected, 3);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_no_reset_cdr)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct ast_flags fork_options = { 0, };
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_FAILED,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test field access CDRs";
+ info->description =
+ "This tests setting/retrieving data on CDR records.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ /* Disable the CDR */
+ ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+
+ /* Fork the CDR. This should be enabled */
+ ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Disable and enable the forked CDR */
+ ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+ ast_test_validate(test, ast_cdr_clear_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+
+ /* Fork and finalize again. This CDR should be propagated */
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Disable all future CDRs */
+ ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL) == 0);
+
+ /* Fork a few more */
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+ if (!ast_hangup(chan)) {
+ chan = NULL;
+ }
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_fork_cdr)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ char varbuffer[128];
+ char fork_varbuffer[128];
+ char answer_time[128];
+ char fork_answer_time[128];
+ char start_time[128];
+ char fork_start_time[128];
+ struct ast_flags fork_options = { 0, };
+ struct timespec to_sleep = {1, 10000};
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr original = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ struct ast_cdr fork_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ struct ast_cdr fork_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+ struct ast_cdr *expected = &original;
+ original.next = &fork_expected_one;
+ fork_expected_one.next = &fork_expected_two;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test field access CDRs";
+ info->description =
+ "This tests setting/retrieving data on CDR records.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &original);
+ ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid));
+ ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid));
+ ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid));
+ ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid));
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ /* Test blowing away variables */
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "record_1") == 0);
+ ast_copy_string(varbuffer, "", sizeof(varbuffer));
+
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", fork_varbuffer, sizeof(fork_varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "record_1") != 0);
+
+ /* Test finalizing previous CDRs */
+ ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Test keep variables; setting a new answer time */
+ ast_setstate(chan, AST_STATE_UP);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "record_2") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", answer_time, sizeof(answer_time)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", start_time, sizeof(start_time)) == 0);
+
+ ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+ ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS);
+ ast_set_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", fork_answer_time, sizeof(fork_answer_time)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", fork_start_time, sizeof(fork_start_time)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", fork_varbuffer, sizeof(fork_varbuffer)) == 0);
+ ast_test_validate(test, strcmp(fork_varbuffer, varbuffer) == 0);
+ ast_test_validate(test, strcmp(fork_start_time, start_time) == 0);
+ ast_test_validate(test, strcmp(fork_answer_time, answer_time) != 0);
+
+ ast_clear_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER);
+ ast_set_flag(&fork_options, AST_CDR_FLAG_RESET);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", fork_answer_time, sizeof(fork_answer_time)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", fork_start_time, sizeof(fork_start_time)) == 0);
+ ast_test_validate(test, strcmp(fork_start_time, start_time) != 0);
+ ast_test_validate(test, strcmp(fork_answer_time, answer_time) != 0);
+
+ ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+ if (!ast_hangup(chan)) {
+ chan = NULL;
+ }
+ result = verify_mock_cdr_record(test, expected, 3);
+
+ return result;
+}
+
+/*!
+ * \internal \brief Callback function called before each test executes
+ */
+static int test_cdr_init_cb(struct ast_test_info *info, struct ast_test *test)
+{
+ /* Back up the real config */
+ saved_config = ast_cdr_get_config();
+ clear_mock_cdr_backend();
+ return 0;
+}
+
+/*!
+ * \internal \brief Callback function called after each test executes
+ */
+static int test_cdr_cleanup_cb(struct ast_test_info *info, struct ast_test *test)
+{
+ /* Restore the real config */
+ ast_cdr_set_config(saved_config);
+ ao2_cleanup(saved_config);
+ saved_config = NULL;
+ clear_mock_cdr_backend();
+
+ return 0;
+}
+
+
+static int unload_module(void)
+{
+ AST_TEST_UNREGISTER(test_cdr_channel_creation);
+ AST_TEST_UNREGISTER(test_cdr_unanswered_inbound_call);
+ AST_TEST_UNREGISTER(test_cdr_unanswered_outbound_call);
+ AST_TEST_UNREGISTER(test_cdr_single_party);
+ AST_TEST_UNREGISTER(test_cdr_single_bridge);
+ AST_TEST_UNREGISTER(test_cdr_single_bridge_continue);
+ AST_TEST_UNREGISTER(test_cdr_single_twoparty_bridge_a);
+ AST_TEST_UNREGISTER(test_cdr_single_twoparty_bridge_b);
+ AST_TEST_UNREGISTER(test_cdr_single_multiparty_bridge);
+
+ AST_TEST_UNREGISTER(test_cdr_dial_unanswered);
+ AST_TEST_UNREGISTER(test_cdr_dial_congestion);
+ AST_TEST_UNREGISTER(test_cdr_dial_busy);
+ AST_TEST_UNREGISTER(test_cdr_dial_unavailable);
+ AST_TEST_UNREGISTER(test_cdr_dial_caller_cancel);
+ AST_TEST_UNREGISTER(test_cdr_dial_parallel_failed);
+ AST_TEST_UNREGISTER(test_cdr_dial_answer_no_bridge);
+ AST_TEST_UNREGISTER(test_cdr_dial_answer_twoparty_bridge_a);
+ AST_TEST_UNREGISTER(test_cdr_dial_answer_twoparty_bridge_b);
+ AST_TEST_UNREGISTER(test_cdr_dial_answer_multiparty);
+
+ AST_TEST_UNREGISTER(test_cdr_park);
+
+ AST_TEST_UNREGISTER(test_cdr_fields);
+ AST_TEST_UNREGISTER(test_cdr_no_reset_cdr);
+ AST_TEST_UNREGISTER(test_cdr_fork_cdr);
+
+ ast_cdr_unregister(MOCK_CDR_BACKEND);
+ ast_channel_unregister(&test_cdr_chan_tech);
+ clear_mock_cdr_backend();
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ ast_cond_init(&mock_cdr_cond, NULL);
+
+ AST_TEST_REGISTER(test_cdr_channel_creation);
+ AST_TEST_REGISTER(test_cdr_unanswered_inbound_call);
+ AST_TEST_REGISTER(test_cdr_unanswered_outbound_call);
+
+ AST_TEST_REGISTER(test_cdr_single_party);
+ AST_TEST_REGISTER(test_cdr_single_bridge);
+ AST_TEST_REGISTER(test_cdr_single_bridge_continue);
+ AST_TEST_REGISTER(test_cdr_single_twoparty_bridge_a);
+ AST_TEST_REGISTER(test_cdr_single_twoparty_bridge_b);
+ AST_TEST_REGISTER(test_cdr_single_multiparty_bridge);
+
+ AST_TEST_REGISTER(test_cdr_dial_unanswered);
+ AST_TEST_REGISTER(test_cdr_dial_congestion);
+ AST_TEST_REGISTER(test_cdr_dial_busy);
+ AST_TEST_REGISTER(test_cdr_dial_unavailable);
+ AST_TEST_REGISTER(test_cdr_dial_caller_cancel);
+ AST_TEST_REGISTER(test_cdr_dial_parallel_failed);
+ AST_TEST_REGISTER(test_cdr_dial_answer_no_bridge);
+ AST_TEST_REGISTER(test_cdr_dial_answer_twoparty_bridge_a);
+ AST_TEST_REGISTER(test_cdr_dial_answer_twoparty_bridge_b);
+ AST_TEST_REGISTER(test_cdr_dial_answer_multiparty);
+
+ AST_TEST_REGISTER(test_cdr_park);
+
+ AST_TEST_REGISTER(test_cdr_fields);
+ AST_TEST_REGISTER(test_cdr_no_reset_cdr);
+ AST_TEST_REGISTER(test_cdr_fork_cdr);
+
+ ast_test_register_init(TEST_CATEGORY, test_cdr_init_cb);
+ ast_test_register_cleanup(TEST_CATEGORY, test_cdr_cleanup_cb);
+
+ ast_channel_register(&test_cdr_chan_tech);
+ ast_cdr_register(MOCK_CDR_BACKEND, "Mock CDR backend", mock_cdr_backend_cb);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "CDR unit tests");