[ 'db_name' => 'corelibs_db_io_test', 'db_user' => 'corelibs_db_io_test', 'db_pass' => 'corelibs_db_io_test', 'db_host' => 'localhost', 'db_port' => 5432, 'db_schema' => 'public', 'db_type' => 'pgsql', 'db_encoding' => '', 'db_ssl' => 'allow', // allow, disable, require, prefer 'db_debug' => true, ], // same as valid, but db debug is off 'valid_debug_false' => [ 'db_name' => 'corelibs_db_io_test', 'db_user' => 'corelibs_db_io_test', 'db_pass' => 'corelibs_db_io_test', 'db_host' => 'localhost', 'db_port' => 5432, 'db_schema' => 'public', 'db_type' => 'pgsql', 'db_encoding' => '', 'db_ssl' => 'allow', // allow, disable, require, prefer 'db_debug' => false, ], // same as valid, but encoding is set 'valid_with_encoding_utf8' => [ 'db_name' => 'corelibs_db_io_test', 'db_user' => 'corelibs_db_io_test', 'db_pass' => 'corelibs_db_io_test', 'db_host' => 'localhost', 'db_port' => 5432, 'db_schema' => 'public', 'db_type' => 'pgsql', 'db_encoding' => 'UTF-8', 'db_ssl' => 'allow', // allow, disable, require, prefer 'db_debug' => true, ], // invalid (missing db name) 'invalid' => [ 'db_name' => '', 'db_user' => '', 'db_pass' => '', 'db_host' => '', 'db_port' => 5432, 'db_schema' => 'public', 'db_type' => 'pgsql', 'db_encoding' => '', 'db_ssl' => 'allow', // allow, disable, require, prefer 'db_debug' => true, ], ]; private static $log; /** * Test if pgsql module loaded * Check if valid DB connection works * Check if tables exist and remove them * Create test tables * * @return void */ public static function setUpBeforeClass(): void { if (!extension_loaded('pgsql')) { self::markTestSkipped( 'The PgSQL extension is not available.' ); } // define basic connection set valid and one invalid self::$log = new \CoreLibs\Debug\Logging([ // 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log', 'log_folder' => DIRECTORY_SEPARATOR . 'tmp', 'file_id' => 'CoreLibs-DB-IO-Test', 'debug_all' => false, 'echo_all' => false, 'print_all' => false, ]); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); if (!$db->dbGetConnectionStatus()) { self::markTestSkipped( 'Cannot connect to valid Test DB.' ); } // check if they already exist, drop them if ($db->dbShowTableMetaData('table_with_primary_key') !== false) { $db->dbExec("DROP TABLE table_with_primary_key"); $db->dbExec("DROP TABLE table_without_primary_key"); $db->dbExec("DROP TABLE test_meta"); } $base_table = "uid VARCHAR, " // uid is for internal reference tests . "row_int INT, " . "row_numeric NUMERIC, " . "row_varchar VARCHAR, " . "row_varchar_literal VARCHAR, " . "row_json JSON, " . "row_jsonb JSONB, " . "row_bytea BYTEA, " . "row_timestamp TIMESTAMP WITHOUT TIME ZONE, " . "row_date DATE, " . "row_interval INTERVAL, " . "row_array_int INT ARRAY, " . "row_array_varchar VARCHAR ARRAY" . ") WITHOUT OIDS"; // create the tables $db->dbExec( "CREATE TABLE table_with_primary_key (" // primary key name is table + '_id' . "table_with_primary_key_id SERIAL PRIMARY KEY, " . $base_table ); $db->dbExec( "CREATE TABLE table_without_primary_key (" . $base_table ); // create simple table for meta test $db->dbExec( "CREATE TABLE test_meta (" . "row_1 VARCHAR, " . "row_2 INT" . ") WITHOUT OIDS" ); // end connection $db->dbClose(); } /** * Check that we can actually do these tests * * @return void */ protected function setUp(): void { // print_r(self::$db_config); } // - connected version test // dbVerions, dbCompareVersion /** * Returns test list for dbCompareVersion check * * @return array */ public function versionProvider(): array { return [ 'compare = ok' => [ '=13.5.0', true ], 'compare = bad' => [ '=9.2.0', false ], 'compare < ok' => [ '<20.0.0', true ], 'compare < bad' => [ '<9.2.0', false ], 'compare <= ok a' => [ '<=20.0.0', true ], 'compare <= ok b' => [ '<=13.5.0', true ], 'compare <= false' => [ '<=9.2.0', false ], 'compare > ok' => [ '>9.2.0', true ], 'compare > bad' => [ '>20.2.0', false ], 'compare >= ok a' => [ '>=13.5.0', true ], 'compare >= ok b' => [ '>=9.2.0', true ], 'compare >= bad' => [ '>=20.0.0', false ], ]; } /** * NOTE * Version tests will fail if versions change * Current base as Version 13.5 for equal check * I can't mock a function on the same class when it is called in a method * NOTE * * @covers ::dbCompareVersion * @dataProvider versionProvider * @testdox Version $input compares as $expected [$_dataName] * * @return void */ public function testDbVerson(string $input, bool $expected): void { // connect to valid DB $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); // print "DB VERSION: " . $db->dbVersion() . "\n"; // TODO: Mock \CoreLibs\DB\SQL\PgSQL somehow or Mock \CoreLibsDB\IO::dbVersion // Create a stub for the SomeClass class. // $stub = $this->createMock(\CoreLibs\DB\IO::class); // $stub->method('dbVersion') // ->willReturn('13.1.0'); // print "DB: " . $stub->dbVersion() . "\n"; // print "TEST: " . ($stub->dbCompareVersion('=13.1.0') ? 'YES' : 'NO') . "\n"; // print "TEST: " . ($stub->dbCompareVersion('=13.5.0') ? 'YES' : 'NO') . "\n"; // $mock = $this->getMockBuilder(CoreLibs\DB\IO::class) // ->addMethods(['dbVersion']) // ->ge‌​tMock(); $this->assertEquals( $expected, $db->dbCompareVersion($input) ); // print "IT HAS TO BE 13.1.0: " . $stub->dbVersion() . "\n"; } // - connect to DB test (dbGetConnectionStatus) // - connected get dbInfo data check (show true, false) // - disconnect: dbClose /** * connection DB strings list with info blocks for connection testing * * @return array */ public function connectionProvider(): array { // 0: connection array // 1: status after connection // 2: info string // 3: ??? return [ 'invalid connection' => [ self::$db_config['invalid'], false, "-DB-info-> Connected to db '' with schema 'public' as user " . "'' at host '' on port '5432' with ssl mode 'allow' **** " . "-DB-info-> DB IO Class debug output: Yes **** ", null, ], 'valid connection' => [ self::$db_config['valid'], true, "-DB-info-> Connected to db 'corelibs_db_io_test' with " . "schema 'public' as user 'corelibs_db_io_test' at host " . "'localhost' on port '5432' with ssl mode 'allow' **** " . "-DB-info-> DB IO Class debug output: Yes **** ", null, ], ]; } /** * Connection tests and confirmation with info blocks * * @covers ::__connectToDB * @dataProvider connectionProvider * @testdox Connection will be $expected [$_dataName] * * @return void */ public function testConnection( array $connection, bool $expected_status, string $expected_string ): void { $db = new \CoreLibs\DB\IO( $connection, self::$log ); $this->assertEquals( $expected_status, $db->dbGetConnectionStatus(), ); $this->assertEquals( $expected_string, $db->dbInfo(false, true) ); // print "DB: " . $db->dbInfo(false, true) . "\n"; if ($db->dbGetConnectionStatus()) { // db close check $db->dbClose(); $this->assertEquals( false, $db->dbGetConnectionStatus() ); } else { // TODO: error checks // print "LAST ERROR: " . $db->dbGetLastError(true) . "\n"; // print "ERRORS: " . print_r($db->dbGetCombinedErrorHistory(), true) . "\n"; } } // - debug flag sets // dbGetDebug, dbSetDebug, dbToggleDebug /** * test set for setDebug * * @return array */ public function debugSetProvider(): array { return [ 'default debug set' => [ // what base connection 'valid', // actions (set) null, // set exepected self::$db_config['valid']['db_debug'], ], 'set debug to true' => [ 'valid_debug_false', true, true, ], 'set debug to false' => [ 'valid', false, false, ] ]; } /** * test set for toggleDEbug * * @return array */ public function debugToggleProvider(): array { return [ 'default debug set' => [ // what base connection 'valid', // actions null, // toggle is inverse self::$db_config['valid']['db_debug'] ? false : true, ], 'toggle debug to true' => [ 'valid_debug_false', true, true, ], 'toggle debug to false' => [ 'valid', false, false, ] ]; } /** * Test dbSetDbug, dbGetDebug * * @covers ::dbGetDbug * @covers ::dbSetDebug * @dataProvider debugSetProvider * @testdox Setting debug $set will be $expected [$_dataName] * * @return void */ public function testDbSetDebug( string $connection, ?bool $set, bool $expected, ): void { $db = new \CoreLibs\DB\IO( self::$db_config[$connection], self::$log ); $this->assertEquals( $expected, $set === null ? $db->dbSetDebug() : $db->dbSetDebug($set) ); // must always match $this->assertEquals( $expected, $db->dbGetDebug() ); $db->dbClose(); } /** * Test dbToggleDebug, dbGetDebug * * @covers ::dbGetDbug * @covers ::dbSetDebug * @dataProvider debugToggleProvider * @testdox Toggle debug $toggle will be $expected [$_dataName] * * @return void */ public function testDbToggleDebug( string $connection, ?bool $toggle, bool $expected, ): void { $db = new \CoreLibs\DB\IO( self::$db_config[$connection], self::$log ); $this->assertEquals( $expected, $toggle === null ? $db->dbToggleDebug() : $db->dbToggleDebug($toggle) ); // must always match $this->assertEquals( $expected, $db->dbGetDebug() ); $db->dbClose(); } // - set max query call sets // dbSetMaxQueryCall, dbGetMaxQueryCall /** * test list for max query run settings * * @return array */ public function maxQueryCallProvider(): array { // 0: max call number // 1: expected flag from set call // 2: expected number from get call // 3: expected last warning id // 4: expected last error id return [ 'set default' => [ null, true, \CoreLibs\DB\IO::DEFAULT_MAX_QUERY_CALL, // expected warning '', // expected error '', ], 'set to -1 with warning' => [ -1, true, -1, // warning 50 '50', '', ], 'set to 0 with error' => [ 0, false, \CoreLibs\DB\IO::DEFAULT_MAX_QUERY_CALL, '', '51', ], 'set to -2 with error' => [ -2, false, \CoreLibs\DB\IO::DEFAULT_MAX_QUERY_CALL, '', '51', ], 'set to valid value' => [ 10, true, 10, '', '', ] ]; } /** * Test max query call set and get flow with warging/errors * * @covers ::dbSetMaxQueryCall * @covers ::dbGetMaxQueryCall * @dataProvider maxQueryCallProvider * @testdox Set max query call with $max_calls out with $expected_flag and $expected_max_calls (Warning: $warning/Error: $error) [$_dataName] * * @param integer|null $max_calls * @param boolean $expected_flag * @param integer $expected_max_calls * @param string $warning * @param string $error * @return void */ public function testMaxQueryCall( ?int $max_calls, bool $expected_flag, int $expected_max_calls, string $warning, string $error ): void { $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); $this->assertEquals( $expected_flag, // TODO special test with null call too $max_calls === null ? $db->dbSetMaxQueryCall() : $db->dbSetMaxQueryCall($max_calls) ); $this->assertEquals( $expected_max_calls, $db->dbGetMaxQueryCall() ); // if string for warning or error is not empty check $this->assertEquals( $warning, $db->dbGetLastWarning() ); $this->assertEquals( $error, $db->dbGetLastError() ); $db->dbClose(); } // - set and get schema // dbGetSchema, dbSetSchema, // TODO: schema set/get test // - encoding settings (exclude encoding test, just set) // dbGetEncoding, dbSetEncoding /** * test encoding change list for dbSetEncoding * * @return array */ public function encodingProvider(): array { // 0: connection // 1: set encoding // 2: expected return from set // 2: expected to get return [ 'default set no encoding' => [ 'valid', '', false, // I expect that the default DB is set to UTF8 'UTF8' ], 'set to Shift JIS' => [ 'valid', 'ShiftJIS', true, 'SJIS' ], // 'set to Invalid' => [ // 'valid', // 'Invalid', // false, // 'UTF8' // ], // other tests includ perhaps mocking for error? // TODO actual data check ]; } /** * change DB encoding, only function set test, not test of encoding change * TODO: add encoding changed test with DB insert * * @covers ::dbSetEncoding * @covers ::dbGetEncoding * @dataProvider encodingProvider * @testdox Set encoding on $connection to $set_encoding expect $expected_set_flag and $expected_get_encoding [$_dataName] * * @param string $connection * @param string $set_encoding * @param boolean $expected_set_flag * @param string $expected_get_encoding * @return void */ public function testEncoding( string $connection, string $set_encoding, bool $expected_set_flag, string $expected_get_encoding ): void { $db = new \CoreLibs\DB\IO( self::$db_config[$connection], self::$log ); $this->assertEquals( $expected_set_flag, $db->dbSetEncoding($set_encoding) ); $this->assertEquals( $expected_get_encoding, $db->dbGetEncoding() ); $db->dbClose(); } // - all general data from connection array // dbGetSetting (name, user, ecnoding, schema, host, port, ssl, debug, password) /** * returns ALL connections sets as a group with * conneciton name on pos 0 and the connection settings on pos 1 * * @return array */ public function connectionCompleteProvider(): array { $connections = []; foreach (self::$db_config as $connection => $settings) { $connections['DB Connection: ' . $connection] = [ $connection, $settings, ]; } return $connections; } /** * Test connection array settings return call * * @covers ::dbGetSetting * @dataProvider connectionCompleteProvider * @testdox Get settings for connection $connection [$_dataName] * * @param string $connection, * @param array $settings * @return void */ public function testGetSetting(string $connection, array $settings): void { $db = new \CoreLibs\DB\IO( self::$db_config[$connection], self::$log ); // each must match foreach ( [ 'name' => 'db_name', 'user' => 'db_user', 'encoding' => 'db_encoding', 'schema' => 'db_schema', 'host' => 'db_host', 'port' => 'db_port', 'ssl' => 'db_ssl', 'debug' => 'db_debug', 'password' => '***', ] as $read => $compare ) { $this->assertEquals( $read == 'password' ? $compare : $settings[$compare], $db->dbGetSetting($read) ); } $db->dbClose(); } // - test boolean convert // dbBoolean /** * test list for dbBoolean * * @return array */ public function booleanProvider(): array { // 0: set // 1: reverse flag // 2: expected return [ 'source "t" to true' => [ 't', false, true, ], 'source "t" to true null flag' => [ 't', null, true, ], 'source "true" to true' => [ 'true', false, true, ], 'source "f" to false' => [ 'f', false, false, ], 'source "false" to false' => [ 'false', false, false, ], 'source anything to true' => [ 'something', false, true, ], 'source empty to false' => [ '', false, false, ], 'source bool true to "t"' => [ true, true, 't', ], 'source bool false to "f"' => [ false, true, 'f', ], ]; } /** * Undocumented function * * @covers ::dbBoolean * @dataProvider booleanProvider * @testdox Have $source and convert ($reverse) to $expected [$_dataName] * * @param string|bool $source * @param bool|null $reverse * @param string|bool $expected * @return void */ public function testDbBoolean($source, ?bool $reverse, $expected): void { $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); $this->assertEquals( $expected, $reverse === null ? $db->dbBoolean($source) : $db->dbBoolean($source, $reverse) ); $db->dbClose(); } // - test interval/age string conversion to // \CoreLibs\Combined\DateTime::stringToTime/timeStringFormat compatbile // dbTimeFormat /** * test list for timestamp parsers * * @return array */ public function timeFormatProvider(): array { // 0: set // 1: micro seconds flag // 2: expected return [ 'interval a' => [ '41 years 9 mons 18 days', false, '41 years 9 mons 18 days' ], 'interval a null micro time' => [ '41 years 9 mons 18 days', null, '41 years 9 mons 18 days' ], 'interval a-1' => [ '41 years 9 mons 18 days 12:31:11', false, '41 years 9 mons 18 days 12h 31m 11s' ], 'interval a-2' => [ '41 years 9 mons 18 days 12:31:11.87418', false, '41 years 9 mons 18 days 12h 31m 11s' ], 'interval a-2-1' => [ '41 years 9 mons 18 days 12:31:11.87418', true, '41 years 9 mons 18 days 12h 31m 11s 87418ms' ], 'interval a-3' => [ '41 years 9 mons 18 days 12:00:11', false, '41 years 9 mons 18 days 12h 11s' ], 'interval b' => [ '1218 days', false, '1218 days' ], 'interval c' => [ '1 year 1 day', false, '1 year 1 day' ], 'interval d' => [ '12:00:05', false, '12h 5s' ], 'interval e' => [ '00:00:00.12345', true, '12345ms' ], 'interval e-1' => [ '00:00:00', true, '0s' ], 'interval a (negative)' => [ '-41 years 9 mons 18 days 00:05:00', false, '-41 years 9 mons 18 days 5m' ], ]; } /** * Test parsing of interval strings into human readable format * * @covers ::dbTimeFormat * @dataProvider timeFormatProvider * @testdox Have $source and convert ($show_micro) to $expected [$_dataName] * * @param string $source * @param bool|null $show_micro * @param string $expected * @return void */ public function testDbTimeFormat(string $source, ?bool $show_micro, string $expected): void { $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); $this->assertEquals( $expected, $show_micro === null ? $db->dbTimeFormat($source) : $db->dbTimeFormat($source, $show_micro) ); $db->dbClose(); } // - convert PostreSQL arrays into PHP // dbArrayParse /** * test list for array convert * * @return array */ public function arrayProvider(): array { // 0: postgresql array string // 1: php array return [ 'array 1' => [ '{1,2,3,"4 this is shit"}', [1, 2, 3, "4 this is shit"] ], 'array 2' => [ '{{1,2,3},{4,5,6}}', [[1, 2, 3], [4, 5, 6]] ], 'array 3' => [ '{{{1,2},{3}},{{4},{5,6}}}', [[[1, 2], [3]], [[4], [5, 6]]] ], 'array 4' => [ '{dfasdf,"qw,,e{q\"we",\'qrer\'}', ['dfasdf', 'qw,,e{q"we', 'qrer'] ] ]; } /** * test convert PostgreSQL array to PHP array * * @covers ::dbArrayParse * @dataProvider arrayProvider * @testdox Input array string $input to $expected [$_dataName] * * @param string $input * @param array|bool $expected * @return void */ public function testDbArrayParse(string $input, $expected): void { $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); $this->assertEquals( $expected, $db->dbArrayParse($input) ); $db->dbClose(); } // - string escape tests // dbEscapeString, dbEscapeLiteral, dbEscapeIdentifier, /** * test list for string encodig * * @return array */ public function stringProvider(): array { // 0: input // 1: expected string // 2: expected literal // 3: expected identifier return [ 'string normal' => [ 'Foo Bar', 'Foo Bar', '\'Foo Bar\'', '"Foo Bar"', ], 'string quotes' => [ 'Foo \'" Bar', 'Foo \'\'" Bar', '\'Foo \'\'" Bar\'', '"Foo \'"" Bar"', ], 'string backslash' => [ 'Foo \ Bar', 'Foo \ Bar', ' E\'Foo \\\\ Bar\'', '"Foo \ Bar"', ], ]; } /** * Check all string escape functions * NOTE: * This depends on the SETTINGS of the DB * The expected setting is the default encoding setting in PostgreSQL * #backslash_quote = safe_encoding * #escape_string_warning = on * TODO: Load current settings from DB and ajust comapre string * * @covers ::dbEscapeString * @covers ::dbEscapeLiteral * @covers ::dbEscapeIdentifier * @dataProvider stringProvider * @testdox Input string $input to $expected [$_dataName] * * @param string $input * @param string $expected * @return void */ public function testStringEscape( string $input, string $expected_string, string $expected_literal, string $expected_identifier ): void { $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); // print "String: " . $input . "\n"; // print "Escape String: -" . $db->dbEscapeString($input) . "-\n"; // print "Escape Literal: -" . $db->dbEscapeLiteral($input) . "-\n"; // print "Escape Identifier: -" . $db->dbEscapeIdentifier($input) . "-\n"; $this->assertEquals( $expected_string, $db->dbEscapeString($input) ); $this->assertEquals( $expected_literal, $db->dbEscapeLiteral($input) ); $this->assertEquals( $expected_identifier, $db->dbEscapeIdentifier($input) ); $db->dbClose(); } // - bytea encoding // dbEscapeBytea /** * test bytea encoding list * * @return array */ public function byteaProvider(): array { // 0: string in // 1: bytea expected return [ 'standard empty string' => [ '', '\x' ], 'random values' => [ '""9f8a!1012938123712378a../%(\'%)"!"#0"#$%\'"#$00"#$0"#0$0"#$', '\x2222396638612131303132393338313233373132333738612e2e2f2528272529222122233022232425272223243030222324302223302430222324' ], 'random text' => [ 'string d', '\x737472696e672064' ] ]; } /** * Test bytea escape * NOTE: * This depends on bytea encoding settings on the server * #bytea_output = 'hex' * * @covers ::dbEscapeBytea * @dataProvider byteaProvider * @testdox Input bytea $input to $expected [$_dataName] * * @param string $input * @param string $expected * @return void */ public function testByteaEscape(string $input, string $expected): void { $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); $this->assertEquals( $expected, $db->dbEscapeBytea($input) ); $db->dbClose(); } // - string escape catcher // dbSqlEscape /** * test list for sql escape function * * @return array */ public function sqlEscapeProvider(): array { // 0: data in // 1: flag // 2: expected output return [ // int (standard) 'integer value' => [1, 'i', 1,], 'bad integer value' => ['za', 'i', 0,], 'empty integer value' => ['', 'i', 'NULL',], 'null integer value' => [null, 'i', 'NULL',], // float (standard) 'float value' => [1.1, 'f', 1.1,], 'bad float value' => ['za', 'f', 0,], 'empty float value' => ['', 'f', 'NULL',], 'null float value' => [null, 'f', 'NULL',], // text (varchar) 'string value' => ['string value', 't', '\'string value\'',], 'empty string value' => ['', 't', '\'\'',], 'null string value' => [null, 't', 'NULL',], // text literal (don't worry about ' around string) 'string value literal' => ['string literal', 'tl', '\'string literal\'',], 'empty string value literal' => ['', 'tl', '\'\'',], 'null string value literal' => [null, 'tl', 'NULL',], // ?d (I have no idea what that does, is like string) 'string value d' => ['string d', 'd', '\'string d\'',], 'empty string value d' => ['', 'd', 'NULL',], 'null string value d' => [null, 'd', 'NULL',], // by bytea 'string value d' => ['string d', 'by', '\x737472696e672064',], 'empty string value d' => ['', 'by', 'NULL',], 'null string value d' => [null, 'by', 'NULL',], // b (bool) 'bool true value' => [true, 'b', '\'t\'',], 'bool false value' => [false, 'b', '\'f\'',], 'empty bool value' => ['', 'b', 'NULL',], 'null bool value' => [null, 'b', 'NULL',], // i2 (integer but with 0 instead of NULL for empty) 'integer2 value' => [1, 'i2', 1,], 'bad integer2 value' => ['za', 'i2', 0,], 'empty integer2 value' => ['', 'i2', 0,], 'null integer2 value' => [null, 'i2', 0,], ]; } /** * Test for the sql escape/null wrapper function * * @covers ::dbSqlEscape * @dataProvider sqlEscapeProvider * @testdox Input value $input as $flag to $expected [$_dataName] * * @param int|float|string|null $input * @param string $flag * @param int|float|string $expected * @return void */ public function testSqlEscape($input, string $flag, $expected): void { $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); $this->assertEquals( $expected, $db->dbSqlEscape($input, $flag) ); $db->dbClose(); } // - show table data // dbShowTableMetaData /** * table meta data return test * * @return array */ public function tableProvider(): array { // 0: table // 1: schema // 2: expected array return [ // disable the default tables, they might change /* 'table with primary key' => [ 'table_with_primary_key', '', [] ], 'table without primary key' => [ 'table_without_primary_key', 'public', [] ], */ 'simple table' => [ 'test_meta', '', [ 'row_1' => [ 'num' => 1, 'type' => 'varchar', 'len' => -1, 'not null' => false, 'has default' => false, 'array dims' => 0, 'is enum' => false, 'is base' => 1, 'is composite' => false, 'is pesudo' => false, 'description' => '', ], 'row_2' => [ 'num' => 2, 'type' => 'int4', 'len' => 4, 'not null' => false, 'has default' => false, 'array dims' => 0, 'is enum' => false, 'is base' => 1, 'is composite' => false, 'is pesudo' => false, 'description' => '', ] ] ], 'simple table null schema' => [ 'test_meta', null, [ 'row_1' => [ 'num' => 1, 'type' => 'varchar', 'len' => -1, 'not null' => false, 'has default' => false, 'array dims' => 0, 'is enum' => false, 'is base' => 1, 'is composite' => false, 'is pesudo' => false, 'description' => '', ], 'row_2' => [ 'num' => 2, 'type' => 'int4', 'len' => 4, 'not null' => false, 'has default' => false, 'array dims' => 0, 'is enum' => false, 'is base' => 1, 'is composite' => false, 'is pesudo' => false, 'description' => '', ] ] ], 'table does not exist' => [ 'non_existing', 'public', false ], ]; } /** * test the table meta data return flow * * @covers ::dbShowTableMetaData * @dataProvider tableProvider * @testdox Check table $table in schema $schema with $expected [$_dataName] * * @param string $table * @param string|null $schema * @param array|bool $expected * @return void */ public function testDbShowTableMetaData(string $table, ?string $schema, $expected): void { $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); // print "TABLE\n" . print_r($db->dbShowTableMetaData($table, $schema), true) . "\n"; $this->assertEquals( $expected, $schema === null ? $db->dbShowTableMetaData($table) : $db->dbShowTableMetaData($table, $schema) ); $db->dbClose(); } // - db exec test for insert/update/select/etc // dbExec, dbResetQueryCalled, dbGetQueryCalled /** * provide queries with return results * * @return array */ public function queryDbExecProvider(): array { // 0: query // 1: optional primary key name, null for empty test // 2: expectes result (bool, object (>=8.1)/resource (<8.1)) // 3: warning // 4: error // 5: run times, not set is once, true is max + 1 return [ // insert 'table with pk insert' => [ 'INSERT INTO table_with_primary_key (row_date) VALUES (NOW())', '', 'resource/object', '', '', ], // insert but null primary key 'table with pk insert null' => [ 'INSERT INTO table_with_primary_key (row_date) VALUES (NOW())', null, 'resource/object', '', '', ], // insert to table with no pk (31?) 'table with no pk insert' => [ 'INSERT INTO table_without_primary_key (row_date) VALUES (NOW())', '', 'resource/object', '', '', ], // INSERT: returning array possible multi insert (32) 'table with pk insert multile' => [ 'INSERT INTO table_with_primary_key (row_date) VALUES' . '(NOW()), ' . '(NOW()), ' . '(NOW()), ' . '(NOW())', '', 'resource/object', '32', '', ], // Skip PK READING 'table with pk insert and NULL pk name' => [ 'INSERT INTO table_with_primary_key (row_date) VALUES (NOW())', 'NULL', 'resource/object', '', '', ], // insert with pk set 'table with pk insert and pk name' => [ 'INSERT INTO table_with_primary_key (row_date) VALUES (NOW())', 'table_with_primary_key_id', 'resource/object', '', '', ], // update 'table with pk update' => [ 'UPDATE table_with_primary_key SET row_date = NOW()', '', 'resource/object', '', '', ], 'table with pk select' => [ 'SELECT * FROM table_with_primary_key', '', 'resource/object', '', '', ], // no query set, error 11 'no query set' => [ '', '', false, '', '11', ], // no db connection setable (16) [needs Mocking] // connection busy [async] (41) // same query run too many times (30) 'same query run too many times' => [ 'SELECT row_date FROM table_with_primary_key', '', 'resource/object', '', '30', true, ], // execution failed (13) 'invalid query' => [ 'INVALID', '', false, '', '13' ], // INSERT: cursor invalid for fetch PK (34) [unreachable code] // INSERT: returning has no data (33) // invalid RETURNING columns // NOTE: After an error was encountered, queries after this // will return a true connection busy although it was error // https://bugs.php.net/bug.php?id=36469 // TODO: fix wron error 42 after error insert 'invalid returning' => [ 'INSERT INTO table_with_primary_key (row_date) VALUES (NOW()) RETURNING invalid', '', false, '', '13' ], ]; } /** * pure dbExec checker * does not check __dbPostExec run, this will be done in the dbGet* functions * tests (internal read data post exec group) * * @covers ::dbExec * @covers ::dbGetQueryCalled * @covers ::dbResetQueryCalled * @dataProvider queryDbExecProvider * @testdox dbExec $query and pk $pk_name with $expected_return (Warning: $warning/Error: $error) [$_dataName] * * @param string $query * @param string|null $pk_name * @param object|resource|bool $expected_return * @param string $warning * @param string $error * @param bool $run_many_times * @return void */ public function testDbExec( string $query, ?string $pk_name, $expected_return, string $warning, string $error, bool $run_many_times = false ): void { // self::$log->setLogLevelAll('debug', true); // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); // clear any current query // $db->dbResetQuery(); // if expected result is not a bool // for PHP 8.1 or higher it has to be an object // for anything before PHP 8.1 this has to be a resource if (is_bool($expected_return)) { $this->assertEquals( $expected_return, // supress ANY errors here $pk_name === null ? @$db->dbExec($query) : @$db->dbExec($query, $pk_name) ); $last_warning = $db->dbGetLastWarning(); $last_error = $db->dbGetLastError(); } else { // if PHP or newer, must be Object PgSql\Result if (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { $result = $pk_name === null ? $db->dbExec($query) : $db->dbExec($query, $pk_name); $last_warning = $db->dbGetLastWarning(); $last_error = $db->dbGetLastError(); $this->assertIsObject( $result ); // also check that this is correct instance type $this->assertInstanceOf( 'PgSql\Result', $result ); } else { $this->assertIsResource( $pk_name === null ? $db->dbExec($query) : $db->dbExec($query, $pk_name) ); $last_warning = $db->dbGetLastWarning(); $last_error = $db->dbGetLastError(); } } // if we have more than one run time // re-run same query and then catch error if ($run_many_times) { for ($i = 1; $i <= $db->dbGetMaxQueryCall() + 1; $i++) { $pk_name === null ? $db->dbExec($query) : $db->dbExec($query, $pk_name); } // will fail now $this->assertFalse( $pk_name === null ? $db->dbExec($query) : $db->dbExec($query, $pk_name) ); $last_warning = $db->dbGetLastWarning(); $last_error = $db->dbGetLastError(); // check query called matching $current_count = $db->dbGetQueryCalled($query); $this->assertEquals( $db->dbGetMaxQueryCall() + 1, $current_count ); // reset query called and check again $this->assertEquals( 0, $db->dbResetQueryCalled($query) ); } // if string for warning or error is not empty check $this->assertEquals( $warning, $last_warning ); $this->assertEquals( $error, $last_error ); // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); $db->dbExec("TRUNCATE table_without_primary_key"); // close connection $db->dbClose(); } // - return one database row // dbReturnRow /** * Undocumented function * * @return array */ public function returnRowProvider(): array { $insert_query = "INSERT INTO table_with_primary_key (row_int, uid) VALUES (1, 'A')"; $read_query = "SELECT row_int, uid FROM table_with_primary_key WHERE uid = 'A'"; // 0: query // 1: flag (assoc) // 2: result // 3: warning // 4: error // 5: insert query return [ 'valid select' => [ $read_query, null, [ 'row_int' => 1, 0 => 1, 'uid' => 'A', 1 => 'A' ], '', '', $insert_query, ], 'valid select, assoc only false' => [ $read_query, false, [ 'row_int' => 1, 0 => 1, 'uid' => 'A', 1 => 'A' ], '', '', $insert_query, ], 'valid select, assoc only true' => [ $read_query, true, [ 'row_int' => 1, 'uid' => 'A', ], '', '', $insert_query, ], 'empty select' => [ '', null, false, '', '11', $insert_query, ], 'insert query' => [ $insert_query, null, false, '', '17', $insert_query ], // invalid QUERY ]; } /** * Undocumented function * * @covers ::dbReturnRow * @dataProvider returnRowProvider * @testdox dbReturnRow $query and assoc $flag_assoc with $expected (Warning: $warning/Error: $error) [$_dataName] * * @param string $query * @param bool|null $flag_assoc * @param array|bool $expected * @param string $warning * @param string $error * @param string $insert_data * @return void */ public function testDbReturnRow( string $query, ?bool $flag_assoc, $expected, string $warning, string $error, string $insert_data, ): void { // self::$log->setLogLevelAll('debug', true); // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); // insert data before we can test, from expected array $db->dbExec($insert_data); // compare $this->assertEquals( $expected, $flag_assoc === null ? $db->dbReturnRow($query) : $db->dbReturnRow($query, $flag_assoc) ); // get last error/warnings $last_warning = $db->dbGetLastWarning(); $last_error = $db->dbGetLastError(); // print "ER: " . $last_error . "/" . $last_warning . "\n"; // if string for warning or error is not empty check $this->assertEquals( $warning, $last_warning ); $this->assertEquals( $error, $last_error ); // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); $db->dbExec("TRUNCATE table_without_primary_key"); // close connection $db->dbClose(); } // - return all database rows // dbReturnArray /** * Undocumented function * * @return array */ public function returnArrayProvider(): array { $insert_query = "INSERT INTO table_with_primary_key (row_int, uid) VALUES " . "(1, 'A'), (2, 'B')"; $read_query = "SELECT row_int, uid FROM table_with_primary_key"; // 0: query // 1: flag (assoc) // 2: result // 3: warning // 4: error // 5: insert query return [ 'valid select' => [ $read_query, null, [ [ 'row_int' => 1, 'uid' => 'A', ], [ 'row_int' => 2, 'uid' => 'B', ], ], '', '', $insert_query, ], 'valid select, assoc ' => [ $read_query, false, [ [ 'row_int' => 1, 0 => 1, 'uid' => 'A', 1 => 'A' ], [ 'row_int' => 2, 0 => 2, 'uid' => 'B', 1 => 'B' ], ], '', '', $insert_query, ], 'empty select' => [ '', null, false, '', '11', $insert_query, ], 'insert query' => [ $insert_query, null, false, '', '17', $insert_query ], // invalid QUERY ]; } /** * Undocumented function * * @covers ::dbReturnArray * @dataProvider returnArrayProvider * @testdox dbReturnArray $query and assoc $flag_assoc with $expected (Warning: $warning/Error: $error) [$_dataName] * * @param string $query * @param boolean|null $flag_assoc * @param array|bool $expected * @param string $warning * @param string $error * @param string $insert_data * @return void */ public function testDbReturnArrray( string $query, ?bool $flag_assoc, $expected, string $warning, string $error, string $insert_data, ): void { // self::$log->setLogLevelAll('debug', true); // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); // insert data before we can test, from expected array $db->dbExec($insert_data); // compare $this->assertEquals( $expected, $flag_assoc === null ? $db->dbReturnArray($query) : $db->dbReturnArray($query, $flag_assoc) ); // get last error/warnings $last_warning = $db->dbGetLastWarning(); $last_error = $db->dbGetLastError(); // if string for warning or error is not empty check $this->assertEquals( $warning, $last_warning ); $this->assertEquals( $error, $last_error ); // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); $db->dbExec("TRUNCATE table_without_primary_key"); // close connection $db->dbClose(); } // - loop data return flow // dbReturn, dbCacheReset, dbCursorPos, dbCursorNumRows, dbGetCursorExt /** * Undocumented function * * @return array */ public function dbReturnProvider(): array { $insert_query = "INSERT INTO table_with_primary_key (row_int, uid) VALUES " . "(1, 'A'), (2, 'B')"; $read_query = "SELECT row_int, uid FROM table_with_primary_key"; // 0: read query // 1: reset flag, null for default // 2: assoc flag, null for default // 3: expected return // 4: read first, read all flag // 5: read all check array // 6: warning // 7: error // 8: insert data return [ 'valid select' => [ $read_query, null, null, [ 'row_int' => 1, 0 => 1, 'uid' => 'A', 1 => 'A' ], true, [], '', '', $insert_query ], 'valid select, default cache, assoc only' => [ $read_query, \CoreLibs\DB\IO::USE_CACHE, true, [ 'row_int' => 1, 'uid' => 'A', ], true, [], '', '', $insert_query ], 'empty select' => [ '', null, null, false, true, [], '', '11', $insert_query, ], 'insert query' => [ $insert_query, null, null, false, true, [], '', '17', $insert_query ], // from here on a complex read all full tests 'valid select, full read' => [ $read_query, null, null, [ 'row_int' => 1, 0 => 1, 'uid' => 'A', 1 => 'A' ], false, [], '', '', $insert_query ], ]; } /** * Undocumented function * * @covers ::dbReturn * @covers ::dbCacheReset * @covers ::dbGetCursorExt * @covers ::dbCursorPos * @covers ::dbCursorNumRows * @dataProvider dbReturnProvider * @testdox dbReturn $query and cache $flag_cache and assoc $flag_assoc with $expected (Warning: $warning/Error: $error) [$_dataName] * * @param string $query * @param integer|null $flag_cache * @param boolean|null $flag_assoc * @param array|bool $expected * @param bool $read_first_only * @param array $cursor_ext_checks * @param string $warning * @param string $error * @param string $insert_data * @return void */ public function testDbReturn( string $query, ?int $flag_cache, ?bool $flag_assoc, $expected, bool $read_first_only, array $cursor_ext_checks, string $warning, string $error, string $insert_data, ): void { // self::$log->setLogLevelAll('debug', true); // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); // insert data before we can test, from expected array $db->dbExec($insert_data); // all checks below if ($read_first_only === true) { // simple assert first read, then discard result // compare $this->assertEquals( $expected, $flag_cache === null && $flag_assoc === null ? $db->dbReturn($query) : ($flag_assoc === null ? $db->dbReturn($query, $flag_cache) : $db->dbReturn($query, $flag_cache, $flag_assoc) ) ); // get last error/warnings $last_warning = $db->dbGetLastWarning(); $last_error = $db->dbGetLastError(); // if string for warning or error is not empty check $this->assertEquals( $warning, $last_warning ); $this->assertEquals( $error, $last_error ); } else { // all tests here have valid returns already, error checks not needed // read all, and then do result compare // cursor ext data checks (field names, rows, pos, data) // do cache reset test $data = []; $pos = 0; while ( is_array( $res = $flag_cache === null && $flag_assoc === null ? $db->dbReturn($query) : ($flag_assoc === null ? $db->dbReturn($query, $flag_cache) : $db->dbReturn($query, $flag_cache, $flag_assoc) ) ) ) { $data[] = $res; $pos++; // check cursor pos $this->assertEquals( $pos, $db->dbGetCursorPos($query) ); } // does count match for returned data and the cursor num rows $this->assertEquals( count($data), $db->dbGetCursorNumRows($query) ); // does data match // try get cursor data for non existing, must be null $this->assertNull( $db->dbGetCursorExt($query, 'nonexistingfield') ); // does reset data work, query cursor must be null $db->dbCacheReset($query); $this->assertNull( $db->dbGetCursorExt($query) ); } // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); $db->dbExec("TRUNCATE table_without_primary_key"); // close connection $db->dbClose(); } // - prepared query execute // dbPrepare, dbExecute, dbFetchArray public function preparedProvider(): array { // 0: statement name // 1: query to prepare // 2: primary key name: null for default run // 3: arguments for query (double array 0: for select, 0..n for insert/update) // 4: expected prepare return // 5: prepare warning // 6: prepare error // 7: expected execute return // 8: execute warning // 9: execute error // 10: execute data to check (array) // 11: insert data return [ ]; } // bool|object|resource public function testDbPrepared( string $stm_name, string $query, ?string $pk_name, array $query_data, $expected_prepare, string $warning_prepare, string $error_prepare, $expected_execute, string $warning_execute, string $error_execute, array $excute_data, string $insert_data, ): void { // self::$log->setLogLevelAll('debug', true); // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log ); // insert data before we can test, from expected array $db->dbExec($insert_data); // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); $db->dbExec("TRUNCATE table_without_primary_key"); // close connection $db->dbClose(); } // - db execution tests // dbExecAsync, dbCheckAsync // - encoding conversion on read // dbSetToEncoding, dbGetToEncoding // - data debug // dbDumpData // - internal read data (post exec) // dbGetReturning, dbGetInsertPKName, dbGetInsertPK, dbGetReturningExt, // dbGetReturningArray, dbGetNumRows, dbGetNumFields, // dbGetFieldNames, dbGetQuery, dbGetQueryHash, dbGetDbh // - complex write sets // dbWriteData, dbWriteDataExt // - deprecated tests [no need to test perhaps] // getInsertReturn, getReturning, getInsertPK, getReturningExt, // getCursorExt, getNumRows // - error handling // dbGetCombinedErrorHistory, dbGetLastError, dbGetLastWarning } // __END__