Initial commit

Change-Id: I6a4444e3c193dae437cd7929f4c39aba7b749efa
diff --git a/tests/testappacct.c b/tests/testappacct.c
new file mode 100644
index 0000000..2ec94f3
--- /dev/null
+++ b/tests/testappacct.c
@@ -0,0 +1,278 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@freediameter.net>							 *
+*													 *
+* Copyright (c) 2013, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+#include "tests.h"
+
+/* The connection string to the database */
+#ifndef TEST_CONNINFO
+#error "Please specify the conninfo information"
+#endif /* TEST_CONNINFO */
+
+/* The table used for tests. This table will receive the following instructions:
+DROP TABLE <table>;
+CREATE TABLE <table>
+(
+  recorded_on timestamp with time zone NOT NULL,
+  "Accounting-Record-Type" integer,
+  "Session-Id" bytea,
+  "Accounting-Record-Number" integer,
+  "Route-Record1" bytea,
+  "Route-Record2" bytea,
+  "Route-Record3" bytea,
+  "Route-Record4" bytea
+);
+*/
+#define TABLE "incoming_test"
+
+#include "app_acct.h"
+#include <libpq-fe.h>
+
+static int add_avp_in_conf(char * avpname, int multi) 
+{
+	struct acct_conf_avp *new;
+	struct dict_object * dict;
+	struct dict_avp_data dictdata;
+
+	/* Validate the avp name first */
+	CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, avpname, &dict, ENOENT) );
+	CHECK_FCT( fd_dict_getval( dict, &dictdata ));
+
+	/* Create a new entry */
+	CHECK_MALLOC( new = malloc(sizeof(struct acct_conf_avp)) );
+	memset(new, 0, sizeof(struct acct_conf_avp));
+	fd_list_init(&new->chain, NULL);
+	new->avpname = avpname;
+	new->avpobj = dict;
+	new->avptype = dictdata.avp_basetype;
+	new->multi = multi;
+
+	/* Add this new entry at the end of the list */
+	fd_list_insert_before( &acct_config->avps, &new->chain );
+	
+	return 0;
+}
+
+#define LOCAL_ID	"test.app.acct"
+#define LOCAL_REALM	"app.acct"
+
+/* Main test routine */
+int main(int argc, char *argv[])
+{
+	extern pthread_key_t connk; /* in acct_db.c */
+	PGconn *conn;
+	extern int fd_ext_init(int major, int minor, char * conffile); /* defined in include's extension.h */
+	extern void fd_ext_fini(void); /* defined in the extension itself */
+	struct msg * msg;
+	os0_t sess_bkp;
+	size_t sess_bkp_len;
+	
+	/* First, initialize the daemon modules */
+	INIT_FD();
+	fd_g_config->cnf_diamid = strdup(LOCAL_ID);
+	fd_g_config->cnf_diamid_len = CONSTSTRLEN(LOCAL_ID);
+	fd_g_config->cnf_diamrlm = strdup(LOCAL_REALM);
+	fd_g_config->cnf_diamrlm_len = CONSTSTRLEN(LOCAL_REALM);
+	
+	CHECK( 0, fd_queues_init()  );
+	CHECK( 0, fd_msg_init()  );
+	CHECK( 0, fd_rtdisp_init()  );
+	
+	/* Initialize the extension configuration for the test */
+	{
+		CHECK( 0, acct_conf_init() );
+		acct_config->conninfo = strdup(TEST_CONNINFO);
+		acct_config->tablename = strdup(TABLE);
+		acct_config->tsfield = strdup("recorded_on");
+		CHECK( 0, add_avp_in_conf(strdup("Session-Id"), 0) );
+		CHECK( 0, add_avp_in_conf(strdup("Accounting-Record-Type"), 0) );
+		CHECK( 0, add_avp_in_conf(strdup("Accounting-Record-Number"), 0) );
+		CHECK( 0, add_avp_in_conf(strdup("Route-Record"), 4) );
+		
+		/* Now, call the one of the extension */
+		CHECK( 0, fd_ext_init(FD_PROJECT_VERSION_MAJOR, FD_PROJECT_VERSION_MINOR,NULL) );
+		conn = pthread_getspecific(connk);
+	}
+	
+	/* Drop and recreate the table for the test */
+	{
+		PGresult * res;
+		CHECK( CONNECTION_OK, PQstatus(conn) );
+		
+		res = PQexec(conn, "DROP TABLE " TABLE ";");
+		CHECK( PGRES_COMMAND_OK, PQresultStatus(res) );
+		PQclear(res);
+		
+		res = PQexec(conn, "CREATE TABLE " TABLE " ( "
+					"  recorded_on timestamp with time zone NOT NULL, "
+					"  \"Accounting-Record-Type\" integer, "
+					"  \"Session-Id\" bytea, "
+					"  \"Accounting-Record-Number\" integer, "
+					"  \"Route-Record1\" bytea, "
+					"  \"Route-Record2\" bytea, "
+					"  \"Route-Record3\" bytea, "
+					"  \"Route-Record4\" bytea "
+					");"
+				);
+		CHECK( PGRES_COMMAND_OK, PQresultStatus(res) );
+		PQclear(res);
+	}
+	
+	/* OK, we are ready to test now. Create an ACR message that will pass the ABNF check */
+	{
+		struct dict_object * d = NULL;
+		struct avp *avp = NULL;
+		union avp_value avp_val;
+
+		/* Now find the ACR dictionary object */
+		CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Accounting-Request", &d, ENOENT ) );
+
+		/* Create the instance */
+		CHECK( 0, fd_msg_new ( d, MSGFL_ALLOC_ETEID, &msg ) );
+		
+		/* App id */
+		{
+			struct msg_hdr * h;
+			CHECK( 0, fd_msg_hdr( msg, &h ) );
+			h->msg_appl = 3;
+		}
+		
+		/* sid */
+		{
+			struct session * sess = NULL;
+			os0_t s;
+			CHECK( 0, fd_sess_new( &sess, fd_g_config->cnf_diamid, fd_g_config->cnf_diamid_len, NULL, 0) );
+			CHECK( 0, fd_sess_getsid(sess, &s, &sess_bkp_len) );
+			CHECK( 1, (sess_bkp = os0dup(s, sess_bkp_len)) ? 1 : 0);
+
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.os.data = sess_bkp;
+			avp_val.os.len = sess_bkp_len;
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_FIRST_CHILD, avp) );
+		}
+		
+		/* Origin-* */
+		CHECK( 0, fd_msg_add_origin(msg, 1) );
+		
+		/* Destination-Realm */
+		{
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.os.data = (unsigned char *)fd_g_config->cnf_diamrlm;
+			avp_val.os.len = fd_g_config->cnf_diamrlm_len;
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+		}
+		
+		/* Accounting-Record-Type */
+		{
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Type", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.u32 = 2;
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+		}
+		
+		/* Accounting-Record-Number */
+		{
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Number", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.u32 = 2;
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+		}
+		
+		/* Route-Record */
+		{
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Route-Record", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.os.data = (unsigned char *)"peer1";
+			avp_val.os.len = strlen((char *)avp_val.os.data);
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+			
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.os.data = (unsigned char *)"peer2";
+			avp_val.os.len = strlen((char *)avp_val.os.data);
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+		}
+		
+		/* Source */
+		CHECK( 0, fd_msg_source_set( msg, "peer3", CONSTSTRLEN("peer3") ) );
+		CHECK( 0, fd_msg_source_setrr( msg, "peer3", CONSTSTRLEN("peer3"), fd_g_config->cnf_dict ) );
+	}
+	
+	/* Now, have the daemon handle this */
+	CHECK( 0, fd_fifo_post(fd_g_incoming, &msg) );
+	
+	/* It is picked by the dispatch module, the extension handles the query, inserts the records in the DB, send creates the answer.
+	   Once the answer is ready, it is sent to "peer3" which is not available of course; then the message is simply destroyed.
+	   We wait 1 second for this to happen... */
+	sleep(1);
+	
+	/* Now, check the record was actually registered properly */
+	{
+		PGresult * res;
+		uint8_t * bs;
+		char * es;
+		size_t l;
+		
+		res = PQexec(conn, "SELECT \"Session-Id\" from " TABLE ";");
+		CHECK( PGRES_TUPLES_OK, PQresultStatus(res) );
+		
+		/* We also check that the Session-Id we retrieve is the same as what we generated earlier (not trashed in the process) */
+		es = PQgetvalue(res, 0, 0);
+		bs = PQunescapeBytea((uint8_t *)es, &l);
+		
+		CHECK( 0, fd_os_cmp(bs, l, sess_bkp, sess_bkp_len) );
+		
+		PQclear(res);
+		PQfreemem(bs);
+	}  
+
+	/* That's all for the tests yet */
+	free(sess_bkp);
+	
+	PASSTEST();
+} 
+