1153 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1153 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php /** @noinspection PhpMissingDocCommentInspection */
 | 
						|
 | 
						|
/*********************************************************************************
 | 
						|
 *
 | 
						|
 * TimeTrex is a Workforce Management program developed by
 | 
						|
 * TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify it under
 | 
						|
 * the terms of the GNU Affero General Public License version 3 as published by
 | 
						|
 * the Free Software Foundation with the addition of the following permission
 | 
						|
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 | 
						|
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
 | 
						|
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
						|
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 | 
						|
 * details.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU Affero General Public License along
 | 
						|
 * with this program; if not, see http://www.gnu.org/licenses or write to the Free
 | 
						|
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | 
						|
 * 02110-1301 USA.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
 | 
						|
 * #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * The interactive user interfaces in modified source and object code versions
 | 
						|
 * of this program must display Appropriate Legal Notices, as required under
 | 
						|
 * Section 5 of the GNU Affero General Public License version 3.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * In accordance with Section 7(b) of the GNU Affero General Public License
 | 
						|
 * version 3, these Appropriate Legal Notices must retain the display of the
 | 
						|
 * "Powered by TimeTrex" logo. If the display of the logo is not reasonably
 | 
						|
 * feasible for technical reasons, the Appropriate Legal Notices must display
 | 
						|
 * the words "Powered by TimeTrex".
 | 
						|
 *
 | 
						|
 ********************************************************************************/
 | 
						|
 | 
						|
