diff --git a/src/application/config/hooks.php b/src/application/config/hooks.php
index 6c6fb6cf..dfbb0be8 100644
--- a/src/application/config/hooks.php
+++ b/src/application/config/hooks.php
@@ -9,14 +9,6 @@
| http://codeigniter.com/user_guide/general/hooks.html
|
*/
-// This hook is necessary to make the phpunit work with
-// the codeigniter framework.
-$hook['display_override'] = array(
- 'class' => 'DisplayHook',
- 'function' => 'captureOutput',
- 'filename' => 'DisplayHook.php',
- 'filepath' => 'hooks'
-);
/* End of file hooks.php */
/* Location: ./application/config/hooks.php */
\ No newline at end of file
diff --git a/src/application/controllers/test.php b/src/application/controllers/test.php
new file mode 100644
index 00000000..fc3f61cd
--- /dev/null
+++ b/src/application/controllers/test.php
@@ -0,0 +1,38 @@
+load->driver('Unit_tests');
+ }
+
+ /**
+ * Run all available unit tests.
+ */
+ public function index() {
+ $this->load->view('general/test');
+ $this->unit_tests->run_all_tests();
+ }
+
+ /**
+ * Test only the app models.
+ */
+ public function models() {
+ $this->load->view('general/test');
+ $this->unit_tests->run_model_tests();
+ }
+
+ /**
+ * Test only the app libraries.
+ */
+ public function libraries() {
+ $this->load->view('general/test');
+ $this->unit_tests->run_library_tests();
+ }
+}
+
+/* End of file test.php */
+/* Location: ./application/controllers/test.php */
\ No newline at end of file
diff --git a/src/application/helpers/custom_exceptions_helper.php b/src/application/helpers/custom_exceptions_helper.php
new file mode 100644
index 00000000..482f13a2
--- /dev/null
+++ b/src/application/helpers/custom_exceptions_helper.php
@@ -0,0 +1,14 @@
+CI =& get_instance();
- $output = $this->CI->output->get_output();
- if (ENVIRONMENT != 'testing') {
- echo $output;
- }
- }
-}
\ No newline at end of file
diff --git a/src/application/libraries/Unit_tests/Unit_tests.php b/src/application/libraries/Unit_tests/Unit_tests.php
new file mode 100644
index 00000000..7848429e
--- /dev/null
+++ b/src/application/libraries/Unit_tests/Unit_tests.php
@@ -0,0 +1,54 @@
+CI =& get_instance();
+ // Add more subclasses to the following array to expand
+ // the unit testing classes.
+ $this->valid_drivers = array(
+ 'Unit_tests_appointments_model'
+ );
+ }
+
+ /**
+ * Run all the available tests of the system.
+ *
+ * If a new group of tests is added, it should be also added in
+ * this method, in order to be executed when all the tests need to
+ * be run.
+ */
+ public function run_all_tests() {
+ $this->run_model_tests();
+ $this->run_library_tests();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // UNIT TEST GROUPS
+ ///////////////////////////////////////////////////////////////////////////
+ /**
+ * Run all the models tests.
+ */
+ public function run_model_tests() {
+ $this->appointments_model->run_all();
+ }
+
+ /**
+ * Run all the library tests.
+ */
+ public function run_library_tests() {
+
+ }
+}
\ No newline at end of file
diff --git a/src/application/libraries/Unit_tests/drivers/Unit_tests_appointments_model.php b/src/application/libraries/Unit_tests/drivers/Unit_tests_appointments_model.php
new file mode 100644
index 00000000..b46fd542
--- /dev/null
+++ b/src/application/libraries/Unit_tests/drivers/Unit_tests_appointments_model.php
@@ -0,0 +1,200 @@
+CI =& get_instance();
+
+ $this->CI->load->library('Unit_test');
+ $this->CI->load->model('Appointments_Model');
+
+ // Get some sample data from the database (will be needed in the
+ // testing methods).
+ $this->provider_id = $this->CI->db
+ ->select('ea_users.id')
+ ->from('ea_users')
+ ->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner')
+ ->where('ea_roles.slug', DB_SLUG_PROVIDER)
+ ->get()->row()->id;
+ $this->service_id = $this->CI->db
+ ->select('ea_services.id')
+ ->from('ea_services')
+ ->join('ea_services_providers', 'ea_services_providers.id_services = ea_services.id', 'inner')
+ ->where('ea_services_providers.id_users', $this->provider_id)
+ ->get()->row()->id;
+ $this->customer_id = $this->CI->db
+ ->select('ea_users.id')
+ ->from('ea_users')
+ ->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner')
+ ->where('ea_roles.slug', DB_SLUG_CUSTOMER)
+ ->get()->row()->id;
+ }
+
+ /**
+ * Run all the available tests
+ */
+ public function run_all() {
+ // All the methods whose names start with "test" are going to be
+ // executed. If you want a method to not be executed remove the
+ // "test" keyword from the beginning.
+ $class_methods = get_class_methods('Unit_tests_appointments_model');
+ foreach ($class_methods as $method_name) {
+ if (strpos($method_name, 'test_') !== FALSE) {
+ call_user_func(array($this, $method_name));
+ }
+ }
+
+ // Create a report on the browser.
+ $this->CI->output->append_output($this->CI->unit->report());
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+ // UNIT TESTS
+ /////////////////////////////////////////////////////////////////////////
+ /**
+ * Test the appointment add method - insert new record.
+ */
+ private function test_add_appointment_insert() {
+ // Add - insert new appointment record to the database.
+ $appointment_data = array(
+ 'start_datetime' => '2013-05-01 12:30:00',
+ 'end_datetime' => '2013-05-01 13:00:00',
+ 'notes' => 'Some notes right here...',
+ 'id_users_provider' => $this->provider_id,
+ 'id_users_customer' => $this->customer_id,
+ 'id_services' => $this->service_id
+ );
+ $appointment_data['id'] = $this->CI->Appointments_Model->add($appointment_data);
+ $this->CI->unit->run($appointment_data['id'], 'is_int', 'Test if add() appointment (insert operation) returned the db row id.');
+
+ // Check if the record is the one that was inserted.
+ $db_data = $this->CI->db->get_where('ea_appointments', array('id' => $appointment_data['id']))->row_array();
+ $this->CI->unit->run($appointment_data, $db_data, 'Test if add() appointment (insert operation) has successfully inserted a record.');
+
+ // Delete inserted record.
+ $this->CI->db->delete('ea_appointments', array('id' => $appointment_data['id']));
+ }
+
+ /**
+ * Test the appointment add method - update existing record.
+ */
+ private function test_add_appointment_update() {
+ // Insert new appointment (this row will be updated later).
+ $appointment_data = array(
+ 'start_datetime' => '2013-05-01 12:30:00',
+ 'end_datetime' => '2013-05-01 13:00:00',
+ 'notes' => 'Some notes right here...',
+ 'id_users_provider' => $this->provider_id,
+ 'id_users_customer' => $this->customer_id,
+ 'id_services' => $this->service_id
+ );
+ $this->CI->db->insert('ea_appointments', $appointment_data);
+ $appointment_data['id'] = $this->CI->db->insert_id();
+
+ // Perform the update operation and check if the record is update.
+ $changed_notes = 'Some CHANGED notes right here ...';
+ $appointment_data['notes'] = $changed_notes;
+
+ $update_result = $this->CI->Appointments_Model->add($appointment_data);
+ $this->CI->unit->run($update_result, 'is_int', 'Test if add() appointment (update operation) has returned the row id.');
+
+ $db_notes = $this->CI->db->get_where('ea_appointments', array('id' => $update_result))->row()->notes;
+ $this->CI->unit->run($changed_notes, $db_notes, 'Test add() appointment (update operation) has successfully updated record.');
+
+ // Delete inserted record.
+ $this->CI->db->delete('ea_appointments', array('id' => $appointment_data['id']));
+ }
+
+ /**
+ * Test appointment data insertion with wrong date
+ * format for the corresponding datetime db fields.
+ */
+ private function test_add_appointment_wrong_date_format() {
+ // Insert new appointment with no foreign keys.
+ $appointment_data = array(
+ 'start_datetime' => '2013-0WRONG5-01 12:30WRONG:00',
+ 'end_datetime' => '2013-0WRONG5-01WRONG 13:00WRONG:00',
+ 'notes' => 'Some notes right here...',
+ 'id_users_provider' => $this->provider_id,
+ 'id_users_customer' => $this->customer_id,
+ 'id_services' => $this->service_id
+ );
+
+ $hasThrownException = FALSE; // This method must throw a validation exception.
+ try {
+ $this->CI->Appointments_Model->add($appointment_data);
+ } catch(ValidationException $valExc) {
+ $hasThrownException = TRUE;
+ }
+
+ $this->CI->unit->run($hasThrownException, TRUE, 'Test add() appointment with wrong date format.', 'A validation exception must be thrown.');
+ }
+
+ /**
+ * Test the exists() method.
+ *
+ * Insert a new appointment and test if it exists.
+ */
+ private function test_appointment_exists() {
+ // Insert new appointment (this row will be checked later).
+ $appointment_data = array(
+ 'start_datetime' => '2013-05-01 12:30:00',
+ 'end_datetime' => '2013-05-01 13:00:00',
+ 'notes' => 'Some notes right here...',
+ 'id_users_provider' => $this->provider_id,
+ 'id_users_customer' => $this->customer_id,
+ 'id_services' => $this->service_id
+ );
+ $this->CI->db->insert('ea_appointments', $appointment_data);
+ $appointment_data['id'] = $this->CI->db->insert_id();
+
+ // Test the exists() method
+ $this->CI->unit->run($this->CI->Appointments_Model->exists($appointment_data), TRUE, 'Test exists() method with an inserted record.');
+
+ // Delete inserted record.
+ $this->CI->db->delete('ea_appointments', array('id' => $appointment_data['id']));
+ }
+
+ private function test_appointment_does_not_exist() {
+ // Create random appointmnet data that doesn't exist in the database.
+ $appointment_data = array(
+ 'start_datetime' => '2013-05-01 08:33:45',
+ 'end_datetime' => '2013-05-02 13:13:13',
+ 'notes' => 'This is totally random!',
+ 'id_users_provider' => '198678',
+ 'id_users_customer' => '194702',
+ 'id_services' => '8766293'
+ );
+
+ $this->CI->unit->run($this->CI->Appointments_Model->exists($appointment_data), FALSE, 'Test exists() method with an appointment that does not exist');
+ }
+
+ private function test_appointment_exists_wrong_data() {
+ // Create random appointmnet data that doesn't exist in the database.
+ $appointment_data = array(
+ 'start_datetime' => '2WRONG013-05-01 0WRONG8:33:45',
+ 'end_datetime' => '2013-0WRONG5-02 13:13:WRONG13',
+ 'notes' => 'This is totally random!',
+ 'id_users_provider' => '1986WRONG78',
+ 'id_users_customer' => '1WRONG94702',
+ 'id_services' => '876WRONG6293'
+ );
+
+ $this->CI->unit->run($this->CI->Appointments_Model->exists($appointment_data), FALSE, 'Test exists() method with wrong appointment data.');
+ }
+
+ // @task Test find_record_id
+ // @task Test delete
+ // @task Test get_batch
+ // @task Test get_row
+ // @task Test get_value
+ // @task Test validate_data
+}
\ No newline at end of file
diff --git a/src/application/models/appointments_model.php b/src/application/models/appointments_model.php
index 4050df2e..fe82de27 100644
--- a/src/application/models/appointments_model.php
+++ b/src/application/models/appointments_model.php
@@ -6,6 +6,7 @@ class Appointments_Model extends CI_Model {
*/
public function __construct() {
parent::__construct();
+ $this->load->helper('custom_exceptions');
}
/**
@@ -15,26 +16,27 @@ class Appointments_Model extends CI_Model {
* appointment doesn't exists it is going to be inserted, otherwise
* the record is going to be updated.
*
+ * @expectedException ValidationException
+ * @expectedException DatabaseException
+ *
* @param array $appointment_data Associative array with the appointmet's
* data. Each key has the same name with the database fields.
* @return int Returns the appointments id.
*/
public function add($appointment_data) {
- try {
- $appointment_id = $this->exists($appointment_data);
-
- if (!$appointment_id) {
- $appointment_id = $this->insert($appointment_data);
- } else {
- $appointment_data['id'] = $appointment_id;
- $this->update($appointment_data);
- }
-
- return $appointment_id;
-
- } catch (Exception $exc) {
- echo $exc->getMessage() . '
' . $exc->getTraceAsString() . ''; + // Validate the appointment data before doing anything. + if (!$this->validate_data($appointment_data)) { + throw new ValidationException('Appointment data are not valid'); } + + // Insert or update the appointment data. + if (!$this->exists($appointment_data)) { + $appointment_data['id'] = $this->insert($appointment_data); + } else { + $appointment_data['id'] = $this->update($appointment_data); + } + + return $appointment_data['id']; } /** @@ -44,36 +46,35 @@ class Appointments_Model extends CI_Model { * the database. This method does not search with the id, but with a * combination of the appointments field values. * + * @uses find_record_id() + * * @param array $appointment_data Associative array with the appointment's * data. Each key has the same name with the database fields. - * @return int|bool Returns the record id or FALSE if it doesn't exist. + * @return bool Returns wether the record exists or not. */ - public function exists($appointment_data) { - $this->db->where(array( - 'start_datetime' => $appointment_data['start_datetime'], - 'end_datetime' => $appointment_data['end_datetime'], - 'id_users_provider' => $appointment_data['id_users_provider'], - 'id_users_customer' => $appointment_data['id_users_customer'], - 'id_services' => $appointment_data['id_services'] - )); - - $result = $this->db->get('ea_appointments'); - - return ($result->num_rows() > 0) ? $result->row()->id : FALSE; + public function exists($appointment_data) { + try { + $this->find_record_id($appointment_data); + return TRUE; + } catch(DatabaseException $dbExc) { + return FALSE; + } } /** * Insert a new appointment record to the database. * + * @expectedException DatabaseException + * * @param array $appointment_data Associative array with the appointment's * data. Each key has the same name with the database fields. * @return int Returns the id of the new record. */ private function insert($appointment_data) { if (!$this->db->insert('ea_appointments', $appointment_data)) { - throw new Exception('Could not insert new appointment record.'); + throw new DatabaseException('Could not insert appointment record.'); } - return $this->db->insert_id(); + return intval($this->db->insert_id()); } /** @@ -84,12 +85,77 @@ class Appointments_Model extends CI_Model { * * @param array $appointment_data Associative array with the appointment's * data. Each key has the same name with the database fields. - * @return @return int Returns the id of the updated record. + * @return int Returns the id of the updated record. */ private function update($appointment_data) { + if (!isset($appointment_data['id'])) { + $appointment_data['id'] = $this->find_record_id($appointment_data); + } + $this->db->where('id', $appointment_data['id']); if (!$this->db->update('ea_appointments', $appointment_data)) { - throw new Exception('Could not update appointment record.'); + throw new DatabaseException('Could not update appointment record.'); + } + + return $appointment_data['id']; + } + + /** + * Find the database id of an appointment record. + * + * The appointment data should include the following fields in order to + * get the unique id from the database: start_datetime, end_datetime, + * id_users_provider, id_users_customer, id_services. + * + * @expectedException DatabaseException + * + * @param array $appointment_data Array with the appointment data. The + * keys of the array should have the same names as the db fields. + * @return int Returns the id. + */ + public function find_record_id($appointment_data) { + $this->db->where(array( + 'start_datetime' => $appointment_data['start_datetime'], + 'end_datetime' => $appointment_data['end_datetime'], + 'id_users_provider' => $appointment_data['id_users_provider'], + 'id_users_customer' => $appointment_data['id_users_customer'], + 'id_services' => $appointment_data['id_services'] + )); + + $result = $this->db->get('ea_appointments'); + + if ($result->num_rows() == 0) { + throw new DatabaseException('Could not find appointment record id.'); + } + + return $result->row()->id; + } + + /** + * Validate appointment data before the insert or + * update operation is executed. + * + * @param array $appointment_data Contains the appointment data. + * @return boolean Returns the validation result. + */ + public function validate_data($appointment_data) { + $this->load->helper('data_validation'); + + try { + // Check if appointment dates are valid. + if (!validate_mysql_datetime($appointment_data['start_datetime'])) { + throw new Exception('Appointment start datetime is invalid.'); + } + + if (!validate_mysql_datetime($appointment_data['end_datetime'])) { + throw new Exception('Appointment end datetime is invalid.'); + } + + // @task Check if appointment foreign keys are valid. + + return TRUE; + } catch (Exception $exc) { + return FALSE; } } diff --git a/src/application/views/general/test.php b/src/application/views/general/test.php new file mode 100644 index 00000000..bbc706a5 --- /dev/null +++ b/src/application/views/general/test.php @@ -0,0 +1,45 @@ + + + + + +