class SQLTest extends PHPUnit\Framework\TestCase {
 | 
						|
	public function setUp(): void {
 | 
						|
		global $dd;
 | 
						|
		Debug::text( 'Running setUp(): ', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
		$dd = new DemoData();
 | 
						|
		$dd->setEnableQuickPunch( false );                     //Helps prevent duplicate punch IDs and validation failures.
 | 
						|
		$dd->setUserNamePostFix( '_' . uniqid( null, true ) ); //Needs to be super random to prevent conflicts and random failing tests.
 | 
						|
		$this->company_id = $dd->createCompany();
 | 
						|
		$this->legal_entity_id = $dd->createLegalEntity( $this->company_id, 10 );
 | 
						|
		Debug::text( 'Company ID: ' . $this->company_id, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		$this->assertTrue( TTUUID::isUUID( $this->company_id ) );
 | 
						|
 | 
						|
		//$dd->createPermissionGroups( $this->company_id, 40 ); //Administrator only.
 | 
						|
 | 
						|
		$dd->createCurrency( $this->company_id, 10 );
 | 
						|
 | 
						|
		$this->branch_id = $dd->createBranch( $this->company_id, 10 ); //NY
 | 
						|
 | 
						|
		//$dd->createPayStubAccount( $this->company_id );
 | 
						|
		//$dd->createPayStubAccountLink( $this->company_id );
 | 
						|
 | 
						|
		$dd->createUserWageGroups( $this->company_id );
 | 
						|
 | 
						|
		$this->user_id = $dd->createUser( $this->company_id, $this->legal_entity_id, 100 );
 | 
						|
		$this->assertTrue( TTUUID::isUUID( $this->user_id ) );
 | 
						|
	}
 | 
						|
 | 
						|
	public function tearDown(): void {
 | 
						|
		Debug::text( 'Running tearDown(): ', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
	}
 | 
						|
 | 
						|
	function getListFactoryClassList( $equal_parts = 1 ) {
 | 
						|
		global $global_class_map;
 | 
						|
 | 
						|
		$retarr = [];
 | 
						|
 | 
						|
		//Get all ListFactory classes
 | 
						|
		foreach ( $global_class_map as $class_name => $class_file_name ) {
 | 
						|
			if ( strpos( $class_name, 'ListFactory' ) !== false ) {
 | 
						|
				$retarr[] = $class_name;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		$chunk_size = ceil( ( count( $retarr ) / $equal_parts ) );
 | 
						|
 | 
						|
		return array_chunk( $retarr, $chunk_size );
 | 
						|
	}
 | 
						|
 | 
						|
	/** @noinspection PhpMissingBreakStatementInspection */
 | 
						|
	function runSQLTestOnListFactory( $factory_name ) {
 | 
						|
		if ( class_exists( $factory_name ) ) {
 | 
						|
			$reflectionClass = new ReflectionClass( $factory_name );
 | 
						|
			$class_file_name = $reflectionClass->getFileName();
 | 
						|
 | 
						|
			Debug::text( 'Checking Class: ' . $factory_name . ' File: ' . $class_file_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			$filter_data_types = [
 | 
						|
					'not_set',
 | 
						|
					'true',
 | 
						|
					'false',
 | 
						|
					'null',
 | 
						|
					'empty_string',
 | 
						|
					'negative_small_int',
 | 
						|
					'small_int',
 | 
						|
					'large_int',
 | 
						|
					'string',
 | 
						|
					'array',
 | 
						|
			];
 | 
						|
 | 
						|
			//Parse filter array keys from class file so we can populate them with dummy data.
 | 
						|
			preg_match_all( '/\$filter_data\[\'([a-z0-9_]*)\'\]/i', file_get_contents( $class_file_name ), $filter_data_match );
 | 
						|
			if ( isset( $filter_data_match[1] ) ) {
 | 
						|
				//Debug::Arr($filter_data_match, 'Filter Data Match: ', __FILE__, __LINE__, __METHOD__, 10);
 | 
						|
				foreach ( $filter_data_types as $filter_data_type ) {
 | 
						|
					Debug::Text( 'Filter Data Type: ' . $filter_data_type, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
					$filter_data = [];
 | 
						|
 | 
						|
					$filter_data_match[1] = array_unique( $filter_data_match[1] );
 | 
						|
					foreach ( $filter_data_match[1] as $filter_data_key ) {
 | 
						|
						//Skip sort_column/sort_order
 | 
						|
						if ( in_array( $filter_data_key, [ 'sort_column', 'sort_order' ] ) ) {
 | 
						|
							continue;
 | 
						|
						}
 | 
						|
 | 
						|
						//Test with:
 | 
						|
						// Small Integers
 | 
						|
						// Large Integers (64bit)
 | 
						|
						// Strings
 | 
						|
						// Arrays
 | 
						|
						switch ( $filter_data_type ) {
 | 
						|
							case 'true':
 | 
						|
								$filter_data[$filter_data_key] = true;
 | 
						|
								break;
 | 
						|
							case 'false':
 | 
						|
								$filter_data[$filter_data_key] = false;
 | 
						|
								break;
 | 
						|
							case 'null':
 | 
						|
								$filter_data[$filter_data_key] = null;
 | 
						|
								break;
 | 
						|
							case 'empty_string':
 | 
						|
								$filter_data[$filter_data_key] = '';
 | 
						|
								break;
 | 
						|
							case 'negative_small_int':
 | 
						|
								$filter_data[$filter_data_key] = ( rand( 0, 128 ) * -1 );
 | 
						|
								break;
 | 
						|
							case 'small_int':
 | 
						|
								$filter_data[$filter_data_key] = rand( 0, 128 );
 | 
						|
								break;
 | 
						|
							case 'large_int':
 | 
						|
								$filter_data[$filter_data_key] = rand( 2147483648, 21474836489 );
 | 
						|
								break;
 | 
						|
							case 'string':
 | 
						|
								$filter_data[$filter_data_key] = 'A' . substr( md5( microtime() ), rand( 0, 26 ), 10 );
 | 
						|
								break;
 | 
						|
							case 'array':
 | 
						|
								$filter_data[$filter_data_key] = [ rand( 0, 128 ), rand( 2147483648, 21474836489 ), 'A' . substr( md5( microtime() ), rand( 0, 26 ), 10 ) ];
 | 
						|
								break;
 | 
						|
							case 'not_set':
 | 
						|
								break;
 | 
						|
						}
 | 
						|
					}
 | 
						|
					//Debug::Arr($filter_data, 'Filter Data: ', __FILE__, __LINE__, __METHOD__, 10);
 | 
						|
 | 
						|
					$lf = TTNew( $factory_name );
 | 
						|
					switch ( $factory_name ) {
 | 
						|
						case 'ScheduleListFactory':
 | 
						|
							$retarr = $lf->getSearchByCompanyIdAndArrayCriteria( $this->company_id, $filter_data, 1, 1, null, null );
 | 
						|
							$this->assertNotEquals( false, $retarr );
 | 
						|
							$this->assertTrue( is_object( $retarr ), true );
 | 
						|
 | 
						|
							$retarr = $lf->getAPISearchByCompanyIdAndArrayCriteria( $this->company_id, $filter_data, 1, 1, null, null );
 | 
						|
							$this->assertNotEquals( false, $retarr );
 | 
						|
							$this->assertTrue( is_object( $retarr ), true );
 | 
						|
							break;
 | 
						|
						case 'MessageControlListFactory':
 | 
						|
						case 'NotificationListFactory':
 | 
						|
							$filter_data['current_user_id'] = $this->user_id;
 | 
						|
							//break; //Intentional that break is not here so it spills into default?
 | 
						|
						default:
 | 
						|
							if ( method_exists( $lf, 'getAPISearchByCompanyIdAndArrayCriteria' ) ) {
 | 
						|
								//Make sure we test pagination, especially with subqueries and the need for _ADODB_COUNT workarounds, $limit = NULL, $page = NULL, $where = NULL, $order = NULL
 | 
						|
								$retarr = $lf->getAPISearchByCompanyIdAndArrayCriteria( $this->company_id, $filter_data, 1, 1, null, null );
 | 
						|
								$this->assertNotEquals( false, $retarr );
 | 
						|
								$this->assertTrue( is_object( $retarr ), true );
 | 
						|
							}
 | 
						|
							break;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			unset( $filter_data_match );
 | 
						|
 | 
						|
			Debug::text( 'Done...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			return true;
 | 
						|
		} else {
 | 
						|
			Debug::text( 'Class does not exist: ' . $factory_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	function runSQLTestOnListFactoryMethods( $factory_name ) {
 | 
						|
		if ( in_array( $factory_name, [
 | 
						|
											'HierarchyListFactory',
 | 
						|
											'PolicyGroupAccrualPolicyListFactory',
 | 
						|
											'PolicyGroupOverTimePolicyListFactory',
 | 
						|
											'PolicyGroupPremiumPolicyListFactory',
 | 
						|
											'PolicyGroupRoundIntervalPolicyListFactory',
 | 
						|
											'ProductTaxPolicyProductListFactory',
 | 
						|
											'RecurringScheduleUserListFactory',
 | 
						|
									]
 | 
						|
		)
 | 
						|
		) {
 | 
						|
			return true; //Deprecated classes.
 | 
						|
		}
 | 
						|
 | 
						|
		if ( class_exists( $factory_name ) ) {
 | 
						|
			$reflectionClass = new ReflectionClass( $factory_name );
 | 
						|
			Debug::text( 'Checking Class: ' . $factory_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			$raw_methods = $reflectionClass->getMethods( ReflectionMethod::IS_PUBLIC );
 | 
						|
			if ( is_array( $raw_methods ) ) {
 | 
						|
				foreach ( $raw_methods as $raw_method ) {
 | 
						|
					if ( $factory_name == $raw_method->class
 | 
						|
							&& (
 | 
						|
									strpos( $raw_method->name, 'getAll' ) !== false
 | 
						|
									|| strpos( $raw_method->name, 'getBy' ) !== false
 | 
						|
									|| strpos( $raw_method->name, 'Report' ) !== false
 | 
						|
							)
 | 
						|
							&& (
 | 
						|
								//Skip getByCompanyIdArray() functions, but include getBy*AndArrayCriteria(). So just check if its ends with Array or not.
 | 
						|
							( substr( $raw_method->name, -5 ) !== 'Array' )
 | 
						|
							)
 | 
						|
					) {
 | 
						|
						Debug::text( 'Class: ' . $factory_name . ' Method: ' . $raw_method->name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
						$test_modes = [ 'default', 'fuzz' ];
 | 
						|
						foreach ( $test_modes as $test_mode ) {
 | 
						|
							Debug::text( '  Test Mode: ' . $test_mode, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
							//Get method arguments.
 | 
						|
							$method_parameters = $raw_method->getParameters();
 | 
						|
							if ( is_array( $method_parameters ) ) {
 | 
						|
								$input_arguments = [];
 | 
						|
								foreach ( $method_parameters as $method_parameter ) {
 | 
						|
									Debug::text( '  Parameter: ' . $method_parameter->name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
									switch ( $factory_name ) {
 | 
						|
										case 'ClientContactListFactory':
 | 
						|
											switch ( $method_parameter->name ) {
 | 
						|
												case 'key':
 | 
						|
													$input_argument = '900d6136975e3a728051a62ed119191034568745';
 | 
						|
													break;
 | 
						|
												case 'name':
 | 
						|
													$input_argument = 'test';
 | 
						|
													break;
 | 
						|
											}
 | 
						|
											break;
 | 
						|
										case 'RoundIntervalPolicyListFactory':
 | 
						|
											switch ( $method_parameter->name ) {
 | 
						|
												case 'type_id':
 | 
						|
													$input_argument = 40;
 | 
						|
													break;
 | 
						|
											}
 | 
						|
											break;
 | 
						|
										case 'ScheduleListFactory':
 | 
						|
											switch ( $method_parameter->name ) {
 | 
						|
												case 'direction':
 | 
						|
													$input_argument = 'before';
 | 
						|
													break;
 | 
						|
											}
 | 
						|
											break;
 | 
						|
										case 'UserListFactory':
 | 
						|
										case 'UserContactListFactory':
 | 
						|
										case 'JobApplicantListFactory':
 | 
						|
											switch ( $method_parameter->name ) {
 | 
						|
												case 'email':
 | 
						|
													$input_argument = 'hi@hi.com';
 | 
						|
													break;
 | 
						|
												case 'key':
 | 
						|
													$input_argument = '900d6136975e3a728051a62ed119191034568745';
 | 
						|
													break;
 | 
						|
											}
 | 
						|
											break;
 | 
						|
										case 'PayPeriodTimeSheetVerifyListFactory':
 | 
						|
										case 'RequestListFactory':
 | 
						|
											switch ( $method_parameter->name ) {
 | 
						|
												case 'hierarchy_level_map':
 | 
						|
													$input_argument = [
 | 
						|
															[
 | 
						|
																	'hierarchy_control_id' => 1,
 | 
						|
																	'level'                => 1,
 | 
						|
																	'last_level'           => 2,
 | 
						|
																	'object_type_id'       => 10,
 | 
						|
															],
 | 
						|
													];
 | 
						|
													break;
 | 
						|
											}
 | 
						|
											break;
 | 
						|
										case 'ExceptionListFactory':
 | 
						|
											switch ( $method_parameter->name ) {
 | 
						|
												case 'time_period':
 | 
						|
													$input_argument = 'week';
 | 
						|
													break;
 | 
						|
											}
 | 
						|
											break;
 | 
						|
									}
 | 
						|
 | 
						|
									if ( $test_mode == 'fuzz' ) {
 | 
						|
										//If LIMIT argument is available always set it to 1 to reduce memory usage.
 | 
						|
										if ( in_array( $method_parameter->name, [ 'where', 'order', 'page' ] ) ) {
 | 
						|
											$input_argument = null;
 | 
						|
										} else if ( !isset( $input_argument ) && ( $method_parameter->name == 'id' || strpos( $method_parameter->name, '_id' ) !== false || $method_parameter->name == 'limit' ) ) { //Use integer as its a ID argument.
 | 
						|
											$input_argument = 'false';                                                                                                                                               //Try passing a string where ID is expected.
 | 
						|
										} else if ( !isset( $input_argument ) ) {
 | 
						|
											$input_argument = 2;
 | 
						|
										}
 | 
						|
										$input_arguments[] = $input_argument;
 | 
						|
									} else {
 | 
						|
										//If LIMIT argument is available always set it to 1 to reduce memory usage.
 | 
						|
										if ( in_array( $method_parameter->name, [ 'where', 'order', 'page' ] ) ) {
 | 
						|
											$input_argument = null;
 | 
						|
										} else if ( !isset( $input_argument ) && ( $method_parameter->name == 'id' || strpos( $method_parameter->name, '_id' ) !== false || $method_parameter->name == 'limit' ) ) { //Use integer as its a ID argument.
 | 
						|
											$input_argument = 1;
 | 
						|
										} else if ( !isset( $input_argument ) ) {
 | 
						|
											$input_argument = 2;
 | 
						|
										}
 | 
						|
										$input_arguments[] = $input_argument;
 | 
						|
									}
 | 
						|
									unset( $input_argument );
 | 
						|
								}
 | 
						|
 | 
						|
								if ( isset( $input_arguments ) && is_array( $input_arguments ) ) {
 | 
						|
									Debug::Arr( $input_arguments, '    Calling Class: ' . $factory_name . ' Method: ' . $raw_method->name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
									$lf = TTNew( $factory_name );
 | 
						|
									switch ( $factory_name . '::' . $raw_method->name ) {
 | 
						|
										case 'StationListFactory::getByUserIdAndStatusAndType':
 | 
						|
										case 'PayStubEntryAccountListFactory::getByTypeArrayByCompanyIdAndStatusId':
 | 
						|
											//Skip due to failures.
 | 
						|
											break;
 | 
						|
										case 'CompanyListFactory::getByPhoneID':
 | 
						|
											$retarr = call_user_func_array( [ $lf, $raw_method->name ], $input_arguments );
 | 
						|
											if ( $test_mode == 'fuzz' ) {
 | 
						|
												$this->assertEquals( false, ( ( is_object( $retarr ) ) ? ( ( $retarr->getRecordCount() == 0 ) ? false : true ) : $retarr ) ); //This will be FALSE
 | 
						|
											} else {
 | 
						|
												$this->assertNotEquals( false, $retarr );
 | 
						|
												$this->assertTrue( is_object( $retarr ), true );
 | 
						|
											}
 | 
						|
											break;
 | 
						|
										case 'MessageControlListFactory::getByCompanyIdAndObjectTypeAndObjectAndNotUser':
 | 
						|
											$retarr = call_user_func_array( [ $lf, $raw_method->name ], $input_arguments );
 | 
						|
											$this->assertEquals( false, ( ( is_object( $retarr ) ) ? ( ( $retarr->getRecordCount() == 0 ) ? false : true ) : $retarr ) ); //This will be FALSE, but it still executes a query.
 | 
						|
											//$this->assertTrue( is_object($retarr), TRUE );
 | 
						|
											break;
 | 
						|
										case 'PayStubEntryListFactory::getByPayStubIdAndEntryNameId':
 | 
						|
											//FUZZ tests should return FALSE, otherwise they should be normal.
 | 
						|
											$retarr = call_user_func_array( [ $lf, $raw_method->name ], $input_arguments );
 | 
						|
											if ( $test_mode == 'fuzz' ) {
 | 
						|
												$this->assertEquals( false, ( ( is_object( $retarr ) ) ? ( ( $retarr->getRecordCount() == 0 ) ? false : true ) : $retarr ) ); //This will be FALSE
 | 
						|
											} else {
 | 
						|
												$this->assertNotEquals( false, $retarr );
 | 
						|
												$this->assertTrue( is_object( $retarr ), true );
 | 
						|
											}
 | 
						|
											break;
 | 
						|
										default:
 | 
						|
											$retarr = call_user_func_array( [ $lf, $raw_method->name ], $input_arguments );
 | 
						|
											//Debug::Arr($retarr, '    RetArr: ', __FILE__, __LINE__, __METHOD__, 10);
 | 
						|
											$this->assertNotEquals( false, $retarr );
 | 
						|
											$this->assertTrue( is_object( $retarr ), true );
 | 
						|
											break;
 | 
						|
									}
 | 
						|
								} else {
 | 
						|
									Debug::text( '  No INPUT arguments... Skipping Class: ' . $factory_name . ' Method: ' . $raw_method->name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
								}
 | 
						|
							}
 | 
						|
						}
 | 
						|
					} else {
 | 
						|
						Debug::text( 'Skipping... Class: ' . $factory_name . ' Method: ' . $raw_method->name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			Debug::text( 'Done...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			return true;
 | 
						|
		} else {
 | 
						|
			Debug::text( 'Class does not exist: ' . $factory_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	function runSQLSortTestOnListFactory( $factory_name ) {
 | 
						|
		if ( class_exists( $factory_name ) ) {
 | 
						|
			$reflectionClass = new ReflectionClass( $factory_name );
 | 
						|
			$class_file_name = $reflectionClass->getFileName();
 | 
						|
 | 
						|
			Debug::text( 'Checking Class: ' . $factory_name . ' File: ' . $class_file_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			$lf = TTNew( $factory_name );
 | 
						|
			if ( method_exists( $lf, 'getOptions' ) ) {
 | 
						|
				if ( method_exists( $lf, 'getAPISearchByCompanyIdAndArrayCriteria' ) ) {
 | 
						|
					$tmp_columns = $lf->getOptions( 'columns' );
 | 
						|
					if ( is_array( $tmp_columns ) && count( $tmp_columns ) > 0 ) {
 | 
						|
						$columns = array_fill_keys( array_keys( array_flip( array_keys( Misc::trimSortPrefix( $lf->getOptions( 'columns' ) ) ) ) ), 'asc' );        //Set sort order to ASC for all columns.
 | 
						|
						unset( $columns['tag'] );                                                                                                                   //Remove columns that we can never sort by.
 | 
						|
						if ( is_array( $columns ) ) {
 | 
						|
							try {
 | 
						|
 | 
						|
								if ( !in_array( $factory_name, [ 'BankAccountListFactory' ] ) ) { //Skip legacy factories.
 | 
						|
									if ( $factory_name == 'MessageControlListFactory' ) {
 | 
						|
										$retarr = $lf->getAPISearchByCompanyIdAndArrayCriteria( $this->company_id, [ 'current_user_id' => TTUUID::getZeroID() ], 1, 1, null, $columns );
 | 
						|
									} else if ( $factory_name == 'NotificationListFactory' ) {
 | 
						|
										$retarr = $lf->getAPISearchByCompanyIdAndArrayCriteria( $this->company_id, [ 'current_user_id' => TTUUID::getZeroID() ], 1, 1, null, $columns );
 | 
						|
									} else {
 | 
						|
										//$retarr = $lf->getAPISearchByCompanyIdAndArrayCriteria( $this->company_id, array(), 1, 1, NULL, array('a.bogus' => 'asc') );
 | 
						|
										$retarr = $lf->getAPISearchByCompanyIdAndArrayCriteria( $this->company_id, [], 1, 1, null, $columns );
 | 
						|
									}
 | 
						|
 | 
						|
									$this->assertNotEquals( false, $retarr );
 | 
						|
									$this->assertTrue( is_object( $retarr ), true );
 | 
						|
								}
 | 
						|
							} catch ( Exception $e ) {
 | 
						|
								Debug::Arr( $columns, 'Columns: ', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
								$this->assertTrue( false, $factory_name . ': ' . $e->getMessage() );
 | 
						|
							}
 | 
						|
						} else {
 | 
						|
							Debug::text( 'getOptions(\'columns\') does not return any data, skipping... Factory: ' . $factory_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
						}
 | 
						|
					}
 | 
						|
					unset( $tmp_columns );
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				Debug::text( 'getOptions() method does not exist, skipping...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			}
 | 
						|
 | 
						|
			Debug::text( 'Done...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			return true;
 | 
						|
		} else {
 | 
						|
			Debug::text( 'Class does not exist: ' . $factory_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	function runSQLTestOnEdition( $product_edition = TT_PRODUCT_ENTERPRISE, $class_list ) {
 | 
						|
		global $TT_PRODUCT_EDITION, $db;
 | 
						|
 | 
						|
		$original_product_edition = getTTProductEdition();
 | 
						|
 | 
						|
		$this->assertTrue( true );
 | 
						|
		if ( $product_edition <= $original_product_edition ) {
 | 
						|
			$TT_PRODUCT_EDITION = $product_edition;
 | 
						|
			Debug::text( 'Checking against Edition: ' . getTTProductEditionName(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			//Loop through all ListFactory classes testing SQL queries.
 | 
						|
 | 
						|
			//Run tests with count rows enabled, then with it disabled as well.
 | 
						|
			$db->pageExecuteCountRows = false;
 | 
						|
			foreach ( $class_list as $class_name ) {
 | 
						|
				$this->runSQLSortTestOnListFactory( $class_name );
 | 
						|
				$this->runSQLTestOnListFactoryMethods( $class_name );
 | 
						|
				$this->runSQLTestOnListFactory( $class_name );
 | 
						|
			}
 | 
						|
 | 
						|
			$db->pageExecuteCountRows = true;
 | 
						|
			foreach ( $class_list as $class_name ) {
 | 
						|
				$this->runSQLTestOnListFactoryMethods( $class_name );
 | 
						|
				$this->runSQLTestOnListFactory( $class_name );
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_CommunityA
 | 
						|
	 */
 | 
						|
	function testSQLCommunityA() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_COMMUNITY, $classes[0] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_CommunityB
 | 
						|
	 */
 | 
						|
	function testSQLCommunityB() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_COMMUNITY, $classes[1] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_CommunityC
 | 
						|
	 */
 | 
						|
	function testSQLCommunityC() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_COMMUNITY, $classes[2] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_CommunityD
 | 
						|
	 */
 | 
						|
	function testSQLCommunityD() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_COMMUNITY, $classes[3] );
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_ProfessionalA
 | 
						|
	 */
 | 
						|
	function testSQLProfessionalA() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_PROFESSIONAL, $classes[0] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_ProfessionalB
 | 
						|
	 */
 | 
						|
	function testSQLProfessionalB() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_PROFESSIONAL, $classes[1] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_ProfessionalC
 | 
						|
	 */
 | 
						|
	function testSQLProfessionalC() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_PROFESSIONAL, $classes[2] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_ProfessionalD
 | 
						|
	 */
 | 
						|
	function testSQLProfessionalD() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_PROFESSIONAL, $classes[3] );
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_CorporateA
 | 
						|
	 */
 | 
						|
	function testSQLCorporateA() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_CORPORATE, $classes[0] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_CorporateB
 | 
						|
	 */
 | 
						|
	function testSQLCorporateB() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_CORPORATE, $classes[1] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_CorporateC
 | 
						|
	 */
 | 
						|
	function testSQLCorporateC() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_CORPORATE, $classes[2] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_CorporateD
 | 
						|
	 */
 | 
						|
	function testSQLCorporateD() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_CORPORATE, $classes[3] );
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_EnterpriseA
 | 
						|
	 */
 | 
						|
	function testSQLEnterpriseA() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_ENTERPRISE, $classes[0] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_EnterpriseB
 | 
						|
	 */
 | 
						|
	function testSQLEnterpriseB() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_ENTERPRISE, $classes[1] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_EnterpriseC
 | 
						|
	 */
 | 
						|
	function testSQLEnterpriseC() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_ENTERPRISE, $classes[2] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_EnterpriseD
 | 
						|
	 */
 | 
						|
	function testSQLEnterpriseD() {
 | 
						|
		$classes = $this->getListFactoryClassList( 4 );
 | 
						|
		$this->runSQLTestOnEdition( TT_PRODUCT_ENTERPRISE, $classes[3] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_ADODBActiveRecordCount
 | 
						|
	 */
 | 
						|
	function testADODBActiveRecordCount() {
 | 
						|
		global $db;
 | 
						|
 | 
						|
		//This will test the automatic functionality of ADODB to add count(*) in SQL queries.
 | 
						|
 | 
						|
		//PageExecute($query, $limit, $page, $ph)
 | 
						|
 | 
						|
		$db->pageExecuteCountRows = true;
 | 
						|
 | 
						|
		try {
 | 
						|
			$query = 'SELECT id FROM currency';
 | 
						|
			$db->PageExecute( $query, 2, 2 );
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
		try {
 | 
						|
			$query = 'SELECT id, status_id FROM currency';
 | 
						|
			$db->PageExecute( $query, 2, 2 );
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
		try {
 | 
						|
			$query = 'SELECT a.id FROM currency AS a LEFT JOIN company as tmp ON a.id = tmp.id';
 | 
						|
			$db->PageExecute( $query, 2, 2 );
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
		try {
 | 
						|
			$query = 'SELECT a.id FROM currency AS a LEFT JOIN ( SELECT id FROM company ) as tmp ON a.id = tmp.id';
 | 
						|
			$db->PageExecute( $query, 2, 2 );
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
		try {
 | 
						|
			$query = 'SELECT a.id, a.status_id FROM currency AS a LEFT JOIN ( SELECT id, status_id FROM company ) as tmp ON a.id = tmp.id';
 | 
						|
			$db->PageExecute( $query, 2, 2 );
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
		try {
 | 
						|
			$query = 'SELECT * FROM ( SELECT a.id FROM currency AS a LEFT JOIN company as tmp ON a.id = tmp.id ) as tmp2';
 | 
						|
			$db->PageExecute( $query, 2, 2 );
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
		try {
 | 
						|
			$query = 'SELECT _ADODB_COUNT id, (SELECT 1 FROM currency LIMIT 1) _ADODB_COUNT AS tmp FROM users';
 | 
						|
			$db->PageExecute( $query, 2, 2 );
 | 
						|
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
		//This query should have the _ADODB_COUNT keyword above to make it work on all databases.
 | 
						|
		//It works on PGSQL because it can wrap it in a sub-query, ie: SELECT count(*) FROM ( $query )
 | 
						|
		try {
 | 
						|
			$query = 'SELECT id, (SELECT 1 FROM currency LIMIT 1) AS tmp FROM users';
 | 
						|
			$db->PageExecute( $query, 2, 2 );
 | 
						|
 | 
						|
			$this->assertTrue( true ); //PGSQL
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false ); //PGSQL
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_SQLInjectionA
 | 
						|
	 */
 | 
						|
	function testSQLInjectionA() {
 | 
						|
		//Test SQL injection with SORT SQL.
 | 
						|
		//Test SQL injection with WHERE SQL.
 | 
						|
 | 
						|
		$utlf = new UserTitleListFactory();
 | 
						|
 | 
						|
		//Test standard case that should work.
 | 
						|
		$sort_arr = [
 | 
						|
				'a.name' => 'asc',
 | 
						|
		];
 | 
						|
		$utlf->getAPISearchByCompanyIdAndArrayCriteria( 1, [], null, null, null, $sort_arr );
 | 
						|
 | 
						|
		//var_dump( $utlf->rs->sql );
 | 
						|
		if ( stripos( $utlf->rs->sql, 'a.name' ) !== false ) {
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} else {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
		//Test advanced case that should work.
 | 
						|
		$sort_arr = [
 | 
						|
			//'a.name = \'test\'' => 'asc'
 | 
						|
			'a.created_date = 0' => 'asc',
 | 
						|
		];
 | 
						|
		$utlf->getAPISearchByCompanyIdAndArrayCriteria( 1, [], null, null, null, $sort_arr );
 | 
						|
 | 
						|
		//var_dump( $utlf->rs->sql );
 | 
						|
		if ( stripos( $utlf->rs->sql, 'a.created_date = 0' ) !== false ) {
 | 
						|
			$this->assertTrue( true );
 | 
						|
		} else {
 | 
						|
			$this->assertTrue( false );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
		//Test advanced case that does not work currently, but we may need to get to work.
 | 
						|
		try {
 | 
						|
			$pself = new PayStubEntryListFactory();
 | 
						|
			$sort_arr = [
 | 
						|
					'abs(a.created_date)' => 'asc',
 | 
						|
			];
 | 
						|
 | 
						|
			$pself->getAPISearchByCompanyIdAndArrayCriteria( 1, [], null, null, null, $sort_arr );
 | 
						|
			//var_dump( $pself->rs->sql );
 | 
						|
			if ( stripos( $pself->rs->sql, 'abs(a.created_date)' ) !== false ) {
 | 
						|
				$this->assertTrue( false );
 | 
						|
			} else {
 | 
						|
				$this->assertTrue( true );
 | 
						|
			}
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( true );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
		//Test SQL injection in the ORDER BY clause.
 | 
						|
		try {
 | 
						|
			$sort_arr = [
 | 
						|
					'created_by' => '(SELECT 1)-- .id.',
 | 
						|
			];
 | 
						|
			$utlf->getAPISearchByCompanyIdAndArrayCriteria( 1, [], null, null, null, $sort_arr );
 | 
						|
 | 
						|
			//var_dump( $utlf->rs->sql );
 | 
						|
			if ( stripos( $utlf->rs->sql, '(SELECT 1)' ) !== false ) {
 | 
						|
				$this->assertTrue( false );
 | 
						|
			} else {
 | 
						|
				$this->assertTrue( true );
 | 
						|
			}
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( true );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
		//Test SQL injection with brackets and "--"
 | 
						|
		try {
 | 
						|
			$sort_arr = [
 | 
						|
					'(SELECT 1)-- .id.' => 1,
 | 
						|
			];
 | 
						|
			$utlf->getAPISearchByCompanyIdAndArrayCriteria( 1, [], null, null, null, $sort_arr );
 | 
						|
 | 
						|
			//var_dump( $utlf->rs->sql );
 | 
						|
			if ( stripos( $utlf->rs->sql, '(SELECT 1)' ) !== false ) {
 | 
						|
				$this->assertTrue( false );
 | 
						|
			} else {
 | 
						|
				$this->assertTrue( true );
 | 
						|
			}
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( true );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
		//Test SQL injection with ";" and "--"
 | 
						|
		try {
 | 
						|
			$sort_arr = [
 | 
						|
					'; (SELECT 1)-- .id.' => 1,
 | 
						|
			];
 | 
						|
			$utlf->getAPISearchByCompanyIdAndArrayCriteria( 1, [], null, null, null, $sort_arr );
 | 
						|
 | 
						|
			//var_dump( $utlf->rs->sql );
 | 
						|
			if ( stripos( $utlf->rs->sql, '(SELECT 1)' ) !== false ) {
 | 
						|
				$this->assertTrue( false );
 | 
						|
			} else {
 | 
						|
				$this->assertTrue( true );
 | 
						|
			}
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( true );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
		//FIXME: Test around the WHERE clause, even though no user input should ever get to it.
 | 
						|
//		$pslf = TTnew('PayStubListFactory');
 | 
						|
//		//$pslf->getByCompanyId( 1, 1, NULL, ( array('a.start_date' => ">= '". $pslf->db->BindTimeStamp( TTDate::getBeginDayEpoch( time() - (86400 * 30) ) )."'") ) );
 | 
						|
//		$pslf->getByCompanyId( 1, 1, NULL, ( array('a.start_date >=' => $pslf->db->BindTimeStamp( TTDate::getBeginDayEpoch( time() - (86400 * 30) ) ) ) ) );
 | 
						|
//		//$pslf->getByCompanyId( 1, 1, NULL, ( array('a.created_date' => "=1-- (SELECT 1)--") ) );
 | 
						|
//		var_dump( $pslf->rs->sql );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Test SQL Injection in the sort query.
 | 
						|
	 * @group SQL_SQLInjectionA
 | 
						|
	 */
 | 
						|
    function testSortSQLInjectionA() {
 | 
						|
		$injected_payload = [ "(CASE WHEN (SELECT lo_import('/etc/hosts'))='1' THEN a.created_date ELSE a.updated_date END) ASC --" => 'ASC' ];
 | 
						|
 | 
						|
		//Reflection class required to test as getSortSQL is a protected function.
 | 
						|
		$uf = new UserFactory();
 | 
						|
		$class = new ReflectionClass($uf);
 | 
						|
		$method = $class->getMethod('getSortSQL');
 | 
						|
		$method->setAccessible(true);
 | 
						|
 | 
						|
		//SQL Payload completely rejected when strict is true.
 | 
						|
		try {
 | 
						|
			$method->invokeArgs($uf, [ $injected_payload, true ] );
 | 
						|
			$this->assertTrue( false );
 | 
						|
		} catch (Exception $e) {
 | 
						|
			$this->assertTrue( true );
 | 
						|
		}
 | 
						|
 | 
						|
		//SQL Payload filtered when strict is false.
 | 
						|
		$this->assertTrue( $method->invokeArgs( $uf, [ $injected_payload, false ] ) === ' ORDER BY (CASE WHEN (SELECT lo_import(/etc/hosts))=1 THEN a.created_date ELSE a.updated_date END) ASC  ASC' );
 | 
						|
		$this->assertTrue( $method->invokeArgs( $uf, [ [ 'safe"$$;-$-' . "'_characters" => 'ASC' ], false ] ) === ' ORDER BY safe_characters ASC' );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Used to call protected methods in the Factory class.
 | 
						|
	 * @param $name
 | 
						|
	 * @return ReflectionMethod
 | 
						|
	 */
 | 
						|
	protected static function getMethod( $name ) {
 | 
						|
		$class = new ReflectionClass( 'UserListFactory' );
 | 
						|
		$method = $class->getMethod( $name );
 | 
						|
		$method->setAccessible( true );
 | 
						|
 | 
						|
		return $method;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_testWhereClauseSQL
 | 
						|
	 */
 | 
						|
	function testWhereClauseSQL() {
 | 
						|
		$method = self::getMethod( 'getWhereClauseSQL' );
 | 
						|
		$ulf = new UserListFactory();
 | 
						|
 | 
						|
 | 
						|
		//Boolean TRUE
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', (bool)true, 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 1, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', (int)1, 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 1, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', 1, 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 1, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', 1.00, 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 1, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', '1', 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 1, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', 'TRUE', 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 1, $ph[0] );
 | 
						|
 | 
						|
 | 
						|
		//Boolean FALSE
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', (bool)false, 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 0, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', (int)0, 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 0, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', 0, 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 0, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', 0.00, 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 0, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', '0', 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 0, $ph[0] );
 | 
						|
 | 
						|
		$ph = [];
 | 
						|
		$retval = $method->invokeArgs( $ulf, [ 'a.private', 'FALSE', 'boolean', &$ph ] );
 | 
						|
		$this->assertEquals( ' AND a.private = ? ', $retval );
 | 
						|
		$this->assertEquals( 0, $ph[0] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_testTransactionNestingA
 | 
						|
	 */
 | 
						|
	function testTransactionNestingA() {
 | 
						|
		$uf = new UserFactory();
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 1, $uf->db->transOff );
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 2, $uf->db->transOff );
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 3, $uf->db->transOff );
 | 
						|
 | 
						|
		//$uf->FailTransaction();
 | 
						|
		$uf->CommitTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 2, $uf->db->transOff );
 | 
						|
 | 
						|
		$uf->CommitTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 1, $uf->db->transOff );
 | 
						|
 | 
						|
		$uf->CommitTransaction();
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_testTransactionNestingB
 | 
						|
	 */
 | 
						|
	function testTransactionNestingB() {
 | 
						|
		$uf = new UserFactory();
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 1, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 2, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 3, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->FailTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 3, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->CommitTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 2, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->CommitTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 1, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->CommitTransaction();
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_testTransactionNestingC
 | 
						|
	 */
 | 
						|
	function testTransactionNestingC() {
 | 
						|
		$uf = new UserFactory();
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 1, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 2, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 3, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->FailTransaction();
 | 
						|
		$this->assertEquals( 1, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 3, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
 | 
						|
		$uf->CommitTransaction( true ); //Unest all transactions.
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_testTransactionNestingC2
 | 
						|
	 */
 | 
						|
	function testTransactionNestingC2() {
 | 
						|
		$uf = new UserFactory();
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->FailTransaction();
 | 
						|
 | 
						|
		$uf->CommitTransaction( true ); //Unest all transactions.
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_testTransactionNestingC3
 | 
						|
	 */
 | 
						|
	function testTransactionNestingC3() {
 | 
						|
		$uf = new UserFactory();
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( true, $uf->db->_transOK );
 | 
						|
 | 
						|
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->StartTransaction();
 | 
						|
		$uf->FailTransaction();
 | 
						|
		$uf->FailTransaction();
 | 
						|
		$uf->FailTransaction();
 | 
						|
 | 
						|
		$uf->CommitTransaction( true ); //Unest all transactions.
 | 
						|
 | 
						|
		$this->assertEquals( 0, $uf->db->transCnt );
 | 
						|
		$this->assertEquals( 0, $uf->db->transOff );
 | 
						|
		$this->assertEquals( false, $uf->db->_transOK );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @group SQL_testMaximumQueryLengthA
 | 
						|
	 */
 | 
						|
	function testMaximumQueryLengthA() {
 | 
						|
		$ulf = new UserListFactory();
 | 
						|
 | 
						|
		//Build list of many UUIDs.
 | 
						|
		for( $i = 0; $i < 500000; $i++ ) { //65535 appears to be the max number of parameters for a WHERE IN clause in PostgreSQL.
 | 
						|
			$ids[] = TTUUID::generateUUID();
 | 
						|
		}
 | 
						|
 | 
						|
		//This should fail with an exception.
 | 
						|
		try {
 | 
						|
			$ulf->getByIdAndCompanyId( $ids, TTUUID::getZeroID() );
 | 
						|
			//$this->assertTrue( false, $ulf->getRecordCount() ); //This would trigger an asserting exception that would be caught in the catch() below.
 | 
						|
			$this->assertEquals( 0, $ulf->getRecordCount() ); //Now that we automatically convert UUID arrays to strings without placeholders in getListSQL(), we can exceed the 65535 limit.
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( true, $e->getMessage() );
 | 
						|
			$this->assertGreaterThan( 10, strlen( $e->getMessage() ) );
 | 
						|
			$this->assertEquals( -1, $e->getCode() );
 | 
						|
		}
 | 
						|
 | 
						|
		//This should succeed with still a very high number.
 | 
						|
		$ids = array_slice( $ids, 0, 5000 );
 | 
						|
		try {
 | 
						|
			$ulf->getByIdAndCompanyId( $ids, TTUUID::getZeroID() );
 | 
						|
			$this->assertTrue( true, $ulf->getRecordCount() );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			$this->assertTrue( false, $e->getMessage() );
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
}
 |