From 6f653c24a02d334e55540d10dec520e65a74e671 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 22 Feb 2023 07:52:51 +0900 Subject: [PATCH] VarSetType rename to SetVarType, add phan/phpstan/phpunit tests Breaking change: VarSetType/VarSetTypeNull classes have been renamed to SetVarType/SetVarTypeNull Added phan/phpstan static checkes and settings Add phpunit tests (in test/phpunit) that are an exact copy from the trunk folder unit tests The phan/phpstan tests have been updated to work with the composer layout config files with static defines needed for certain classes are stored in test/configs (config.php, config.other.php, config.master.php) --- .phan/config.php | 120 + ReadMe.md | 5 +- composer.json | 16 +- phpstan-baseline.neon | 47 + phpstan-bootstrap.php | 12 + phpstan-conditional.php | 21 + phpstan.neon | 42 + phpunit.xml | 6 + ...{VarSetTypeMain.php => SetVarTypeMain.php} | 2 +- .../{VarSetType.php => SetVarType.php} | 22 +- ...{VarSetTypeNull.php => SetVarTypeNull.php} | 22 +- src/DB/IO.php | 2 - src/Template/SmartyExtend.php | 2 +- test/checking/phan.sh | 2 + test/checking/phpstan.sh | 2 + test/checking/phpunit.sh | 44 + test/configs/config.master.php | 312 ++ test/configs/config.other.php | 35 + test/configs/config.php | 78 + test/phpunit/CoreLibsACLLoginTest.php | 2056 ++++++++ .../CoreLibsACLLogin_database_prepare.sh | 52 + test/phpunit/CoreLibsAdminBackendTest.php | 47 + test/phpunit/CoreLibsAdminEditPageTest.php | 47 + test/phpunit/CoreLibsCheckColorsTest.php | 329 ++ test/phpunit/CoreLibsCheckEmailTest.php | 381 ++ test/phpunit/CoreLibsCheckEncodingTest.php | 120 + test/phpunit/CoreLibsCheckFileTest.php | 120 + test/phpunit/CoreLibsCheckPasswordTest.php | 73 + test/phpunit/CoreLibsCheckPhpVersionTest.php | 73 + .../CoreLibsCombinedArrayHandlerTest.php | 862 ++++ test/phpunit/CoreLibsCombinedDateTimeTest.php | 785 +++ test/phpunit/CoreLibsConvertByteTest.php | 280 ++ test/phpunit/CoreLibsConvertColorsTest.php | 420 ++ test/phpunit/CoreLibsConvertEncodingTest.php | 102 + test/phpunit/CoreLibsConvertHtmlTest.php | 188 + test/phpunit/CoreLibsConvertJsonTest.php | 166 + test/phpunit/CoreLibsConvertMathTest.php | 118 + .../CoreLibsConvertMimeAppNameTest.php | 60 + .../phpunit/CoreLibsConvertMimeEncodeTest.php | 101 + .../CoreLibsConvertSetVarTypeNullTest.php | 652 +++ .../phpunit/CoreLibsConvertSetVarTypeTest.php | 632 +++ test/phpunit/CoreLibsConvertStringsTest.php | 261 + test/phpunit/CoreLibsCreateEmailTest.php | 700 +++ test/phpunit/CoreLibsCreateHashTest.php | 212 + test/phpunit/CoreLibsCreateRandomKeyTest.php | 205 + test/phpunit/CoreLibsCreateSessionTest.php | 472 ++ test/phpunit/CoreLibsCreateUidsTest.php | 186 + .../phpunit/CoreLibsDBExtendedArrayIOTest.php | 48 + test/phpunit/CoreLibsDBIOTest.php | 4470 +++++++++++++++++ test/phpunit/CoreLibsDebugFileWriterTest.php | 176 + test/phpunit/CoreLibsDebugLoggingTest.php | 990 ++++ test/phpunit/CoreLibsDebugMemoryUsageTest.php | 119 + test/phpunit/CoreLibsDebugRunningTimeTest.php | 183 + test/phpunit/CoreLibsDebugSupportTest.php | 475 ++ test/phpunit/CoreLibsGetDotEnvTest.php | 162 + test/phpunit/CoreLibsGetSystemTest.php | 221 + .../phpunit/CoreLibsLanguageGetLocaleTest.php | 310 ++ test/phpunit/CoreLibsLanguageL10nTest.php | 1046 ++++ .../CoreLibsOutputFormElementsTest.php | 33 + .../CoreLibsOutputFormGenerateTest.php | 33 + test/phpunit/CoreLibsOutputFormTokenTest.php | 33 + test/phpunit/CoreLibsOutputImageTest.php | 33 + .../phpunit/CoreLibsOutputProgressbarTest.php | 33 + .../CoreLibsACLLogin_database_create_data.sql | 1031 ++++ test/phpunit/dotenv/cannot_read.env | 0 test/phpunit/dotenv/empty.env | 0 test/phpunit/dotenv/test.env | 49 + test/phpunit/includes/create_po.sh | 16 + test/phpunit/includes/locale/en_US-admin.po | 34 + .../phpunit/includes/locale/en_US-frontend.po | 34 + .../locale/en_US/LC_MESSAGES/admin.mo | Bin 0 -> 807 bytes .../locale/en_US/LC_MESSAGES/frontend.mo | Bin 0 -> 834 bytes test/phpunit/includes/locale/ja_JP-admin.po | 35 + .../phpunit/includes/locale/ja_JP-frontend.po | 35 + .../locale/ja_JP/LC_MESSAGES/admin.mo | Bin 0 -> 839 bytes .../locale/ja_JP/LC_MESSAGES/frontend.mo | Bin 0 -> 866 bytes test/phpunit/log/.gitignore | 3 + 77 files changed, 20066 insertions(+), 28 deletions(-) create mode 100644 .phan/config.php create mode 100644 phpstan-baseline.neon create mode 100755 phpstan-bootstrap.php create mode 100644 phpstan-conditional.php create mode 100644 phpstan.neon create mode 100644 phpunit.xml rename src/Convert/Extends/{VarSetTypeMain.php => SetVarTypeMain.php} (99%) rename src/Convert/{VarSetType.php => SetVarType.php} (83%) rename src/Convert/{VarSetTypeNull.php => SetVarTypeNull.php} (84%) create mode 100755 test/checking/phan.sh create mode 100755 test/checking/phpstan.sh create mode 100755 test/checking/phpunit.sh create mode 100644 test/configs/config.master.php create mode 100644 test/configs/config.other.php create mode 100644 test/configs/config.php create mode 100644 test/phpunit/CoreLibsACLLoginTest.php create mode 100755 test/phpunit/CoreLibsACLLogin_database_prepare.sh create mode 100644 test/phpunit/CoreLibsAdminBackendTest.php create mode 100644 test/phpunit/CoreLibsAdminEditPageTest.php create mode 100644 test/phpunit/CoreLibsCheckColorsTest.php create mode 100644 test/phpunit/CoreLibsCheckEmailTest.php create mode 100644 test/phpunit/CoreLibsCheckEncodingTest.php create mode 100644 test/phpunit/CoreLibsCheckFileTest.php create mode 100644 test/phpunit/CoreLibsCheckPasswordTest.php create mode 100644 test/phpunit/CoreLibsCheckPhpVersionTest.php create mode 100644 test/phpunit/CoreLibsCombinedArrayHandlerTest.php create mode 100644 test/phpunit/CoreLibsCombinedDateTimeTest.php create mode 100644 test/phpunit/CoreLibsConvertByteTest.php create mode 100644 test/phpunit/CoreLibsConvertColorsTest.php create mode 100644 test/phpunit/CoreLibsConvertEncodingTest.php create mode 100644 test/phpunit/CoreLibsConvertHtmlTest.php create mode 100644 test/phpunit/CoreLibsConvertJsonTest.php create mode 100644 test/phpunit/CoreLibsConvertMathTest.php create mode 100644 test/phpunit/CoreLibsConvertMimeAppNameTest.php create mode 100644 test/phpunit/CoreLibsConvertMimeEncodeTest.php create mode 100644 test/phpunit/CoreLibsConvertSetVarTypeNullTest.php create mode 100644 test/phpunit/CoreLibsConvertSetVarTypeTest.php create mode 100644 test/phpunit/CoreLibsConvertStringsTest.php create mode 100644 test/phpunit/CoreLibsCreateEmailTest.php create mode 100644 test/phpunit/CoreLibsCreateHashTest.php create mode 100644 test/phpunit/CoreLibsCreateRandomKeyTest.php create mode 100644 test/phpunit/CoreLibsCreateSessionTest.php create mode 100644 test/phpunit/CoreLibsCreateUidsTest.php create mode 100644 test/phpunit/CoreLibsDBExtendedArrayIOTest.php create mode 100644 test/phpunit/CoreLibsDBIOTest.php create mode 100644 test/phpunit/CoreLibsDebugFileWriterTest.php create mode 100644 test/phpunit/CoreLibsDebugLoggingTest.php create mode 100644 test/phpunit/CoreLibsDebugMemoryUsageTest.php create mode 100644 test/phpunit/CoreLibsDebugRunningTimeTest.php create mode 100644 test/phpunit/CoreLibsDebugSupportTest.php create mode 100644 test/phpunit/CoreLibsGetDotEnvTest.php create mode 100644 test/phpunit/CoreLibsGetSystemTest.php create mode 100644 test/phpunit/CoreLibsLanguageGetLocaleTest.php create mode 100644 test/phpunit/CoreLibsLanguageL10nTest.php create mode 100644 test/phpunit/CoreLibsOutputFormElementsTest.php create mode 100644 test/phpunit/CoreLibsOutputFormGenerateTest.php create mode 100644 test/phpunit/CoreLibsOutputFormTokenTest.php create mode 100644 test/phpunit/CoreLibsOutputImageTest.php create mode 100644 test/phpunit/CoreLibsOutputProgressbarTest.php create mode 100644 test/phpunit/database/CoreLibsACLLogin_database_create_data.sql create mode 100644 test/phpunit/dotenv/cannot_read.env create mode 100644 test/phpunit/dotenv/empty.env create mode 100644 test/phpunit/dotenv/test.env create mode 100755 test/phpunit/includes/create_po.sh create mode 100644 test/phpunit/includes/locale/en_US-admin.po create mode 100644 test/phpunit/includes/locale/en_US-frontend.po create mode 100644 test/phpunit/includes/locale/en_US/LC_MESSAGES/admin.mo create mode 100644 test/phpunit/includes/locale/en_US/LC_MESSAGES/frontend.mo create mode 100644 test/phpunit/includes/locale/ja_JP-admin.po create mode 100644 test/phpunit/includes/locale/ja_JP-frontend.po create mode 100644 test/phpunit/includes/locale/ja_JP/LC_MESSAGES/admin.mo create mode 100644 test/phpunit/includes/locale/ja_JP/LC_MESSAGES/frontend.mo create mode 100644 test/phpunit/log/.gitignore diff --git a/.phan/config.php b/.phan/config.php new file mode 100644 index 0000000..cc0ea26 --- /dev/null +++ b/.phan/config.php @@ -0,0 +1,120 @@ + "8.2", + // turn color on (-C) + "color_issue_messages_if_supported" => true, + // If true, missing properties will be created when + // they are first seen. If false, we'll report an + // error message. + "allow_missing_properties" => false, + + // Allow null to be cast as any type and for any + // type to be cast to null. + "null_casts_as_any_type" => false, + + // Backwards Compatibility Checking + 'backward_compatibility_checks' => false, + + // Run a quick version of checks that takes less + // time + "quick_mode" => false, + + // Only emit critical issues to start with + // (0 is low severity, 5 is normal severity, 10 is critical) + "minimum_severity" => 0, + + // enable for dead code check + // this will spill out errors for all methods never called + // use after all is OK to try to find unused code blocks + // ignore recommended: PhanUnreferencedPublicMethod + // "dead_code_detection" => true, + + // default false for include path check + "enable_include_path_checks" => true, + "include_paths" => [ + '.', + // '../test/configs/' + ], + 'ignore_undeclared_variables_in_global_scope' => true, + + "file_list" => [ + "./test/configs/config.php", + // "./test/configs/config.db.php", + // "./test/configs/config.host.php", + // "./test/configs/config.path.php", + "./test/configs/config.other.php", + "./test/configs/config.master.php", + ], + + // A list of directories that should be parsed for class and + // method information. After excluding the directories + // defined in exclude_analysis_directory_list, the remaining + // files will be statically analyzed for errors. + // + // Thus, both first-party and third-party code being used by + // your application should be included in this list. + 'directory_list' => [ + // Change this to include the folders you wish to analyze + // (and the folders of their dependencies) + 'src', + // To speed up analysis, we recommend going back later and + // limiting this to only the vendor/ subdirectories your + // project depends on. + // `phan --init` will generate a list of folders for you + 'vendor/egrajp/smarty-extended', + ], + + + // A list of directories holding code that we want + // to parse, but not analyze + "exclude_analysis_directory_list" => [ + 'vendor/egrajp/smarty-extended', + ], + 'exclude_file_list' => [ + ], + + // what not to show as problem + 'suppress_issue_types' => [ + // 'PhanUndeclaredMethod', + 'PhanEmptyFile', + // ignore unreferences public methods, etc here (for dead code check) + 'PhanUnreferencedPublicMethod', + 'PhanUnreferencedClass', + 'PhanWriteOnlyPublicProperty', + 'PhanUnreferencedConstant', + 'PhanWriteOnlyPublicProperty', + 'PhanReadOnlyPublicProperty' + ], + + // Override to hardcode existence and types of (non-builtin) globals in the global scope. + // Class names should be prefixed with `\`. + // + // (E.g. `['_FOO' => '\FooClass', 'page' => '\PageClass', 'userId' => 'int']`) + 'globals_type_map' => [], +]; diff --git a/ReadMe.md b/ReadMe.md index d076adb..77fb6d3 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -4,7 +4,10 @@ This is just the lib/CoreLibs folder in a composer package. For local install only -**Note**: for Template\SmartyExtended the `egrajp/smarty-extended` has to be installed +**Note**: for following classes the `egrajp/smarty-extended` has to be installed + +- Template\SmartyExtended +- Admin\EditBase ## Setup from central composer diff --git a/composer.json b/composer.json index 45cb174..2f0480f 100644 --- a/composer.json +++ b/composer.json @@ -15,5 +15,19 @@ } ], "minimum-stability": "dev", - "require": {} + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpstan/phpstan": "1.10.x-dev", + "phan/phan": "v5.x-dev", + "phpunit/phpunit": "^9", + "egrajp/smarty-extended": "^4.3" + }, + "repositories": { + "git.egplusww.jp.Composer": { + "type": "composer", + "url": "https://git.egplusww.jp/api/packages/Composer/composer" + } + } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..5413dc2 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,47 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$connection of function pg_escape_bytea expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + + - + message: "#^Parameter \\#1 \\$connection of function pg_escape_identifier expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + + - + message: "#^Parameter \\#1 \\$connection of function pg_escape_literal expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + + - + message: "#^Parameter \\#1 \\$connection of function pg_escape_string expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + + - + message: "#^Parameter \\#1 \\$connection of function pg_execute expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + + - + message: "#^Parameter \\#1 \\$connection of function pg_parameter_status expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + + - + message: "#^Parameter \\#1 \\$connection of function pg_prepare expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + + - + message: "#^Parameter \\#1 \\$connection of function pg_query expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + + - + message: "#^Parameter \\#1 \\$connection of function pg_query_params expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#" + count: 1 + path: src/DB/SQL/PgSQL.php + diff --git a/phpstan-bootstrap.php b/phpstan-bootstrap.php new file mode 100755 index 0000000..3d16522 --- /dev/null +++ b/phpstan-bootstrap.php @@ -0,0 +1,12 @@ += 8_00_00) { + // Change of signature in PHP 8.1 + /* $config['parameters']['ignoreErrors'][] = [ + 'message' => '~Parameter #1 \$(result|connection) of function pg_\w+ ' + . 'expects resource(\|null)?, object\|resource given\.~', + 'path' => 'www/lib/CoreLibs/DB/SQL/PgSQL.php', + // 'count' => 1, + ]; */ +} + +return $config; + +// __END_ diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..64368b0 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,42 @@ +# PHP Stan Config +includes: + - phpstan-conditional.php +parameters: + tmpDir: /tmp/phpstan-corelibs-composer + level: 8 # max is now 9 + checkMissingCallableSignature: true + treatPhpDocTypesAsCertain: false + paths: + - %currentWorkingDirectory%/src + bootstrapFiles: + - %currentWorkingDirectory%/phpstan-bootstrap.php + - %currentWorkingDirectory%/vendor/autoload.php + scanDirectories: + - vendor/egrajp/smarty-extended + scanFiles: + - test/configs/config.php + - test/configs/config.master.php + - test/configs/config.other.php + # excludePaths: + # ignore composer + # - vendor + # ignore errores with + ignoreErrors: + #- # this error is ignore because of the PHP 8.0 to 8.1 change for pg_*, only for 8.0 or lower + # message: "#^Parameter \\#1 \\$(result|connection) of function pg_\\w+ expects resource(\\|null)?, object\\|resource(\\|bool)? given\\.$#" + # path: %currentWorkingDirectory%/www/lib/CoreLibs/DB/SQL/PgSQL.php + - # this is for 8.1 or newer + message: "#^Parameter \\#1 \\$(result|connection) of function pg_\\w+ expects PgSql\\\\(Result|Connection(\\|string)?(\\|null)?), object\\|resource given\\.$#" + path: %currentWorkingDirectory%/src/DB/SQL/PgSQL.php + # this is ignored for now + # - '#Expression in empty\(\) is always falsy.#' + # - + # message: '#Reflection error: [a-zA-Z0-9\\_]+ not found.#' + # path: www/includes/edit_base.php + #- 'error regex' + #- + # message: 'error regex' + # path: %currentWorkingDirectory%/www/some/* + # paths: + # - ... + # - ... diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..23d3de4 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,6 @@ + + diff --git a/src/Convert/Extends/VarSetTypeMain.php b/src/Convert/Extends/SetVarTypeMain.php similarity index 99% rename from src/Convert/Extends/VarSetTypeMain.php rename to src/Convert/Extends/SetVarTypeMain.php index e1f4382..98ff053 100644 --- a/src/Convert/Extends/VarSetTypeMain.php +++ b/src/Convert/Extends/SetVarTypeMain.php @@ -9,7 +9,7 @@ declare(strict_types=1); namespace CoreLibs\Convert\Extends; -class VarSetTypeMain +class SetVarTypeMain { /** * If input variable is string then returns it, else returns default set diff --git a/src/Convert/VarSetType.php b/src/Convert/SetVarType.php similarity index 83% rename from src/Convert/VarSetType.php rename to src/Convert/SetVarType.php index 8ed140e..49894b8 100644 --- a/src/Convert/VarSetType.php +++ b/src/Convert/SetVarType.php @@ -11,9 +11,9 @@ declare(strict_types=1); namespace CoreLibs\Convert; -use CoreLibs\Convert\Extends\VarSetTypeMain; +use CoreLibs\Convert\Extends\SetVarTypeMain; -class VarSetType extends Extends\VarSetTypeMain +class SetVarType extends Extends\SetVarTypeMain { /** * Check is input is string, if not return default string. @@ -25,7 +25,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function setStr(mixed $val, string $default = ''): string { - return (string)VarSetTypeMain::setStrMain($val, $default, false); + return (string)SetVarTypeMain::setStrMain($val, $default, false); } /** @@ -39,7 +39,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function makeStr(mixed $val, string $default = ''): string { - return (string)VarSetTypeMain::makeStrMain($val, $default, false); + return (string)SetVarTypeMain::makeStrMain($val, $default, false); } /** @@ -52,7 +52,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function setInt(mixed $val, int $default = 0): int { - return (int)VarSetTypeMain::setIntMain($val, $default, false); + return (int)SetVarTypeMain::setIntMain($val, $default, false); } /** @@ -65,7 +65,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function makeInt(mixed $val, int $default = 0): int { - return (int)VarSetTypeMain::makeIntMain($val, $default, false); + return (int)SetVarTypeMain::makeIntMain($val, $default, false); } /** @@ -78,7 +78,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function setFloat(mixed $val, float $default = 0.0): float { - return (float)VarSetTypeMain::setFloatMain($val, $default, false); + return (float)SetVarTypeMain::setFloatMain($val, $default, false); } /** @@ -91,7 +91,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function makeFloat(mixed $val, float $default = 0.0): float { - return (float)VarSetTypeMain::makeFloatMain($val, $default, false); + return (float)SetVarTypeMain::makeFloatMain($val, $default, false); } /** @@ -104,7 +104,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function setArray(mixed $val, array $default = []): array { - return (array)VarSetTypeMain::setArrayMain($val, $default, false); + return (array)SetVarTypeMain::setArrayMain($val, $default, false); } /** @@ -117,7 +117,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function setBool(mixed $val, bool $default = false): bool { - return (bool)VarSetTypeMain::setBoolMain($val, $default, false); + return (bool)SetVarTypeMain::setBoolMain($val, $default, false); } /** @@ -129,7 +129,7 @@ class VarSetType extends Extends\VarSetTypeMain */ public static function makeBool(mixed $val, bool $default = false): bool { - return (bool)VarSetTypeMain::makeBoolMain($val, $default, false); + return (bool)SetVarTypeMain::makeBoolMain($val, $default, false); } } diff --git a/src/Convert/VarSetTypeNull.php b/src/Convert/SetVarTypeNull.php similarity index 84% rename from src/Convert/VarSetTypeNull.php rename to src/Convert/SetVarTypeNull.php index 0fc82e5..6d2c161 100644 --- a/src/Convert/VarSetTypeNull.php +++ b/src/Convert/SetVarTypeNull.php @@ -9,9 +9,9 @@ declare(strict_types=1); namespace CoreLibs\Convert; -use CoreLibs\Convert\Extends\VarSetTypeMain; +use CoreLibs\Convert\Extends\SetVarTypeMain; -class VarSetTypeNull extends Extends\VarSetTypeMain +class SetVarTypeNull extends Extends\SetVarTypeMain { /** * Check is input is string, if not return default string. @@ -23,7 +23,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain */ public static function setStr(mixed $val, ?string $default = null): ?string { - return VarSetTypeMain::setStrMain($val, $default, true); + return SetVarTypeMain::setStrMain($val, $default, true); } /** @@ -37,7 +37,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain */ public static function makeStr(mixed $val, string $default = null): ?string { - return VarSetTypeMain::makeStrMain($val, $default, true); + return SetVarTypeMain::makeStrMain($val, $default, true); } @@ -50,7 +50,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain */ public static function setInt(mixed $val, ?int $default = null): ?int { - return VarSetTypeMain::setIntMain($val, $default, true); + return SetVarTypeMain::setIntMain($val, $default, true); } /** @@ -62,7 +62,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain */ public static function makeInt(mixed $val, int $default = null): ?int { - return VarSetTypeMain::makeIntMain($val, $default, true); + return SetVarTypeMain::makeIntMain($val, $default, true); } /** @@ -74,7 +74,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain */ public static function setFloat(mixed $val, ?float $default = null): ?float { - return VarSetTypeMain::setFloatMain($val, $default, true); + return SetVarTypeMain::setFloatMain($val, $default, true); } /** @@ -86,7 +86,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain */ public static function makeFloat(mixed $val, float $default = null): ?float { - return VarSetTypeMain::makeFloatMain($val, $default, true); + return SetVarTypeMain::makeFloatMain($val, $default, true); } /** @@ -98,7 +98,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain */ public static function setArray(mixed $val, ?array $default = null): ?array { - return VarSetTypeMain::setArrayMain($val, $default, true); + return SetVarTypeMain::setArrayMain($val, $default, true); } /** @@ -110,7 +110,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain */ public static function setBool(mixed $val, ?bool $default = null): ?bool { - return VarSetTypeMain::setBoolMain($val, $default, true); + return SetVarTypeMain::setBoolMain($val, $default, true); } /** @@ -123,7 +123,7 @@ class VarSetTypeNull extends Extends\VarSetTypeMain { // note that the default value here is irrelevant, we return null // on unsetable string var - return VarSetTypeMain::makeBoolMain($val, false, true); + return SetVarTypeMain::makeBoolMain($val, false, true); } } diff --git a/src/DB/IO.php b/src/DB/IO.php index af1668c..4ecef2b 100644 --- a/src/DB/IO.php +++ b/src/DB/IO.php @@ -1851,7 +1851,6 @@ class IO // if cursor exists ... if ($this->cursor_ext[$query_hash]['cursor']) { - /** @phpstan-ignore-next-line claims this is always false, but can be true */ if ($first_call === true) { $this->cursor_ext[$query_hash]['log'][] = 'First call'; // count the rows returned (if select) @@ -3048,7 +3047,6 @@ class IO } else { // find in all inside the array $__arr = array_column($this->insert_id_arr, $key); - /** @phpstan-ignore-next-line [Why is this always true?] */ if (count($__arr)) { return $__arr; } else { diff --git a/src/Template/SmartyExtend.php b/src/Template/SmartyExtend.php index 08bef93..ffbd028 100644 --- a/src/Template/SmartyExtend.php +++ b/src/Template/SmartyExtend.php @@ -453,7 +453,7 @@ class SmartyExtend extends \Smarty $this->DATA['nav_menu_count'] = count($this->DATA['nav_menu']); // messages = ['msg' =>, 'class' => 'error/warning/...'] $this->DATA['messages'] = $cms->messages; - } else { /** @phpstan-ignore-line Because I assume object for phpstan */ + } else { $this->DATA['show_ea_extra'] = false; $this->DATA['ADMIN'] = 0; $this->DATA['nav_menu'] = []; diff --git a/test/checking/phan.sh b/test/checking/phan.sh new file mode 100755 index 0000000..49db58c --- /dev/null +++ b/test/checking/phan.sh @@ -0,0 +1,2 @@ +base="/storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All/"; +vendor/bin/phan --progress-bar -C --analyze-twice diff --git a/test/checking/phpstan.sh b/test/checking/phpstan.sh new file mode 100755 index 0000000..94a6e9b --- /dev/null +++ b/test/checking/phpstan.sh @@ -0,0 +1,2 @@ +base="/storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All/"; +vendor/bin/phpstan diff --git a/test/checking/phpunit.sh b/test/checking/phpunit.sh new file mode 100755 index 0000000..2cb646a --- /dev/null +++ b/test/checking/phpunit.sh @@ -0,0 +1,44 @@ +base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/"; +# -c phpunit.xml +# --testdox +# call with "t" to give verbose testdox output +# SUPPORTED: https://www.php.net/supported-versions.php +# call with php version number to force a certain php version + +opt_testdox=""; +if [ "${1}" = "t" ] || [ "${2}" = "t" ]; then + opt_testdox="--testdox"; +fi; +php_bin=""; +if [ ! -z "${1}" ]; then + case "${1}" in + # "7.3") php_bin="/usr/bin/php7.3 "; ;; + "7.4") php_bin="/usr/bin/php7.4 "; ;; + "8.0") php_bin="/usr/bin/php8.0 "; ;; + "8.1") php_bin="/usr/bin/php8.1 "; ;; + "8.2") php_bin="/usr/bin/php8.2 "; ;; + *) echo "Not support PHP: ${1}"; exit; ;; + esac; +fi; +if [ ! -z "${2}" ] && [ -z "${php_bin}" ]; then + case "${2}" in + # "7.3") php_bin="/usr/bin/php7.3 "; ;; + "7.4") php_bin="/usr/bin/php7.4 "; ;; + "8.0") php_bin="/usr/bin/php8.0 "; ;; + "8.1") php_bin="/usr/bin/php8.1 "; ;; + "8.2") php_bin="/usr/bin/php8.2 "; ;; + *) echo "Not support PHP: ${1}"; exit; ;; + esac; +fi; + +phpunit_call="${php_bin}${base}vendor/bin/phpunit ${opt_testdox} -c ${base}phpunit.xml ${base}4dev/tests/"; + +${phpunit_call}; + +if [ ! -z "${php_bin}" ]; then + echo "CALLED WITH PHP: ${php_bin}"$(${php_bin} --version); +else + echo "Default PHP used: "$(php --version); +fi; + +# __END__ diff --git a/test/configs/config.master.php b/test/configs/config.master.php new file mode 100644 index 0000000..355ab93 --- /dev/null +++ b/test/configs/config.master.php @@ -0,0 +1,312 @@ + +define('PUBLIC_SCHEMA', 'public'); +define('DEV_SCHEMA', 'public'); +define('TEST_SCHEMA', 'public'); +define('LIVE_SCHEMA', 'public'); +define('GLOBAL_DB_SCHEMA', ''); +define('LOGIN_DB_SCHEMA', ''); + +/************* CORE HOST SETTINGS *****************/ +if (file_exists(BASE . CONFIGS . 'config.host.php')) { + require BASE . CONFIGS . 'config.host.php'; +} +if (!isset($SITE_CONFIG)) { + $SITE_CONFIG = []; +} +/************* DB ACCESS *****************/ +if (file_exists(BASE . CONFIGS . 'config.db.php')) { + require BASE . CONFIGS . 'config.db.php'; +} +if (!isset($DB_CONFIG)) { + $DB_CONFIG = []; +} +/************* OTHER PATHS *****************/ +if (file_exists(BASE . CONFIGS . 'config.path.php')) { + require BASE . CONFIGS . 'config.path.php'; +} + +/************* MASTER INIT *****************/ +// live frontend pages +// ** missing live domains ** +// get the name without the port +list($HOST_NAME) = array_pad(explode(':', $_SERVER['HTTP_HOST'], 2), 2, null); +// set HOST name +define('HOST_NAME', $HOST_NAME); +// BAIL ON MISSING MASTER SITE CONFIG +if (!isset($SITE_CONFIG[HOST_NAME]['location'])) { + echo 'Missing SITE_CONFIG entry for: "' . HOST_NAME . '". Contact Administrator'; + exit; +} +// BAIL ON MISSING DB CONFIG: +// we have either no db selction for this host but have db config entries +// or we have a db selection but no db config as array or empty +// or we have a selection but no matching db config entry +if ( + (!isset($SITE_CONFIG[HOST_NAME]['db_host']) && count($DB_CONFIG)) || + (isset($SITE_CONFIG[HOST_NAME]['db_host']) && + // missing DB CONFIG + ((is_array($DB_CONFIG) && !count($DB_CONFIG)) || + !is_array($DB_CONFIG) || + // has DB CONFIG but no match + empty($DB_CONFIG[$SITE_CONFIG[HOST_NAME]['db_host']])) + ) +) { + echo 'No matching DB config found for: "' . HOST_NAME . '". Contact Administrator'; + exit; +} +// set SSL on +$is_secure = false; +if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { + $is_secure = true; +} elseif ( + !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || + !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on' +) { + $is_secure = true; +} +if ($is_secure) { + define('HOST_SSL', true); + define('HOST_PROTOCOL', 'https://'); +} else { + define('HOST_SSL', false); + define('HOST_PROTOCOL', 'http://'); +} +// define the db config set name, the db config and the db schema +define('DB_CONFIG_NAME', $SITE_CONFIG[HOST_NAME]['db_host'] ?? ''); +define('DB_CONFIG', $DB_CONFIG[DB_CONFIG_NAME] ?? []); +// because we can't change constant, but we want to for db debug flag +$GLOBALS['DB_CONFIG_SET'] = DB_CONFIG; +// define('DB_CONFIG_TARGET', SITE_CONFIG[$HOST_NAME]['db_host_target']); +// define('DB_CONFIG_OTHER', SITE_CONFIG[$HOST_NAME]['db_host_other']); +// override for login and global schemas +// where the edit* tables are +// define('LOGIN_DB_SCHEMA', PUBLIC_SCHEMA); +// where global tables are that are used by all schemas (eg queue tables for online, etc) +// define('GLOBAL_DB_SCHEMA', PUBLIC_SCHEMA); +// debug settings, site lang, etc +define('TARGET', $SITE_CONFIG[HOST_NAME]['location'] ?? 'test'); +define('DEBUG', $SITE_CONFIG[HOST_NAME]['debug_flag'] ?? false); +define('SITE_LOCALE', $SITE_CONFIG[HOST_NAME]['site_locale'] ?? DEFAULT_LOCALE); +define('SITE_ENCODING', $SITE_CONFIG[HOST_NAME]['site_encoding'] ?? DEFAULT_ENCODING); +define('LOGIN_ENABLED', $SITE_CONFIG[HOST_NAME]['login_enabled'] ?? false); +define('AUTH', $SITE_CONFIG[HOST_NAME]['auth'] ?? false); +// paths +// define('CSV_PATH', $PATHS[TARGET]['csv_path'] ?? ''); +// define('EXPORT_SCRIPT', $PATHS[TARGET]['perl_bin'] ?? ''); +// define('REDIRECT_URL', $PATHS[TARGET]['redirect_url'] ?? ''); + +// show all errors if debug_all & show_error_handling are enabled +define('SHOW_ALL_ERRORS', true); + +/************* GENERAL PAGE TITLE ********/ +define('G_TITLE', $_ENV['G_TITLE'] ?? ''); + +/************ STYLE SHEETS / JS **********/ +define('ADMIN_STYLESHEET', 'edit.css'); +define('ADMIN_JAVASCRIPT', 'edit.js'); +define('STYLESHEET', $_ENV['STYLESHEET'] ?? 'frontend.css'); +define('JAVASCRIPT', $_ENV['JAVASCRIPT'] ?? 'frontend.js'); + +// anything optional +/************* INTERNAL ******************/ +// any other global definitons in the config.other.php +if (file_exists(BASE . CONFIGS . 'config.other.php')) { + require BASE . CONFIGS . 'config.other.php'; +} + +/************* DEBUG *******************/ +// turn off debug if debug flag is OFF +if (defined('DEBUG') && DEBUG == false) { + $ECHO_ALL = false; + $DEBUG_ALL = false; + $PRINT_ALL = false; + $DB_DEBUG = false; + $ENABLE_ERROR_HANDLING = false; + $DEBUG_ALL_OVERRIDE = false; +} else { + $ECHO_ALL = false; + $DEBUG_ALL = true; + $PRINT_ALL = true; + $DB_DEBUG = true; + $ENABLE_ERROR_HANDLING = false; + $DEBUG_ALL_OVERRIDE = false; +} + +// __END__ diff --git a/test/configs/config.other.php b/test/configs/config.other.php new file mode 100644 index 0000000..3865635 --- /dev/null +++ b/test/configs/config.other.php @@ -0,0 +1,35 @@ +); + +/************* CONVERT *******************/ +// this only needed if the external thumbnail create is used +$paths = [ + '/bin', + '/usr/bin', + '/usr/local/bin', +]; +// find convert +foreach ($paths as $path) { + if ( + file_exists($path . DIRECTORY_SEPARATOR . 'convert') && + is_file($path . DIRECTORY_SEPARATOR . 'convert') + ) { + // image magick convert location + define('CONVERT', $path . DIRECTORY_SEPARATOR . 'convert'); + break; + } +} +unset($paths); + +// __END__ diff --git a/test/configs/config.php b/test/configs/config.php new file mode 100644 index 0000000..df7f3b9 --- /dev/null +++ b/test/configs/config.php @@ -0,0 +1,78 @@ + includes master config +* HISTORY: +*********************************************************************/ + +declare(strict_types=1); + +define('CONFIG_PATH', 'configs' . DIRECTORY_SEPARATOR); +// config path prefix search, start with 0, got down each level __DIR__ has, +// if nothing found -> bail +$CONFIG_PATH_PREFIX = ''; +// base path for loads +$__DIR__PATH = __DIR__ . DIRECTORY_SEPARATOR; +// don't load autoloader twice +$end_autoload = false; +for ( + $dir_pos = 0, $dir_max = count(explode(DIRECTORY_SEPARATOR, __DIR__)); + $dir_pos <= $dir_max; + $dir_pos++ +) { + $CONFIG_PATH_PREFIX .= '..' . DIRECTORY_SEPARATOR; + if ($end_autoload === false) { + /************* AUTO LOADER *******************/ + // composer auto loader, in composer.json file add classmap for lib/: + // "autoload": { + // "classmap": [ + // "lib/" + // ] + // }, + // NOTE: MUST RUN composer dump-autoload if file/class names are + // changed or new ones are added + if ( + is_file( + $__DIR__PATH . $CONFIG_PATH_PREFIX + . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php' + ) + ) { + require $__DIR__PATH . $CONFIG_PATH_PREFIX + . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; + $end_autoload = true; + } + } + /************* MASTER CONFIG *******************/ + if ( + is_file($__DIR__PATH . $CONFIG_PATH_PREFIX . CONFIG_PATH . 'config.master.php') + ) { + // load enviorment file if it exists + \CoreLibs\Get\DotEnv::readEnvFile( + $__DIR__PATH . $CONFIG_PATH_PREFIX . CONFIG_PATH + ); + // load master config file that loads all other config files + require $__DIR__PATH . $CONFIG_PATH_PREFIX . CONFIG_PATH . 'config.master.php'; + break; + } +} +// fail if no base DIR is not set +if (!defined('DIR')) { + exit('Base config could not be loaded'); +} +// find trigger name "admin/" or "frontend/" in the getcwd() folder +foreach (['admin', 'frontend'] as $folder) { + if (strstr(getcwd() ?: '', DIRECTORY_SEPARATOR . $folder)) { + break; + } +} +// if content path is empty, fallback is default +/** @phpstan-ignore-next-line can be empty */ +if (empty($folder)) { + $folder = 'default'; +} +define('CONTENT_PATH', $folder . DIRECTORY_SEPARATOR); + +// __END__ diff --git a/test/phpunit/CoreLibsACLLoginTest.php b/test/phpunit/CoreLibsACLLoginTest.php new file mode 100644 index 0000000..c57528a --- /dev/null +++ b/test/phpunit/CoreLibsACLLoginTest.php @@ -0,0 +1,2056 @@ + __DIR__ . DIRECTORY_SEPARATOR . 'log', + 'log_folder' => DIRECTORY_SEPARATOR . 'tmp', + 'file_id' => 'CoreLibs-ACL-Login-Test', + 'debug_all' => true, + 'echo_all' => false, + 'print_all' => true, + ]); + // test database we need to connect do, if not possible this test is skipped + self::$db = new \CoreLibs\DB\IO( + [ + 'db_name' => $db_name, + 'db_user' => $db_user, + 'db_pass' => $db_password, + 'db_host' => $db_host, + 'db_port' => 5432, + 'db_schema' => 'public', + 'db_type' => 'pgsql', + 'db_encoding' => '', + 'db_ssl' => 'allow', // allow, disable, require, prefer + 'db_debug' => true, + ], + self::$log + ); + // ALWAYS drop DB and RECREATE DB + // dropdb -U corelibs_acl_login_test -h localhost corelibs_acl_login_test + // createdb -U corelibs_acl_login_test -O corelibs_acl_login_test -E utf8 corelibs_acl_login_test; + if (!self::$db->dbGetConnectionStatus()) { + self::markTestSkipped( + 'Cannot connect to valid Test DB for ACL\Login test.' + ); + } + // check that edit_user table exist, I assume if this one does, + // the rest does too + if (!self::$db->dbShowTableMetaData('edit_user') !== false) { + self::markTestIncomplete( + 'Cannot find edit_user table in ACL\Login database for testing' + ); + } + // always disable max query calls + self::$db->dbSetMaxQueryCall(-1); + // insert additional content for testing (locked user, etc) + $queries = [ + "INSERT INTO edit_access_data " + . "(edit_access_id, name, value, enabled) VALUES " + . "((SELECT edit_access_id FROM edit_access WHERE uid = 'AdminAccess'), " + . "'test', 'value', 1)" + ]; + foreach ($queries as $query) { + self::$db->dbExec($query); + } + + // define mandatory constant + // must set + // TARGET + define('TARGET', 'test'); + // LOGIN DB SCHEMA + // define('LOGIN_DB_SCHEMA', ''); + + // SHOULD SET + // PASSWORD_MIN_LENGTH (d9) + // PASSWORD_MAX_LENGTH (d255) + // DEFAULT_ACL_LEVEL (d80) + + // OPT: + // LOGOUT_TARGET + // PASSWORD_CHANGE + // PASSWORD_FORGOT + + // LANG: + // SITE_LOCALE + // DEFAULT_LOCALE + // CONTENT_PATH (domain) + + $_SESSION = []; + global $_SESSION; + } + + /** + * close db + * + * @return void + */ + public static function tearDownAfterClass(): void + { + if (self::$db->dbGetConnectionStatus()) { + self::$db->dbClose(); + } + } + + /** + * Undocumented function + * + * @return array + */ + public function loginProvider(): array + { + // 0[mock] : mock settings/override flag settings + // 1[get] : get array IN + // 2[post] : post array IN + // login_login, login_username, login_password, login_logout + // change_password, pw_username, pw_old_password, pw_new_password, + // pw_new_password_confirm + // 3[session]: override session set + // 4[error] : expected error code, 0 for all ok, 3000 for login page view + // note that 1000 (no db), 2000 (no session) must be tested too + // 5[return] : expected return array, eg login_error code, + // or other info data to match + $tests = [ + 'load, no login' => [ + // error code, only for exceptions + [ + 'page_name' => 'edit_users.php', + ], + [], + [], + [], + 3000, + [ + 'login_error' => 0, + 'error_string' => 'Success: No error', + 'error_string_text' => 'Success: No error', + ], + ], + 'load, no login, ajax flag' => [ + // error code, only for exceptions + [ + 'page_name' => 'edit_users.php', + // for ajax + 'ajax_page' => true, + 'ajax_test_type' => 'parameter', + ], + [], + [], + [], + 3000, + [ + 'login_error' => 0, + 'error_string' => 'Success: No error', + 'error_string_text' => 'Success: No error', + // for ajax + 'action' => 'login', + 'ajax_get_count' => 0, + 'ajax_post_count' => 5, + 'ajax_post_action' => 'login', + ], + ], + 'load, no login, ajax globals' => [ + // error code, only for exceptions + [ + 'page_name' => 'edit_users.php', + // for ajax + 'ajax_page' => true, + 'ajax_test_type' => 'globals', + ], + [], + [], + [], + 3000, + [ + 'login_error' => 0, + 'error_string' => 'Success: No error', + 'error_string_text' => 'Success: No error', + // for ajax + 'action' => 'login', + 'ajax_get_count' => 0, + 'ajax_post_count' => 5, + 'ajax_post_action' => 'login', + ], + ], + 'load, session euid set only, php error' => [ + [ + 'page_name' => 'edit_users.php', + ], + [], + [], + [ + 'EUID' => 1, + ], + 2, + [], + ], + 'load, session euid set, all set' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + ], + [], + [], + [ + 'EUID' => 1, + 'USER_NAME' => '', + 'GROUP_NAME' => '', + 'ADMIN' => 1, + 'GROUP_ACL_LEVEL' => -1, + 'PAGES_ACL_LEVEL' => [], + 'USER_ACL_LEVEL' => -1, + 'UNIT_UID' => [ + 'AdminAccess' => 1, + ], + 'UNIT' => [ + 1 => [ + 'acl_level' => 80, + 'name' => 'Admin Access', + 'uid' => 'AdminAccess', + 'level' => -1, + 'default' => 0, + 'data' => [ + 'test' => 'value', + ], + ], + ], + // 'UNIT_DEFAULT' => '', + // 'DEFAULT_ACL_LIST' => [], + ], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ], + ], + // login: all missing + 'login: failed: all missing' => [ + [ + 'page_name' => 'edit_users.php', + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => '', + 'login_password' => '', + ], + [], + 3000, + [ + 'login_error' => 102, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Please enter username and password', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Please enter username and password' + ] + ], + // login: missing username + 'login: failed: missing username' => [ + [ + 'page_name' => 'edit_users.php', + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => '', + 'login_password' => 'abc', + ], + [], + 3000, + [ + 'login_error' => 102, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Please enter username and password', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Please enter username and password' + ] + ], + // login: missing password + 'login: failed: missing password' => [ + [ + 'page_name' => 'edit_users.php', + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'abc', + 'login_password' => '', + ], + [], + 3000, + [ + 'login_error' => 102, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Please enter username and password', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Please enter username and password' + ] + ], + // login: user not found + 'login: failed: user not found' => [ + [ + 'page_name' => 'edit_users.php', + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'abc', + 'login_password' => 'abc', + ], + [], + 3000, + [ + 'login_error' => 1010, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Wrong Username or Password', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Wrong Username or Password' + ] + ], + // login: invalid password + // 9999: not valid password encoding + // 1013: normal password failed + // 1012: plain password check failed + 'login: failed: invalid password' => [ + [ + 'page_name' => 'edit_users.php', + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'abc', + ], + [], + 3000, + [ + // default password is plain text + 'login_error' => 1012, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Wrong Username or Password', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Wrong Username or Password' + ] + ], + // login: ok (but deleted) + 'login: ok -> failed: but deleted' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_deleted' => true + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 3000, + [ + 'login_error' => 106, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - User is deleted', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - User is deleted' + ] + ], + // login: ok (but not enabled) + 'login: ok -> failed: but not enabled' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_enabled' => true + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 3000, + [ + 'login_error' => 104, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - User not enabled', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - User not enabled' + ] + ], + // login: ok (but locked) + 'login: ok -> failed: but locked' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_locked' => true + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 3000, + [ + 'login_error' => 105, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - User is locked', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - User is locked' + ] + ], + // login: make user get locked strict + 'login: ok -> failed: get locked, strict' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_get_locked' => true, + 'max_login_error_count' => 2, + 'test_locked_strict' => true, + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 0, + [ + 'lock_run_login_error' => 1012, + 'login_error' => 105, + ] + ], + // login ok, but in locked period (until) + 'login: ok -> failed: but locked period (until:on)' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_locked_period_until' => 'on' + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 3000, + [ + 'login_error' => 107, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - User in locked via date period', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - User in locked via date period' + ] + ], + // login ok, but in locked period (until) + 'login: ok, but locked period (until:off)' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_locked_period_until' => 'off' + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + // login ok, but in locked period (after) + 'login: ok -> failed: but locked period (after:on)' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_locked_period_after' => 'on' + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 3000, + [ + 'login_error' => 107, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - User in locked via date period', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - User in locked via date period' + ] + ], + // login ok, but in locked period (until, after) + 'login: ok -> failed:, but locked period (until:on, after:on)' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_locked_period_until' => 'on', + 'test_locked_period_after' => 'on' + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 3000, + [ + 'login_error' => 107, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - User in locked via date period', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - User in locked via date period' + ] + ], + // login ok, but login user id locked + 'login: ok -> failed:, but loginUserId locked' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id_locked' => true + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 3000, + [ + 'login_error' => 108, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - User is locked via Login User ID', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - User is locked via Login User ID' + ] + ], + // login: ok + 'login: ok' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + ], + [], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + // login check via _GET loginUserId + 'login: ok, _GET loginUserId' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + // login check via _POST loginUserId + 'login: ok, _POST loginUserId' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + // login: wrong GET loginUserId + 'login: ok, illegal chars in _GET loginUserId' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG' + ], + [ + 'loginUserId' => '123$%_/45678¥\-^9~~0$AB&CDEFG', + ], + [], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + 'login: not matching _GET loginUserId' => [ + [ + 'page_name' => 'edit_users.php', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG' + ], + [ + 'loginUserId' => 'ABC' + ], + [], + [], + 3000, + [ + 'login_error' => 1010, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Wrong Username or Password', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Wrong Username or Password' + ] + ], + // login ok with both _GET loginUserId and _POST login username/password + 'login: ok, _GET loginUserId AND login post user data' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + // login with invalid loginUserId but valid username/password + 'login: ok, bad _GET loginUserId AND good login post user data' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => 'ABCS', + ], + [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + // loginUserId check with revalidate on/off + 'login: ok -> failed:, but revalidate trigger, _GET loginUserId' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id_revalidate_after' => 'on', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + [], + 3000, + [ + 'login_error' => 1101, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Login User ID must be validated', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Login User ID must be validated' + ] + ], + // loginUserId check with revalidate on/off + 'login: ok, revalidate set (outside), _GET loginUserId' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id_revalidate_after' => 'off', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + // loginUserId check with active time from only + 'login: ok -> failed:, _GET loginUserId, but outside valid (from:on) ' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id_valid_from' => 'on', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + [], + 3000, + [ + 'login_error' => 1102, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Login User ID is outside valid date range', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Login User ID is outside valid date range' + ] + ], + // loginUserId check with inactive time from only + 'login: ok, _GET loginUserId, but outside valid (from:off) ' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id_valid_from' => 'off', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ], + // loginUserId check with active time until only + 'login: ok -> failed:, _GET loginUserId, but outside valid (until:on) ' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id_valid_until' => 'on', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + [], + 3000, + [ + 'login_error' => 1102, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Login User ID is outside valid date range', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Login User ID is outside valid date range' + ] + ], + // loginUserId check with active time from/until + 'login: ok -> failed:, _GET loginUserId, but outside valid (from:on,until:on) ' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id_valid_from' => 'on', + 'test_login_user_id_valid_until' => 'on', + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + ], + [ + 'loginUserId' => '1234567890ABCDEFG', + ], + [], + [], + 3000, + [ + 'login_error' => 1102, + 'error_string' => 'Fatal Error: ' + . 'Login Failed - Login User ID is outside valid date range', + 'error_string_text' => 'Fatal Error: ' + . 'Login Failed - Login User ID is outside valid date range' + ] + ], + // TODO: Test that if we have n day check with login, that after login we can use parameter login again + 'login: ok -> failed -> ok:, _GET loginUserId, but must revalidate, normal login, _GET loginUserId' => [ + [ + 'page_name' => 'edit_users.php', + 'edit_access_id' => 1, + 'edit_access_uid' => 'AdminAccess', + 'edit_access_data' => 'test', + 'base_access' => 'list', + 'page_access' => 'list', + 'test_login_user_id_revalidate_reset' => true, + 'test_login_user_id' => true, + 'test_username' => 'admin', + 'loginUserId' => '1234567890ABCDEFG', + // this error is thrown on first login round + 'login_error' => 1101, + // get post as set sub arrays + 'get' => [ + 'loginUserId' => '1234567890ABCDEFG', + ], + 'post' => [ + 'login_login' => 'Login', + 'login_username' => 'admin', + 'login_password' => 'admin', + ], + ], + // all empty get, post, session + [], + [], + [], + 0, + [ + 'login_error' => 0, + 'admin_flag' => true, + 'check_access' => true, + 'check_access_id' => 1, + 'check_access_data' => 'value', + 'base_access' => true, + 'page_access' => true, + ] + ] + // + // other: + // login check edit access id of ID not null and not in array + // login OK, but during action user gets disabled/deleted/etc + ]; + + return $tests; + } + + /** + * main test for acl login + * + * @dataProvider loginProvider + * @testdox ACL\Login Class tests [$_dataName] + * + * @param array $mock_settings + * @param array $get + * @param array $post + * @param array $session + * @param int $error + * @param array $expected + * @return void + */ + public function testACLLoginFlow( + array $mock_settings, + array $get, + array $post, + array $session, + int $error, + array $expected + ): void { + // reset session + $_SESSION = []; + // reset post & get + $_GET = []; + $_POST = []; + // reset global ajax page call + unset($GLOBALS['AJAX_PAGE']); + // init session (as MOCK) + /** @var \CoreLibs\Create\Session&MockObject */ + $session_mock = $this->createPartialMock( + \CoreLibs\Create\Session::class, + ['startSession', 'checkActiveSession', 'sessionDestroy'] + ); + $session_mock->method('startSession')->willReturn('ACLLOGINTEST12'); + $session_mock->method('checkActiveSession')->willReturn(true); + $session_mock->method('sessionDestroy')->will( + $this->returnCallback(function () { + global $_SESSION; + $_SESSION = []; + return true; + }) + ); + + // set _GET data + foreach ($get as $get_var => $get_value) { + $_GET[$get_var] = $get_value; + } + + // set _POST data + foreach ($post as $post_var => $post_value) { + $_POST[$post_var] = $post_value; + } + + // set _SESSION data + foreach ($session as $session_var => $session_value) { + $_SESSION[$session_var] = $session_value; + } + + /** @var \CoreLibs\ACL\Login&MockObject */ + $login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class) + ->setConstructorArgs([self::$db, self::$log, $session_mock, false]) + ->onlyMethods(['loginTerminate', 'loginReadPageName', 'loginPrintLogin']) + ->getMock(); + $login_mock->expects($this->any()) + ->method('loginTerminate') + ->will( + $this->returnCallback(function ($code) { + throw new \Exception('', $code); + }) + ); + $login_mock->expects($this->any()) + ->method('loginReadPageName') + // set from mock settings, or empty if not set at all + ->willReturn($mock_settings['page_name'] ?? ''); + // do not echo out any string here + $login_mock->expects($this->any()) + ->method('loginPrintLogin') + ->willReturnCallback(function () { + }); + + // if mock_settings: enabled OFF + // run DB update and set off + if (!empty($mock_settings['test_enabled'])) { + self::$db->dbExec( + "UPDATE edit_user SET enabled = 0 WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + if (!empty($mock_settings['test_deleted'])) { + self::$db->dbExec( + "UPDATE edit_user SET deleted = 1 WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + if (!empty($mock_settings['test_login_user_id_locked'])) { + self::$db->dbExec( + "UPDATE edit_user SET login_user_id_locked = 1 WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + if ( + !empty($mock_settings['test_locked_period_until']) || + !empty($mock_settings['test_locked_period_after']) + ) { + $q_sub = ''; + if (!empty($mock_settings['test_locked_period_until'])) { + if ($mock_settings['test_locked_period_until'] == 'on') { + $q_sub .= "lock_until = NOW() + '1 day'::interval "; + } elseif ($mock_settings['test_locked_period_until'] == 'off') { + $q_sub .= "lock_until = NOW() - '1 day'::interval "; + } + } + if (!empty($mock_settings['test_locked_period_after'])) { + if (!empty($q_sub)) { + $q_sub .= ", "; + } + if ($mock_settings['test_locked_period_after'] == 'on') { + $q_sub .= "lock_after = NOW() - '1 day'::interval "; + } elseif ($mock_settings['test_locked_period_after'] == 'off') { + $q_sub .= "lock_after = NOW() + '1 day'::interval "; + } + } + self::$db->dbExec( + "UPDATE edit_user SET " + . $q_sub + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + // test locked already + if (!empty($mock_settings['test_locked'])) { + self::$db->dbExec( + "UPDATE edit_user SET locked = 1 WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + // test get locked + if (!empty($mock_settings['test_get_locked'])) { + // enable strict if needed + if (!empty($mock_settings['test_locked_strict'])) { + self::$db->dbExec( + "UPDATE edit_user SET strict = 1 WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + // reset any previous login error counts + self::$db->dbExec( + "UPDATE edit_user " + . "SET login_error_count = 0, login_error_date_last = NULL, " + . "login_error_date_first = NULL " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + // check the max login error count and try login until one time before + // on each run, check that lock count is matching + // next run (run test) should then fail with user locked IF strict, + // else fail as normal login + $login_mock->loginSetMaxLoginErrorCount($mock_settings['max_login_error_count']); + // temporary wrong password + $_POST['login_password'] = 'wrong'; + for ($run = 1, $max_run = $login_mock->loginGetMaxLoginErrorCount(); $run <= $max_run; $run++) { + try { + $login_mock->loginMainCall(); + } catch (\Exception $e) { + // print 'Expected error code: ' . $e->getCode() + // . ', M:' . $e->getMessage() + // . ', L:' . $e->getLine() + // . ', E: ' . $login_mock->loginGetLastErrorCode() + // . "\n"; + $this->assertEquals( + $expected['lock_run_login_error'], + $login_mock->loginGetLastErrorCode(), + 'Assert login error code, exit on lock run' + ); + } + // check user error count + $res = self::$db->dbReturnRow( + "SELECT login_error_count FROM edit_user " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + $this->assertEquals( + $res['login_error_count'], + $run, + 'Assert equal login error count' + ); + } + // set correct password next locked login + $_POST['login_password'] = $post['login_password']; + } + if (!empty($mock_settings['test_login_user_id'])) { + self::$db->dbExec( + "UPDATE edit_user SET " + . "login_user_id = " + . self::$db->dbEscapeLiteral($mock_settings['loginUserId']) + . " " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($mock_settings['test_username']) + ); + } + if (!empty($mock_settings['test_login_user_id_revalidate_after'])) { + $q_sub = ''; + if ($mock_settings['test_login_user_id_revalidate_after'] == 'on') { + $q_sub = "login_user_id_last_revalidate = NOW() - '1 day'::interval, " + . "login_user_id_revalidate_after = '1 day'::interval "; + } else { + $q_sub = "login_user_id_last_revalidate = NOW(), " + . "login_user_id_revalidate_after = '6 day'::interval "; + } + self::$db->dbExec( + "UPDATE edit_user SET " + . $q_sub + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($mock_settings['test_username']) + ); + } + if (!empty($mock_settings['test_login_user_id_revalidate_reset'])) { + // init dates data for revalidate frame, + // set to last revalidate 3 days ago and set revalidate frame to + // three days + self::$db->dbExec( + "UPDATE edit_user SET " + . "login_user_id_last_revalidate = NOW() - '3 day'::interval, " + . "login_user_id_revalidate_after = '3 day'::interval " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($mock_settings['test_username']) + ); + $_GET = $mock_settings['get']; + // login with loginUserId -> fail + try { + $login_mock->loginMainCall(); + } catch (\Exception $e) { + $this->assertEquals( + $mock_settings['login_error'], + $login_mock->loginGetLastErrorCode(), + 'loginUserId reset 1: Assert first loginUserId run failes' + ); + } + $_GET = []; + // login with username and password -> reset -> ok + // set _POST data + $_POST = $mock_settings['post']; + try { + $login_mock->loginMainCall(); + $this->assertEquals( + 0, + $login_mock->loginGetLastErrorCode(), + 'loginUserId reset 2: Assert username/password login is successful' + ); + } catch (\Exception $e) { + // if we end up here we have an issue + $this->assertTrue( + false, + 'loginUserId reset 2: FAILED successful login' + ); + } + $_POST = []; + // logut and run normal login with loginUserId + $_GET = $mock_settings['get']; + } + if ( + !empty($mock_settings['test_login_user_id_valid_from']) || + !empty($mock_settings['test_login_user_id_valid_until']) + ) { + $q_sub = ''; + if (!empty($mock_settings['test_login_user_id_valid_from'])) { + if ($mock_settings['test_login_user_id_valid_from'] == 'on') { + $q_sub .= "login_user_id_valid_from = NOW() + '1 day'::interval "; + } elseif ($mock_settings['test_login_user_id_valid_from'] == 'off') { + $q_sub .= "login_user_id_valid_from = NOW() - '1 day'::interval "; + } + } + if (!empty($mock_settings['test_login_user_id_valid_until'])) { + if (!empty($q_sub)) { + $q_sub .= ", "; + } + if ($mock_settings['test_login_user_id_valid_until'] == 'on') { + $q_sub .= "login_user_id_valid_until = NOW() - '1 day'::interval "; + } elseif ($mock_settings['test_login_user_id_valid_until'] == 'off') { + $q_sub .= "login_user_id_valid_until = NOW() + '1 day'::interval "; + } + } + self::$db->dbExec( + "UPDATE edit_user SET " + . $q_sub + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($mock_settings['test_username']) + ); + } + + // run test + try { + // if ajax call + // check if parameter, or globals (old type) + // else normal call + if ( + !empty($mock_settings['ajax_test_type']) && + $mock_settings['ajax_test_type'] == 'parameter' + ) { + $login_mock->loginMainCall($mock_settings['ajax_page']); + } elseif ( + !empty($mock_settings['ajax_test_type']) && + $mock_settings['ajax_test_type'] == 'globals' + ) { + $GLOBALS['AJAX_PAGE'] = $mock_settings['ajax_page']; + $login_mock->loginMainCall(); + } else { + $login_mock->loginMainCall(); + } + // on ok, do post login check based on expected return + // - loginGetLastErrorCode + $this->assertEquals( + $expected['login_error'], + $login_mock->loginGetLastErrorCode(), + 'Assert login error code' + ); + // - loginGetPageName + $this->assertEquals( + $mock_settings['page_name'], + $login_mock->loginGetPageName(), + 'Assert page name' + ); + // - loginCheckPermissions [duplicated from loginrun] + $this->assertTrue( + $login_mock->loginCheckPermissions(), + 'Assert true for login permission ok' + ); + // - loginCheckAccess [use Base, Page below] + $this->assertEquals( + $expected['base_access'], + $login_mock->loginCheckAccess('base', $mock_settings['base_access']), + 'Assert base access, via main method' + ); + $this->assertEquals( + $expected['base_access'], + $login_mock->loginCheckAccess('page', $mock_settings['base_access']), + 'Assert page access, via main method' + ); + // - loginCheckAccessBase + $this->assertEquals( + $expected['base_access'], + $login_mock->loginCheckAccessBase($mock_settings['base_access']), + 'Assert base access' + ); + // - loginCheckAccessPage + $this->assertEquals( + $expected['page_access'], + $login_mock->loginCheckAccessPage($mock_settings['page_access']), + 'Assert page access' + ); + // - loginCheckEditAccess + $this->assertEquals( + $expected['check_access'], + $login_mock->loginCheckEditAccess($mock_settings['edit_access_id']), + 'Assert check access' + ); + // - loginCheckEditAccessId + $this->assertEquals( + $expected['check_access_id'], + $login_mock->loginCheckEditAccessId((int)$mock_settings['edit_access_id']), + 'Assert check access id valid' + ); + // - loginGetEditAccessIdFromUid + $this->assertEquals( + $expected['check_access_id'], + $login_mock->loginGetEditAccessIdFromUid($mock_settings['edit_access_uid']), + 'Assert check access uid to id valid' + ); + // - loginGetEditAccessData + $this->assertEquals( + $expected['check_access_data'], + $login_mock->loginGetEditAccessData( + $mock_settings['edit_access_id'], + $mock_settings['edit_access_data'] + ), + 'Assert check access id data value valid' + ); + // - loginIsAdmin + $this->assertEquals( + $expected['admin_flag'], + $login_mock->loginIsAdmin(), + 'Assert admin flag set' + ); + // - loginGetAcl + $this->assertIsArray( + $login_mock->loginGetAcl(), + 'Assert get acl is array' + ); + // if loginUserId in _GET or _POST check that it is set + if (!empty($get['loginUserId']) || !empty($post['loginUserId'])) { + $this->assertNotEmpty( + $login_mock->loginGetLoginUserId(), + 'Assert loginUserId is set' + ); + } + // TODO: detail match of ACL array (loginGetAcl) + + // .. end with: loginLogoutUser + // _POST['login_logout'] = 'lgogout + // $login_mock->loginMainCall(); + // - loginCheckPermissions + // - loginGetPermissionOkay + } catch (\Exception $e) { + // print "[E]: " . $e->getCode() . ", ERROR: " . $login_mock->loginGetLastErrorCode() . "/" + // . ($expected['login_error'] ?? 0) . "\n"; + // print "AJAX: " . $login_mock->loginGetAjaxFlag() . "\n"; + // print "AJAX GLOBAL: " . ($GLOBALS['AJAX_PAGE'] ?? '{f}') . "\n"; + // print "Login error expext: " . ($expected['login_error'] ?? '{0}') . "\n"; + // if this is 3000, then we do further error checks + if ( + $e->getCode() == 3000 || + !empty($_POST['login_exit']) && $_POST['login_exit'] == 3000 + ) { + $this->assertEquals( + $expected['login_error'], + $login_mock->loginGetLastErrorCode(), + 'Assert login error code, exit' + ); + // - loginGetErrorMsg + $this->assertEquals( + $expected['error_string'], + $login_mock->loginGetErrorMsg($login_mock->loginGetLastErrorCode()), + 'Assert error string, html' + ); + $this->assertEquals( + $expected['error_string_text'], + $login_mock->loginGetErrorMsg($login_mock->loginGetLastErrorCode(), true), + 'Assert error string, text' + ); + // - loginGetLoginHTML + $this->assertStringContainsString( + 'assertEquals( + !$login_mock->loginGetAjaxFlag() ? + $e->getCode() : + $_POST['login_exit'] ?? 0, + $error, + 'Expected error code: ' . $e->getCode() + . ', ' . $e->getMessage() + . ', ' . $e->getLine() + ); + } + + // if _POST login set check this is matching + if (!empty($post['login_login'])) { + $this->assertTrue( + $login_mock->loginActionRun(), + 'Assert that post login_login was pressed' + ); + } + + // always check, even on error or not set + if (!$login_mock->loginGetLoginUserIdUnclean()) { + $this->assertEquals( + $_GET['loginUserId'] ?? $_POST['loginUserId'] ?? '', + $login_mock->loginGetLoginUserId(), + 'Assert loginUserId matches' + ); + } else { + $this->assertTrue( + $login_mock->loginGetLoginUserIdUnclean(), + 'Assert loginUserId is unclear' + ); + $this->assertNotEquals( + $_GET['loginUserId'] ?? $_POST['loginUserId'] ?? '', + $login_mock->loginGetLoginUserId(), + 'Assert loginUserId does not matche _GET/_POST' + ); + } + // check get/post login user id + $this->assertEquals( + (!empty($_GET['loginUserId']) ? + 'GET' : + (!empty($_POST['loginUserId']) ? + 'POST' : '') + ), + $login_mock->loginGetLoginUserIdSource(), + 'Assert loginUserId source matches' + ); + + // enable user again if flag set + if (!empty($mock_settings['test_enabled'])) { + self::$db->dbExec( + "UPDATE edit_user SET enabled = 1 " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + if (!empty($mock_settings['test_deleted'])) { + self::$db->dbExec( + "UPDATE edit_user SET deleted = 0 WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + if (!empty($mock_settings['test_login_user_id_locked'])) { + self::$db->dbExec( + "UPDATE edit_user SET login_user_id_locked = 0 WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + if ( + !empty($mock_settings['test_locked_period_until']) || + !empty($mock_settings['test_locked_period_after']) + ) { + self::$db->dbExec( + "UPDATE edit_user SET " + . "lock_until = NULL, " + . "lock_after = NULL " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + // reset lock flag + if (!empty($mock_settings['test_locked'])) { + self::$db->dbExec( + "UPDATE edit_user SET locked = 0 " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + // rest the get locked flow + if (!empty($mock_settings['test_get_locked'])) { + self::$db->dbExec( + "UPDATE edit_user " + . "SET login_error_count = 0, login_error_date_last = NULL, " + . "login_error_date_first = NULL, locked = 0, strict = 0 " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($post['login_username']) + ); + } + if (!empty($mock_settings['test_login_user_id'])) { + self::$db->dbExec( + "UPDATE edit_user SET " + . "login_user_id = NULL, " + // below to rows are automatcially reset + . "login_user_id_set_date = NULL, " + . "login_user_id_last_revalidate = NULL " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($mock_settings['test_username']) + ); + } + if (!empty($mock_settings['test_login_user_id_revalidate_after'])) { + self::$db->dbExec( + "UPDATE edit_user SET " + . "login_user_id_last_revalidate = NULL, " + . "login_user_id_revalidate_after = NULL " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($mock_settings['test_username']) + ); + } + if ( + !empty($mock_settings['test_login_user_id_valid_from']) || + !empty($mock_settings['test_login_user_id_valid_until']) + ) { + self::$db->dbExec( + "UPDATE edit_user SET " + . "login_user_id_valid_from = NULL, " + . "login_user_id_valid_until = NULL " + . "WHERE LOWER(username) = " + . self::$db->dbEscapeLiteral($mock_settings['test_username']) + ); + } + } + + // - loginGetAclList (null, invalid,) + + public function aclListProvider(): array + { + // 0: level (int|null) + // 2: type (string), null for skip (if 0 = null) + // 1: acl return from level (array) + // 2: level number to return (must match 0) + return [ + 'null, get full list' => [ + null, + null, + [ + 0 => [ + 'type' => 'none', + 'name' => 'No Access', + ], + 10 => [ + 'type' => 'list', + 'name' => 'List', + ], + 20 => [ + 'type' => 'read', + 'name' => 'Read', + ], + 30 => [ + 'type' => 'mod_trans', + 'name' => 'Translator', + ], + 40 => [ + 'type' => 'mod', + 'name' => 'Modify', + ], + 60 => [ + 'type' => 'write', + 'name' => 'Create/Write', + ], + 80 => [ + 'type' => 'del', + 'name' => 'Delete', + ], + 90 => [ + 'type' => 'siteadmin', + 'name' => 'Site Admin', + ], + 100 => [ + 'type' => 'admin', + 'name' => 'Admin', + ], + ], + null + ], + 'valid, search' => [ + 20, + 'read', + [ + 'type' => 'read', + 'name' => 'Read' + ], + 20 + ], + 'invalud search' => [ + 12, + 'foo', + [], + false, + ] + ]; + } + + /** + * Undocumented function + * + * @dataProvider aclListProvider() + * @testdox ACL\Login list if $level and $type exepcted level is $expected_level [$_dataName] + * + * @param int|null $level + * @param string|null $type + * @param array $expected_list + * @param int|null|bool $expected_level + * @return void + */ + public function testAclLoginList( + ?int $level, + ?string $type, + array $expected_list, + $expected_level + ): void { + $_SESSION = []; + // init session (as MOCK) + /** @var \CoreLibs\Create\Session&MockObject */ + $session_mock = $this->createPartialMock( + \CoreLibs\Create\Session::class, + ['startSession', 'checkActiveSession', 'sessionDestroy'] + ); + $session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); + $session_mock->method('checkActiveSession')->willReturn(true); + $session_mock->method('sessionDestroy')->will( + $this->returnCallback(function () { + global $_SESSION; + $_SESSION = []; + return true; + }) + ); + /** @var \CoreLibs\ACL\Login&MockObject */ + $login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class) + ->setConstructorArgs([self::$db, self::$log, $session_mock, false]) + ->onlyMethods(['loginTerminate']) + ->getMock(); + $login_mock->expects($this->any()) + ->method('loginTerminate') + ->will( + $this->returnCallback(function ($code) { + throw new \Exception('', $code); + }) + ); + + $list = $login_mock->loginGetAclList($level); + $this->assertIsArray( + $list, + 'assert get acl list is array' + ); + $this->assertEquals( + $expected_list, + $list + ); + if ($type !== null) { + $this->assertEquals( + $expected_level, + $login_mock->loginGetAclListFromType($type), + 'assert type is level' + ); + // only back assert if found + if (isset($list['type'])) { + $this->assertEquals( + $list['type'], + $type, + 'assert level read type is type' + ); + } + } + } + + /** + * Undocumented function + * + * @return array + */ + public function minPasswordCheckProvider(): array + { + // 0: set length + // 1: expected return from set + // 2: expected set length + return [ + 'set new length' => [ + 12, + true, + 12, + ], + 'set new length, too short' => [ + 5, + false, + 9 + ], + 'set new length, too long' => [ + 500, + false, + 9 + ] + ]; + } + + /** + * check setting minimum password length + * + * @covers ::loginSetPasswordMinLength + * @covers ::loginGetPasswordLenght + * @dataProvider minPasswordCheckProvider() + * @testdox ACL\Login password min length set $input is $expected_return and matches $expected [$_dataName] + * + * @param int $input + * @param bool $expected_return + * @param int $expected + * @return void + */ + public function testACLLoginPasswordMinLenght(int $input, bool $expected_return, int $expected): void + { + $_SESSION = []; + // init session (as MOCK) + /** @var \CoreLibs\Create\Session&MockObject */ + $session_mock = $this->createPartialMock( + \CoreLibs\Create\Session::class, + ['startSession', 'checkActiveSession', 'sessionDestroy'] + ); + $session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); + $session_mock->method('checkActiveSession')->willReturn(true); + $session_mock->method('sessionDestroy')->will( + $this->returnCallback(function () { + global $_SESSION; + $_SESSION = []; + return true; + }) + ); + /** @var \CoreLibs\ACL\Login&MockObject */ + $login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class) + ->setConstructorArgs([self::$db, self::$log, $session_mock, false]) + ->onlyMethods(['loginTerminate']) + ->getMock(); + $login_mock->expects($this->any()) + ->method('loginTerminate') + ->will( + $this->returnCallback(function ($code) { + throw new \Exception('', $code); + }) + ); + + // set new min password length + $this->assertEquals( + $expected_return, + $login_mock->loginSetPasswordMinLength($input), + 'assert bool set password min length' + ); + // check value + $this->assertEquals( + $expected, + $login_mock->loginGetPasswordLenght('min'), + 'assert get password min length' + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function getPasswordLengthProvider(): array + { + return [ + 'min' => ['min'], + 'lower' => ['lower'], + 'max' => ['max'], + 'upper' => ['upper'], + 'minimum_length' => ['minimum_length'], + 'min_length' => ['min_length'], + 'length' => ['length'], + ]; + } + + /** + * check all possible readable password length params + * + * @covers ::loginGetPasswordLenght + * @dataProvider getPasswordLengthProvider() + * @testdox ACL\Login get password length $input [$_dataName] + * + * @param string $input + * @return void + */ + public function testACLLoginGetPasswordLength(string $input): void + { + $_SESSION = []; + // init session (as MOCK) + /** @var \CoreLibs\Create\Session&MockObject */ + $session_mock = $this->createPartialMock( + \CoreLibs\Create\Session::class, + ['startSession', 'checkActiveSession', 'sessionDestroy'] + ); + $session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); + $session_mock->method('checkActiveSession')->willReturn(true); + $session_mock->method('sessionDestroy')->will( + $this->returnCallback(function () { + global $_SESSION; + $_SESSION = []; + return true; + }) + ); + /** @var \CoreLibs\ACL\Login&MockObject */ + $login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class) + ->setConstructorArgs([self::$db, self::$log, $session_mock, false]) + ->onlyMethods(['loginTerminate']) + ->getMock(); + $login_mock->expects($this->any()) + ->method('loginTerminate') + ->will( + $this->returnCallback(function ($code) { + throw new \Exception('', $code); + }) + ); + + $this->assertMatchesRegularExpression( + "/^\d+$/", + (string)$login_mock->loginGetPasswordLenght($input) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function loginMaxErrorProvider(): array + { + return [ + 'set valid max failed login' => [ + 10, + true, + 10 + ], + 'set valid unlimted' => [ + -1, + true, + -1 + ], + 'set invalid 0' => [ + 0, + false, + -1 + ], + 'set invalid negative' => [ + -5, + false, + -1 + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::loginSetMaxLoginErrorCount + * @covers ::loginGetMaxLoginErrorCount + * @dataProvider loginMaxErrorProvider() + * @testdox ACL\Login failed login set/get $input is $expected_return and matches $expected [$_dataName] + * + * @param int $input + * @param bool $expected_return + * @param int $expected + * @return void + */ + public function testACLLoginErrorCount(int $input, bool $expected_return, int $expected): void + { + $_SESSION = []; + // init session (as MOCK) + /** @var \CoreLibs\Create\Session&MockObject */ + $session_mock = $this->createPartialMock( + \CoreLibs\Create\Session::class, + ['startSession', 'checkActiveSession', 'sessionDestroy'] + ); + $session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); + $session_mock->method('checkActiveSession')->willReturn(true); + $session_mock->method('sessionDestroy')->will( + $this->returnCallback(function () { + global $_SESSION; + $_SESSION = []; + return true; + }) + ); + /** @var \CoreLibs\ACL\Login&MockObject */ + $login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class) + ->setConstructorArgs([self::$db, self::$log, $session_mock, false]) + ->onlyMethods(['loginTerminate']) + ->getMock(); + $login_mock->expects($this->any()) + ->method('loginTerminate') + ->will( + $this->returnCallback(function ($code) { + throw new \Exception('', $code); + }) + ); + + // set new min password length + $this->assertEquals( + $expected_return, + $login_mock->loginSetMaxLoginErrorCount($input), + 'assert bool set max login errors' + ); + // check value + $this->assertEquals( + $expected, + $login_mock->loginGetMaxLoginErrorCount(), + 'assert get max login errors' + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsACLLogin_database_prepare.sh b/test/phpunit/CoreLibsACLLogin_database_prepare.sh new file mode 100755 index 0000000..34b37a7 --- /dev/null +++ b/test/phpunit/CoreLibsACLLogin_database_prepare.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# note: there is currently no port selection, standard 5432 port is assumed +# note: we use the default in path postgresql commands and connect to whatever default DB is set + +# PARAMETER 1: database data file to load +# PARAMETER 2: db user WHO MUST BE ABLE TO CREATE A DATABASE +# PARAMETER 3: db name +# PARAMETER 4: db host +# PARAMETER 5: print out for testing + +load_sql="${1}"; +# abort with 1 if we cannot find the file +if [ ! -f "${load_sql}" ]; then + echo 1; + exit 1; +fi; +db_user="${2}"; +db_name="${3}"; +db_host="${4}"; +# empty db name or db user -> exit with 2 +if [ -z "${db_user}" ] || [ -z "${db_name}" ] || [ -z "${db_host}" ]; then + echo 2; + exit 2; +fi; +# drop database, on error exit with 3 +dropdb -U ${db_user} -h ${db_host} ${db_name} 2>&1; +if [ $? -ne 0 ]; then + echo 3; + exit 3; +fi; +# create database, on error exit with 4 +createdb -U ${db_user} -O ${db_user} -h ${db_host} -E utf8 ${db_name} 2>&1; +if [ $? -ne 0 ]; then + echo 4; + exit 4; +fi; +# if error 5 thrown, test with enabled below +if [ ! -z "${5}" ]; then + psql -U ${db_user} -h ${db_host} -f ${load_sql} ${db_name}; +else + # load data (redirect ALL error to null), on error exit with 5 + psql -U ${db_user} -h ${db_host} -f ${load_sql} ${db_name} 2>&1 1>/dev/null 2>/dev/null; +fi; +if [ $? -ne 0 ]; then + echo 5; + exit 5; +fi; +echo 0; +exit 0; + +# __END__ diff --git a/test/phpunit/CoreLibsAdminBackendTest.php b/test/phpunit/CoreLibsAdminBackendTest.php new file mode 100644 index 0000000..2cfc898 --- /dev/null +++ b/test/phpunit/CoreLibsAdminBackendTest.php @@ -0,0 +1,47 @@ +markTestSkipped( + 'The PgSQL extension is not available.' + ); + } + } + + /** + * Undocumented function + * + * @testdox Admin\Backend Class tests + * + * @return void + */ + public function testAdminBackend() + { + /* $this->assertTrue(true, 'ACL Login Tests not implemented'); + $this->markTestIncomplete( + 'ACL\Login Tests have not yet been implemented' + ); */ + $this->markTestSkipped('No implementation for Admin\Backend at the moment'); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsAdminEditPageTest.php b/test/phpunit/CoreLibsAdminEditPageTest.php new file mode 100644 index 0000000..07915df --- /dev/null +++ b/test/phpunit/CoreLibsAdminEditPageTest.php @@ -0,0 +1,47 @@ +markTestSkipped( + 'The PgSQL extension is not available.' + ); + } + } + + /** + * Undocumented function + * + * @testdox Admin\EditPage Class tests + * + * @return void + */ + public function testAdminEditPage() + { + /* $this->assertTrue(true, 'ACL Login Tests not implemented'); + $this->markTestIncomplete( + 'ACL\Login Tests have not yet been implemented' + ); */ + $this->markTestSkipped('No implementation for Admin\EditPage at the moment'); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCheckColorsTest.php b/test/phpunit/CoreLibsCheckColorsTest.php new file mode 100644 index 0000000..7386bf7 --- /dev/null +++ b/test/phpunit/CoreLibsCheckColorsTest.php @@ -0,0 +1,329 @@ + [ + '#ab12cd', + null, + true, + ], + 'valid hex rgb, flag ALL' => [ + '#ab12cd', + \CoreLibs\Check\Colors::ALL, + true, + ], + 'valid hex rgb, flag HEX_RGB' => [ + '#ab12cd', + \CoreLibs\Check\Colors::HEX_RGB, + true, + ], + 'valid hex rgb, wrong flag' => [ + '#ab12cd', + \CoreLibs\Check\Colors::RGB, + false, + ], + // error + 'invalid hex rgb A' => [ + '#ab12zz', + null, + false, + ], + 'invalid hex rgb B' => [ + '#ZyQfo', + null, + false, + ], + // other valid hex checks + 'valid hex rgb, alt A' => [ + '#AB12cd', + null, + true, + ], + // * hax alpha + 'valid hex rgb alpha, flag ALL (default)' => [ + '#ab12cd12', + null, + true, + ], + 'valid hex rgb alpha, flag ALL' => [ + '#ab12cd12', + \CoreLibs\Check\Colors::ALL, + true, + ], + 'valid hex rgb alpha, flag HEX_RGBA' => [ + '#ab12cd12', + \CoreLibs\Check\Colors::HEX_RGBA, + true, + ], + 'valid hex rgb alpha, wrong flag' => [ + '#ab12cd12', + \CoreLibs\Check\Colors::RGB, + false, + ], + // error + 'invalid hex rgb alpha A' => [ + '#ab12dd1', + null, + false, + ], + 'invalid hex rgb alpha B' => [ + '#ab12ddzz', + null, + false, + ], + 'valid hex rgb alpha, alt A' => [ + '#ab12cdEE', + null, + true, + ], + // * rgb + 'valid rgb, flag ALL (default)' => [ + 'rgb(255, 10, 20)', + null, + true, + ], + 'valid rgb, flag ALL' => [ + 'rgb(255, 10, 20)', + \CoreLibs\Check\Colors::ALL, + true, + ], + 'valid rgb, flag RGB' => [ + 'rgb(255, 10, 20)', + \CoreLibs\Check\Colors::RGB, + true, + ], + 'valid rgb, wrong flag' => [ + 'rgb(255, 10, 20)', + \CoreLibs\Check\Colors::HEX_RGB, + false, + ], + // error + 'invalid rgb A' => [ + 'rgb(356, 10, 20)', + null, + false, + ], + // other valid rgb conbinations + 'valid rgb, alt A (percent)' => [ + 'rgb(100%, 10%, 20%)', + null, + true, + ], + // TODO check all % and non percent combinations + 'valid rgb, alt B (percent, mix)' => [ + 'rgb(100%, 10, 40)', + null, + true, + ], + // * rgb alpha + 'valid rgba, flag ALL (default)' => [ + 'rgba(255, 10, 20, 0.5)', + null, + true, + ], + 'valid rgba, flag ALL' => [ + 'rgba(255, 10, 20, 0.5)', + \CoreLibs\Check\Colors::ALL, + true, + ], + 'valid rgba, flag RGB' => [ + 'rgba(255, 10, 20, 0.5)', + \CoreLibs\Check\Colors::RGBA, + true, + ], + 'valid rgba, wrong flag' => [ + 'rgba(255, 10, 20, 0.5)', + \CoreLibs\Check\Colors::HEX_RGB, + false, + ], + // error + 'invalid rgba A' => [ + 'rgba(356, 10, 20, 0.5)', + null, + false, + ], + // other valid rgba combinations + 'valid rgba, alt A (percent)' => [ + 'rgba(100%, 10%, 20%, 0.5)', + null, + true, + ], + // TODO check all % and non percent combinations + 'valid rgba, alt B (percent, mix)' => [ + 'rgba(100%, 10, 40, 0.5)', + null, + true, + ], + // TODO check all % and non percent combinations with percent transparent + 'valid rgba, alt C (percent transparent)' => [ + 'rgba(100%, 10%, 20%, 50%)', + null, + true, + ], + /* + // hsl + 'hsl(100, 50%, 60%)', + 'hsl(100, 50.5%, 60.5%)', + 'hsla(100, 50%, 60%)', + 'hsla(100, 50.5%, 60.5%)', + 'hsla(100, 50%, 60%, 0.5)', + 'hsla(100, 50.5%, 60.5%, 0.5)', + 'hsla(100, 50%, 60%, 50%)', + 'hsla(100, 50.5%, 60.5%, 50%)', + */ + // * hsl + 'valid hsl, flag ALL (default)' => [ + 'hsl(100, 50%, 60%)', + null, + true, + ], + 'valid hsl, flag ALL' => [ + 'hsl(100, 50%, 60%)', + \CoreLibs\Check\Colors::ALL, + true, + ], + 'valid hsl, flag RGB' => [ + 'hsl(100, 50%, 60%)', + \CoreLibs\Check\Colors::HSL, + true, + ], + 'valid hsl, wrong flag' => [ + 'hsl(100, 50%, 60%)', + \CoreLibs\Check\Colors::HEX_RGB, + false, + ], + 'invalid hsl A' => [ + 'hsl(500, 50%, 60%)', + null, + false, + ], + 'valid hsl, alt A' => [ + 'hsl(100, 50.5%, 60.5%)', + null, + true, + ], + // * hsl alpha + 'valid hsla, flag ALL (default)' => [ + 'hsla(100, 50%, 60%, 0.5)', + null, + true, + ], + 'valid hsla, flag ALL' => [ + 'hsla(100, 50%, 60%, 0.5)', + \CoreLibs\Check\Colors::ALL, + true, + ], + 'valid hsla, flag RGB' => [ + 'hsla(100, 50%, 60%, 0.5)', + \CoreLibs\Check\Colors::HSLA, + true, + ], + 'valid hsla, wrong flag' => [ + 'hsla(100, 50%, 60%, 0.5)', + \CoreLibs\Check\Colors::HEX_RGB, + false, + ], + 'invalid hsla A' => [ + 'hsla(500, 50%, 60%, 0.5)', + null, + false, + ], + 'valid hsla, alt A (percent alpha' => [ + 'hsla(100, 50%, 60%, 50%)', + null, + true, + ], + 'valid hsla, alt A (percent alpha' => [ + 'hsla(100, 50.5%, 60.5%, 50%)', + null, + true, + ], + // * combined flag checks + 'valid rgb, flag RGB|RGBA' => [ + 'rgb(100%, 10%, 20%)', + \CoreLibs\Check\Colors::RGB | \CoreLibs\Check\Colors::RGBA, + true, + ], + // TODO other combined flag checks all combinations + // * invalid string + 'invalid string A' => [ + 'invalid string', + null, + false, + ], + 'invalid string B' => [ + '(hsla(100, 100, 100))', + null, + false, + ], + 'invalid string C' => [ + 'hsla(100, 100, 100', + null, + false, + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::validateColor + * @dataProvider validateColorProvider + * @testdox validateColor $input with flags $flags be $expected [$_dataName] + * + * @param string $input + * @param int|null $flags + * @param bool $expected + * @return void + */ + public function testValidateColor(string $input, ?int $flags, bool $expected) + { + if ($flags === null) { + $result = \CoreLibs\Check\Colors::validateColor($input); + } else { + $result = \CoreLibs\Check\Colors::validateColor($input, $flags); + } + $this->assertEquals( + $expected, + $result + ); + } + + /** + * Undocumented function + * + * @covers ::validateColor + * @testWith [99] + * @testdox Check Exception throw for $flag + * + * @param int $flag + * @return void + */ + public function testValidateColorException(int $flag): void + { + $this->expectException(\Exception::class); + \CoreLibs\Check\Colors::validateColor('#ffffff', $flag); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCheckEmailTest.php b/test/phpunit/CoreLibsCheckEmailTest.php new file mode 100644 index 0000000..81aeceb --- /dev/null +++ b/test/phpunit/CoreLibsCheckEmailTest.php @@ -0,0 +1,381 @@ + + */ + public function emailRegexProvider(): array + { + return [ + 'get email regex invalid -1, will be 0' => [ + -1, + "^[A-Za-z0-9!#$%&'*+\-\/=?^_`{|}~][A-Za-z0-9!#$%:\(\)&'*+\-\/=?^_`{|}~\.]{0,63}@" + . "[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]{1,})*\.([a-zA-Z]{2,}){1}$" + ], + 'get email regex invalid 10, will be 0' => [ + 10, + "^[A-Za-z0-9!#$%&'*+\-\/=?^_`{|}~][A-Za-z0-9!#$%:\(\)&'*+\-\/=?^_`{|}~\.]{0,63}@" + . "[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]{1,})*\.([a-zA-Z]{2,}){1}$" + ], + 'get email regex valid 1, will be 1' => [ + 1, + "@(.*)@(.*)" + ] + ]; + } + + /** + * Test regex level return + * + * @covers ::getEmailRegex + * @dataProvider emailRegexProvider + * @testdox getEmailRegex $input will be $expected [$_dataName] + * + * @param int $input + * @param string $expected + * @return void + */ + public function testGetEmailRegexReturn(int $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Check\Email::getEmailRegex($input) + ); + } + + /** + * provides data for emailCheckProvider and emailCheckFullProvider + * + * @return array + */ + public function emailCheckList(): array + { + return [ + 'valid email' => ['test@test.com', true, []], + 'invalid empty email' => ['', false, [0, 2, 3, 4, 5]], + 'invalid email' => ['-@-', false, [0, 3, 4, 5]], + 'invalid email leading dot' => ['.test@test.com', false, [0, 2]], + 'invalid email invalid domain' => ['test@t_est.com', false, [0, 3, 4]], + 'invalid email double @' => ['test@@test.com', false, [0, 1]], + 'invalid email double dot' => ['test@test..com', false, [0, 3, 6]], + 'invalid email end with dot' => ['test@test.', false, [0, 3, 5, 7]], + 'invalid email bad top level' => ['test@test.j', false, [0, 3, 5]], + 'invalid email double @ and double dot' => ['test@@test..com', false, [0, 1, 3, 6]], + ]; + } + + /** + * Valids or not valid email address + * + * @return array + */ + public function emailCheckProvider(): array + { + $list = []; + foreach ($this->emailCheckList() as $key => $data) { + $list[$key] = [$data[0], $data[1]]; + } + return $list; + } + + /** + * Undocumented function + * + * @covers ::checkEmail + * @dataProvider emailCheckProvider + * @testdox checkEmail $input will be $expected [$_dataName] + * + * @return void + */ + public function testCheckEmail(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Check\Email::checkEmail($input) + ); + } + + /** + * this is like emailCheckProvider but it has the full detail errors + * All errors should be tetsed in testGetEmailRegexErrorMessage + * + * @return array + */ + public function emailCheckFullProvider(): array + { + $list = []; + foreach ($this->emailCheckList() as $key => $data) { + $list[$key] = [$data[0], $data[2]]; + } + return $list; + } + + /** + * Undocumented function + * + * @covers ::checkEmailFull + * @dataProvider emailCheckFullProvider + * @testdox checkEmailFull $input will be $expected [$_dataName] + * + * @param string $input + * @param array $expected + * @return void + */ + public function testCheckEmailFull(string $input, array $expected): void + { + $this->assertEqualsCanonicalizing( + $expected, + \CoreLibs\Check\Email::checkEmailFull($input, true) + ); + } + + /** + * error data returned for each error position + * + * @return array + */ + public function emailRegexErrorProvider(): array + { + return [ + 'error 0 will return general' => [ + 0, + [ + 'error' => 0, + 'message' => 'Invalid email address', + 'regex' => "^[A-Za-z0-9!#$%&'*+\-\/=?^_`{|}~][A-Za-z0-9!#$%:\(\)&'*+\-\/=?^_`{|}~\.]{0,63}@" + . "[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]{1,})*\.([a-zA-Z]{2,}){1}$" + ] + ], + 'error 1 will return double @ error' => [ + 1, + [ + 'error' => 1, + 'message' => 'Double @ mark in email address', + 'regex' => "@(.*)@(.*)" + ] + ], + 'error 2 will be invalid before @' => [ + 2, + [ + 'error' => 2, + 'message' => 'Invalid email part before @ sign', + 'regex' => "^[A-Za-z0-9!#$%&'*+\-\/=?^_`{|}~][A-Za-z0-9!#$%:\(\)&'*+\-\/=?^_`{|}~\.]{0,63}@" + ] + ], + 'error 3 will be invalid domain and top level' => [ + 3, + [ + 'error' => 3, + 'message' => 'Invalid domain part after @ sign', + 'regex' => "@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]{1,})*\.([a-zA-Z]{2,}){1}$" + ] + ], + 'error 4 will be invalid domain' => [ + 4, + [ + 'error' => 4, + 'message' => 'Invalid domain name part', + 'regex' => "@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]{1,})*\." + ] + ], + 'error 5 will be invalid domain top level only' => [ + 5, + [ + 'error' => 5, + 'message' => 'Wrong domain top level part', + 'regex' => "\.([a-zA-Z]{2,6}){1}$" + ] + ], + 'error 6 will be domain double dot' => [ + 6, + [ + 'error' => 6, + 'message' => 'Double consecutive dots in domain name (..)', + 'regex' => "@(.*)\.{2,}" + ] + ], + 'error 7 will domain ends with dot' => [ + 7, + [ + 'error' => 7, + 'message' => 'Domain ends with a dot or is missing top level part', + 'regex' => "@.*\.$" + ] + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::getEmailRegexErrorMessage + * @dataProvider emailRegexErrorProvider + * @testdox getEmailRegexErrorMessage $input will be $expected [$_dataName] + * + * @param integer $input + * @param array $expected + * @return void + */ + public function testGetEmailRegexErrorMessage(int $input, array $expected): void + { + $this->assertEqualsCanonicalizing( + $expected, + \CoreLibs\Check\Email::getEmailRegexErrorMessage($input) + ); + } + + /** + * This holds all email type checks normal and short + * + * @return array + */ + public function emailTypeProvider(): array + { + return [ + ['test@test.com', 'pc_html', 'pc'], + ['test@docomo.ne.jp', 'keitai_docomo', 'docomo'], + ['test@softbank.ne.jp', 'keitai_softbank', 'softbank'], + ['test@i.softbank.ne.jp', 'smartphone_softbank_iphone', 'iphone'], + // TODO: add more test emails here + ]; + } + + /** + * Returns only normal email type checks + * + * @return array + */ + public function emailTypeProviderLong(): array + { + $list = []; + foreach ($this->emailTypeProvider() as $set) { + $list['email ' . $set[0] . ' is valid and matches normal ' . $set[1]] = [$set[0], $set[1]]; + } + $list['email is empty and not valid normal'] = ['', 'invalid']; + return $list; + } + + /** + * only short email type list + * + * @return array + */ + public function emailTypeProviderShort(): array + { + $list = []; + foreach ($this->emailTypeProvider() as $set) { + $list['email ' . $set[0] . ' is valid and matches short ' . $set[2]] = [$set[0], $set[2]]; + } + $list['email is empty and not valid short'] = ['', 'invalid']; + return $list; + } + + /** + * Undocumented function + * + * @covers ::getEmailType + * @dataProvider emailTypeProviderLong + * @testdox getEmailType $input will be normal $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testGetEmailTypeNormal(string $input, string $expected) + { + $this->assertEquals( + $expected, + \CoreLibs\Check\Email::getEmailType($input, false) + ); + } + + /** + * Undocumented function + * + * @covers ::getEmailType + * @dataProvider emailTypeProviderShort + * @testdox getEmailType $input will be short $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testGetEmailTypeShort(string $input, string $expected) + { + $this->assertEquals( + $expected, + \CoreLibs\Check\Email::getEmailType($input, true) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function emailProviderTypeLongToShort(): array + { + $mobile_email_type_short = [ + 'keitai_docomo' => 'docomo', + 'keitai_kddi_ezweb' => 'kddi', + 'keitai_kddi' => 'kddi', + 'keitai_kddi_tu-ka' => 'kddi', + 'keitai_kddi_sky' => 'kddi', + 'keitai_softbank' => 'softbank', + 'smartphone_softbank_iphone' => 'iphone', + 'keitai_softbank_disney' => 'softbank', + 'keitai_softbank_vodafone' => 'softbank', + 'keitai_softbank_j-phone' => 'softbank', + 'keitai_willcom' => 'willcom', + 'keitai_willcom_pdx' => 'willcom', + 'keitai_willcom_bandai' => 'willcom', + 'keitai_willcom_pipopa' => 'willcom', + 'keitai_willcom_ymobile' => 'willcom', + 'keitai_willcom_emnet' => 'willcom', + 'pc_html' => 'pc', + ]; + $list = []; + // use the static one + foreach ($mobile_email_type_short as $long => $short) { + $list[$long . ' matches to ' . $short] = [$long, $short]; + } + // add invalid check + $list['Not found will be bool false'] = ['invalid', false]; + return $list; + } + + /** + * Undocumented function + * + * @covers ::getShortEmailType + * @dataProvider emailProviderTypeLongToShort + * @testdox getShortEmailType $input will be $expected [$_dataName] + * + * @param string $input + * @param string|bool $expected + * @return void + */ + public function testGetShortEmailType(string $input, $expected) + { + $this->assertEquals( + $expected, + \CoreLibs\Check\Email::getShortEmailType($input) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCheckEncodingTest.php b/test/phpunit/CoreLibsCheckEncodingTest.php new file mode 100644 index 0000000..16aea2b --- /dev/null +++ b/test/phpunit/CoreLibsCheckEncodingTest.php @@ -0,0 +1,120 @@ + [ + '日本語', + 'UTF-8', + 'SJIS', + null, + false + ], + 'invalid test UTF-8 to SJIS (dots as code point)' => [ + '❶', + 'UTF-8', + 'SJIS', + 0x2234, + ['❶'] + ], + 'invalid test UTF-8 to SJIS (dots as string)' => [ + '❶', + 'UTF-8', + 'SJIS', + '∴', + ['❶'] + ], + 'invalid test UTF-8 to SJIS (none)' => [ + '❶', + 'UTF-8', + 'SJIS', + 'none', + ['❶'] + ], + 'invalid test UTF-8 to SJIS (long)' => [ + '❶', + 'UTF-8', + 'SJIS', + 'long', + ['❶'] + ], + 'invalid test UTF-8 to SJIS (entity)' => [ + '❶', + 'UTF-8', + 'SJIS', + 'entity', + ['❶'] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::checkConvertEncoding + * @dataProvider checkConvertEncodingProvider + * @testdox check encoding convert from $from_encoding to $to_encoding [$_dataName] + * + * @param string $input + * @param string $from_encoding + * @param string $to_encoding + * @param string|int|null $error_char + * @param array|bool $expected + * @return void + */ + public function testCheckConvertEncoding( + string $input, + string $from_encoding, + string $to_encoding, + $error_char, + $expected + ): void { + $current_subsitute_character = mb_substitute_character(); + if ($error_char !== null) { + \CoreLibs\Check\Encoding::setErrorChar($error_char); + if (!in_array($error_char, ['none', 'long', 'entity'])) { + $this->assertEquals( + \IntlChar::chr($error_char), + \CoreLibs\Check\Encoding::getErrorChar() + ); + } else { + $this->assertEquals( + $error_char, + \CoreLibs\Check\Encoding::getErrorChar() + ); + } + } + $return = \CoreLibs\Check\Encoding::checkConvertEncoding($input, $from_encoding, $to_encoding); + $this->assertEquals( + $expected, + $return + ); + // reset after test + mb_substitute_character($current_subsitute_character); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCheckFileTest.php b/test/phpunit/CoreLibsCheckFileTest.php new file mode 100644 index 0000000..c94ad94 --- /dev/null +++ b/test/phpunit/CoreLibsCheckFileTest.php @@ -0,0 +1,120 @@ + */ + // private $files_list = []; + /** @var string */ + private $base_folder = DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + + /** + * main file list + data provider + * + * filename, file extension matching, lines in file, -1 for nothing + * + * @return array + */ + public function filesList(): array + { + return [ + ['filename.txt', 'txt', 5], + ['filename.csv', 'csv', 15], + ['filename.tsv', 'tsv', 0], + ['file_does_not_exits', '', -1], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function filesExtensionProvider(): array + { + $list = []; + foreach ($this->filesList() as $row) { + $list[$row[0] . ' must be extension ' . $row[1]] = [$row[0], $row[1]]; + } + return $list; + } + + /** + * Undocumented function + * + * @return array + */ + public function filesLinesProvider(): array + { + $list = []; + foreach ($this->filesList() as $row) { + $list[$row[0] . ' must have ' . $row[2] . ' lines'] = [$row[0], $row[2]]; + } + return $list; + } + + /** + * Tests if file extension matches + * + * @covers ::getFilenameEnding + * @dataProvider filesExtensionProvider + * @testdox getFilenameEnding $input must be extension $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testGetFilenameEnding(string $input, string $expected): void + { + // getFilenameEnding + $this->assertEquals( + $expected, + \CoreLibs\Check\File::getFilenameEnding($input) + ); + } + + /** + * Tests the file line read + * + * @covers ::getLinesFromFile + * @dataProvider filesLinesProvider + * @testdox getLinesFromFile $input must have $expected lines [$_dataName] + * + * @param string $input file name + * @param int $expected lines in file + * @return void + */ + public function testGetLinesFromFile(string $input, int $expected): void + { + // create file + if ($expected > -1) { + $file = $this->base_folder . $input; + $fp = fopen($file, 'w'); + for ($i = 0; $i < $expected; $i++) { + fwrite($fp, 'This is row ' . ($i + 1) . PHP_EOL); + } + fclose($fp); + } + // test + $this->assertEquals( + $expected, + \CoreLibs\Check\File::getLinesFromFile($this->base_folder . $input) + ); + // unlink file + if (is_file($this->base_folder . $input)) { + unlink($this->base_folder . $input); + } + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCheckPasswordTest.php b/test/phpunit/CoreLibsCheckPasswordTest.php new file mode 100644 index 0000000..ce6a749 --- /dev/null +++ b/test/phpunit/CoreLibsCheckPasswordTest.php @@ -0,0 +1,73 @@ + ['test', 'test', true], + 'not matching password' => ['test', 'not_test', false], + ]; + } + + public function passwordRehashProvider(): array + { + return [ + 'no rehash needed' => ['$2y$10$EgWJ2WE73DWi.hIyFRCdpejLXTvHbmTK3LEOclO1tAvXAXUNuUS4W', false], + 'rehash needed' => ['9c42a1346e333a770904b2a2b37fa7d3', true], + ]; + } + + /** + * Undocumented function + * + * @covers ::passwordVerify + * @covers ::passwordSet + * @dataProvider passwordProvider + * @testdox passwordSet $input compare to $input_hash: passwordVerify $expected [$_dataName] + * + * @param string $input + * @param string $input_hash + * @param boolean $expected + * @return void + */ + public function testPasswordSetVerify(string $input, string $input_hash, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Check\Password::passwordVerify($input, \CoreLibs\Check\Password::passwordSet($input_hash)) + ); + } + + /** + * Undocumented function + * + * @covers ::passwordRehashCheck + * @dataProvider passwordRehashProvider + * @testdox passwordRehashCheck $input will be $expected [$_dataName] + * + * @param string $input + * @param boolean $expected + * @return void + */ + public function testPasswordRehashCheck(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Check\Password::passwordRehashCheck($input) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCheckPhpVersionTest.php b/test/phpunit/CoreLibsCheckPhpVersionTest.php new file mode 100644 index 0000000..113a5e1 --- /dev/null +++ b/test/phpunit/CoreLibsCheckPhpVersionTest.php @@ -0,0 +1,73 @@ + ['7', '', true], + 'min 7.4' => ['7.4', '', true], + 'min 7.4.1' => ['7.4.1', '', true], + // NOTE: update if php version bigger than 10 + 'min 10' => ['10', '', false], + 'min 10.0' => ['10.0', '', false], + 'min 10.0.0' => ['10.0.0', '', false], + // min/max version, NOTE: update if php version bigger than 10 + 'min 7/max 10' => ['7', '10', true], + 'min 7/max 10.0' => ['7', '10.0', true], + 'min 7/max 10.0.0' => ['7', '10.0.0', true], + // min/max version + 'min 5/max 7' => ['5', '7', false], + 'min 5/max 7.4' => ['5', '7.4', false], + 'min 5/max 7.4.1' => ['5', '7.4.1', false], + // max only + 'max 7' => ['', '7', false], + 'max 7.4' => ['', '7.4', false], + 'max 7.4.1' => ['', '7.4.1', false], + // max over + 'max 10' => ['', '10', true], + 'max 10.0' => ['', '10.0', true], + 'max 10.0.0' => ['', '10.0.0', true], + // TODO: add null tests + ]; + } + + /** + * Undocumented function + * + * @covers ::checkPHPVersion + * @dataProvider phpVersionProvider + * @testdox checkPHPVersion $input_min and $input_max will be $expected [$_dataName] + * + * @param string $input_min + * @param string $input_max + * @param string $expected + * @return void + */ + public function testCheckPHPVersion(string $input_min, string $input_max, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Check\PhpVersion::checkPHPVersion($input_min, $input_max) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCombinedArrayHandlerTest.php b/test/phpunit/CoreLibsCombinedArrayHandlerTest.php new file mode 100644 index 0000000..cd6a70f --- /dev/null +++ b/test/phpunit/CoreLibsCombinedArrayHandlerTest.php @@ -0,0 +1,862 @@ + [ + 'b' => 'bar', + 'c' => 'foo', + 'same' => 'same', + 3 => 'foobar', + 'foobar' => 4, + 'true' => true, + ], + 'd', + 4, + 'b', + 'c' => 'test', + 'same' => 'same', + 'deep' => [ + 'sub' => [ + 'nested' => 'bar', + 'same' => 'same', + 'more' => 'test' + ] + ] + ]; + + /** + * Undocumented function + * + * @return array + */ + public function arraySearchRecursiveProvider(): array + { + return [ + 'find value' => [ + 0 => 'bar', + 1 => self::$array, + 2 => null, + 3 => ['a', 'b'], + ], + 'find value with key' => [ + 0 => 'bar', + 1 => self::$array, + 2 => 'nested', + 3 => ['deep', 'sub', 'nested'] + ], + 'not existing value' => [ + 0 => 'not exists', + 1 => self::$array, + 2 => null, + 3 => [], + ], + 'find value int' => [ + 0 => 4, + 1 => self::$array, + 2 => null, + 3 => ['a', 'foobar'] + ], + 'find value int as string' => [ + 0 => '4', + 1 => self::$array, + 2 => null, + 3 => [] + ], + 'find value int as string with key' => [ + 0 => '4', + 1 => self::$array, + 2 => 'foobar', + 3 => [] + ], + 'first level value' => [ + 0 => 'd', + 1 => self::$array, + 2 => null, + 4 => [0] + ], + 'find value, return int key' => [ + 0 => 'foobar', + 1 => self::$array, + 2 => null, + 3 => ['a', 3] + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function arraySearchRecursiveAllProvider(): array + { + return [ + 'find value' => [ + 0 => 'bar', + 1 => self::$array, + 2 => null, + 3 => true, + 4 => [ + 'level' => -1, + 'work' => [], + 'found' => [ + 0 => ['a', 'b'], + 1 => ['deep', 'sub', 'nested'] + ] + ] + ], + 'find value, new type' => [ + 0 => 'bar', + 1 => self::$array, + 2 => null, + 3 => false, + 4 => [ + 0 => ['a', 'b'], + 1 => ['deep', 'sub', 'nested'] + ] + ], + 'find value with key' => [ + 0 => 'bar', + 1 => self::$array, + 2 => 'nested', + 3 => true, + 4 => [ + 'level' => -1, + 'work' => [], + 'found' => [ + 0 => ['deep', 'sub', 'nested'] + ] + ] + ], + 'not existing value' => [ + 0 => 'not exists', + 1 => self::$array, + 2 => null, + 3 => true, + 4 => [ + 'level' => -1, + 'work' => [], + ], + ], + 'not existing value, new type' => [ + 0 => 'not exists', + 1 => self::$array, + 2 => null, + 3 => false, + 4 => [], + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function arraySearchSimpleProvider(): array + { + return [ + 'key/value exist' => [ + 0 => self::$array, + 1 => 'c', + 2 => 'foo', + 3 => false, + 4 => true, + ], + 'key/value exists twice' => [ + 0 => self::$array, + 1 => 'same', + 2 => 'same', + 3 => false, + 4 => true, + ], + 'key/value not found' => [ + 0 => self::$array, + 1 => 'not exists', + 2 => 'not exists', + 3 => false, + 4 => false, + ], + 'key exists, value not' => [ + 0 => self::$array, + 1 => 'b', + 2 => 'not exists', + 3 => false, + 4 => false, + ], + 'key not, value exists' => [ + 0 => self::$array, + 1 => 'not exists', + 2 => 'bar', + 3 => false, + 4 => false, + ], + 'numeric key, value exists' => [ + 0 => self::$array, + 1 => 0, + 2 => 'd', + 3 => false, + 4 => true, + ], + 'numeric key as string, value exists' => [ + 0 => self::$array, + 1 => '0', + 2 => 'd', + 3 => false, + 4 => true, + ], + 'numeric key as string, value exists, strinct' => [ + 0 => self::$array, + 1 => '0', + 2 => 'd', + 3 => true, + 4 => false, + ], + 'key exists, value numeric' => [ + 0 => self::$array, + 1 => 'foobar', + 2 => 4, + 3 => false, + 4 => true, + ], + 'key exists, value numeric as string' => [ + 0 => self::$array, + 1 => 'foobar', + 2 => '4', + 3 => false, + 4 => true, + ], + 'key exists, value numeric as string, strict' => [ + 0 => self::$array, + 1 => 'foobar', + 2 => '4', + 3 => true, + 4 => false, + ], + 'key exists, value bool' => [ + 0 => self::$array, + 1 => 'true', + 2 => true, + 3 => false, + 4 => true, + ], + 'key exists, value bool as string' => [ + 0 => self::$array, + 1 => 'true', + 2 => 'true', + 3 => false, + 4 => true, + ], + 'key exists, value bool as string, strict' => [ + 0 => self::$array, + 1 => 'true', + 2 => 'true', + 3 => true, + 4 => false, + ], + ]; + } + + /** + * provides array listing for the merge test + * + * @return array + */ + public function arrayMergeRecursiveProvider(): array + { + return [ + // 0: expected + // 1..n: to merge arrays + // n+1: trigger for handle keys as string + 'two arrays' => [ + ['a' => 1, 'b' => 2, 'c' => 3], + ['a' => 1, 'b' => 2], + ['b' => 2, 'c' => 3], + ], + 'two arrays, string flag' => [ + ['a' => 1, 'b' => 2, 'c' => 3], + ['a' => 1, 'b' => 2], + ['b' => 2, 'c' => 3], + true, + ], + // non hash arrays + 'non hash array merge, no string flag' => [ + [3, 4, 5], + [1, 2, 3], + [3, 4, 5], + ], + 'non hash array merge, string flag' => [ + [1, 2, 3, 3, 4, 5], + [1, 2, 3], + [3, 4, 5], + true + ], + ]; + } + + /** + * for warning checks + * + * @return array + */ + public function arrayMergeRecursiveProviderWarning(): array + { + return [ + // error <2 arguments + 'too view arguments' => [ + 'arrayMergeRecursive needs two or more array arguments', + [1] + ], + // error <2 arrays + 'only one array' => [ + 'arrayMergeRecursive needs two or more array arguments', + [1], + true, + ], + // error element is not array + 'non array between array' => [ + 'arrayMergeRecursive encountered a non array argument', + [1], + 'string', + [2] + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function arrayCompareProvider(): array + { + return [ + 'one matching' => [ + ['a', 'b', 'c'], + ['c', 'd', 'e'], + ['a', 'b', 'd', 'e'] + ], + 'all the same' => [ + ['a', 'b', 'c'], + ['a', 'b', 'c'], + [] + ], + 'all different' => [ + ['a', 'b'], + ['c', 'd'], + ['a', 'b', 'c', 'd'] + ], + 'empty arrays' => [ + [], + [], + [] + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function inArrayAnyProvider(): array + { + return [ + 'all exist in haystack' => [ + [1], + [1, 2, 3, 4], + [1] + ], + 'not all exist in haystack' => [ + [1, 5], + [1, 2, 3, 4], + [1] + ], + 'none exist in haystack' => [ + [5], + [1, 2, 3, 4], + false + ], + ]; + } + + public function genAssocArrayProvider(): array + { + return [ + 'non set' => [ + [ + 0 => ['a' => 'a1', 'b' => 2], + 1 => ['a' => 'a2', 'b' => 3], + 2 => ['a' => '', 'b' => null], + ], + false, + false, + false, + [], + ], + 'key set' => [ + [ + 0 => ['a' => 'a1', 'b' => 2], + 1 => ['a' => 'a2', 'b' => 3], + 2 => ['a' => '', 'b' => null], + ], + 'a', + false, + false, + ['a1' => 0, 'a2' => 1], + ], + 'value set' => [ + [ + 0 => ['a' => 'a1', 'b' => 2], + 1 => ['a' => 'a2', 'b' => 3], + 2 => ['a' => '', 'b' => null], + ], + false, + 'a', + false, + [0 => 'a1', 1 => 'a2', 2 => ''], + ], + 'key and value set, add empty, null' => [ + [ + 0 => ['a' => 'a1', 'b' => 2], + 1 => ['a' => 'a2', 'b' => 3], + 2 => ['a' => '', 'b' => null], + ], + 'a', + 'b', + false, + ['a1' => 2, 'a2' => 3], + ], + 'key and value set, add empty' => [ + [ + 0 => ['a' => 'a1', 'b' => 2], + 1 => ['a' => 'a2', 'b' => 3], + 2 => ['a' => '', 'b' => ''], + 3 => ['a' => 'a4', 'b' => ''], + ], + 'a', + 'b', + false, + ['a1' => 2, 'a2' => 3, 'a4' => ''], + ], + 'key/value set, skip empty' => [ + [ + 0 => ['a' => 'a1', 'b' => 2], + 1 => ['a' => 'a2', 'b' => 3], + 2 => ['a' => '', 'b' => null], + ], + 'a', + 'b', + true, + ['a1' => 2, 'a2' => 3], + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function flattenArrayProvider(): array + { + return [ + 'array key/value, single' => [ + 0 => ['a' => 'foo', 1 => 'bar', 'c' => 2], + 1 => ['foo', 'bar', 2], + 2 => ['a', 1, 'c'], + 3 => ['a', 1, 'c'], + ], + 'array values, single' => [ + 0 => ['foo', 'bar', 2], + 1 => ['foo', 'bar', 2], + 2 => [0, 1, 2], + 3 => [0, 1, 2], + ], + 'array key/value, multi' => [ + 0 => [ + 'a' => ['a1' => 'a1foo', 'a2' => 'a1bar'], + 1 => 'bar', + 'c' => [2, 3, 4], + 'd' => [ + 'e' => [ + 'de1' => 'subfoo', 'de2' => 'subbar', 'a2' => 'a1bar' + ] + ] + ], + 1 => ['a1foo', 'a1bar', 'bar', 2, 3, 4, 'subfoo', 'subbar', 'a1bar'], + 2 => ['a', 'a1', 'a2', 1, 'c', 0, 1, 2, 'd', 'e', 'de1', 'de2', 'a2'], + 3 => ['a1', 'a2', 1, 0, 1, 2, 'de1', 'de2', 'a2'], + ], + 'array with double values' => [ + 0 => ['a', 'a', 'b'], + 1 => ['a', 'a', 'b'], + 2 => [0, 1, 2], + 3 => [0, 1, 2], + ] + ]; + } + + /** + * use the flattenArrayProvider and replace 1 with 2 array pos + * + * @return array + */ + public function flattenArrayKeyProvider(): array + { + $list = []; + foreach ($this->flattenArrayProvider() as $key => $row) { + $list[$key] = [ + $row[0], + $row[2], + ]; + } + return $list; + } + + /** + * use the flattenArrayProvider and replace 1 with 3 array pos + * + * @return array + */ + public function flattenArrayKeyLeavesOnlyProvider(): array + { + $list = []; + foreach ($this->flattenArrayProvider() as $key => $row) { + $list[$key] = [ + $row[0], + $row[3], + ]; + } + return $list; + } + + /** + * Undocumented function + * + * @return array + */ + public function arrayFlatForKeyProvider(): array + { + return [ + 'all present, single level' => [ + 0 => [ + 'a' => ['b1' => 'foo', 'a2' => 'a-foo'], + 'b' => ['b1' => 'bar', 'a2' => 'b-foo'], + 'c' => ['b1' => 'foobar', 'a2' => 'c-foo'], + ], + 1 => 'a2', + 2 => [ + 'a' => 'a-foo', + 'b' => 'b-foo', + 'c' => 'c-foo', + ], + ], + 'no sub arrays' => [ + 0 => ['a', 'b', 'c'], + 1 => 'a', + 2 => ['a', 'b', 'c'], + ], + 'sub arrays with missing' => [ + 0 => [ + 'a' => ['b1' => 'foo', 'a2' => 'a-foo'], + 'b' => ['b1' => 'bar'], + 'c' => ['b1' => 'foobar', 'a2' => 'c-foo'], + ], + 1 => 'a2', + 2 => [ + 'a' => 'a-foo', + 'b' => ['b1' => 'bar'], + 'c' => 'c-foo', + ], + ], + 'deep nested sub arrays' => [ + 0 => [ + 'a' => [ + 'b1' => 'foo', + 'a2' => [ + 'text' => ['a-foo', 'a-bar'], + ], + ], + 'b' => [ + 'b1' => 'bar', + 'a2' => [ + 'text' => 'b-foo', + ], + ], + ], + 1 => 'a2', + 2 => [ + 'a' => [ + 'text' => ['a-foo', 'a-bar'], + ], + 'b' => [ + 'text' => 'b-foo', + ], + ], + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::arraySearchRecursive + * @dataProvider arraySearchRecursiveProvider + * @testdox arraySearchRecursive $needle (key $key_search_for) in $input and will be $expected [$_dataName] + * + * @param string|null $needle + * @param array $input + * @param string|null $key_search_for + * @return void + */ + public function testArraySearchRecursive($needle, array $input, ?string $key_search_for, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::arraySearchRecursive($needle, $input, $key_search_for) + ); + } + + /** + * Undocumented function + * + * @covers ::arraySearchRecursiveAll + * @dataProvider arraySearchRecursiveAllProvider + * @testdox arraySearchRecursiveAll $needle (key $key_search_for) in $input and will be $expected (old: $flag) [$_dataName] + * + * @param string|null $needle + * @param array $input + * @param string|null $key_search_for + * @param bool $flag + * @return void + */ + public function testArraySearchRecursiveAll($needle, array $input, ?string $key_search_for, bool $flag, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::arraySearchRecursiveAll($needle, $input, $key_search_for, $flag) + ); + } + + /** + * Undocumented function + * + * @covers ::arraySearchSimple + * @dataProvider arraySearchSimpleProvider + * @testdox arraySearchSimple $input searched with key: $key / value: $value (strict: $flag) will be $expected [$_dataName] + * + * @param array $input + * @param string|int $key + * @param string|int $value + * @param bool $expected + * @return void + */ + public function testArraySearchSimple(array $input, $key, $value, bool $flag, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::arraySearchSimple($input, $key, $value, $flag) + ); + } + + /** + * Undocumented function + * + * @covers ::arrayMergeRecursive + * @dataProvider arrayMergeRecursiveProvider + * @testdox arrayMergeRecursive ... [$_dataName] + * + * @return void + * + */ + public function testArrayMergeRecursive(): void + { + $arrays = func_get_args(); + // first is expected array, always + $expected = array_shift($arrays); + $output = \CoreLibs\Combined\ArrayHandler::arrayMergeRecursive( + ...$arrays + ); + $this->assertEquals( + $expected, + $output + ); + } + + /** + * Undocumented function + * + * @covers ::arrayMergeRecursive + * @dataProvider arrayMergeRecursiveProviderWarning + * @testdox arrayMergeRecursive with E_USER_WARNING [$_dataName] + * + * @return void + */ + public function testArrayMergeRecursiveWarningA(): void + { + $arrays = func_get_args(); + // first is expected warning + $warning = array_shift($arrays); + $this->expectWarning(); + $this->expectWarningMessage($warning); + \CoreLibs\Combined\ArrayHandler::arrayMergeRecursive(...$arrays); + } + + /** + * Undocumented function + * + * @covers ::arrayDiff + * @dataProvider arrayCompareProvider + * @testdox arrayDiff $input_a diff $input_b will be $expected [$_dataName] + * + * @param array $input_a + * @param array $input_b + * @param array $expected + * @return void + */ + public function testArrayDiff(array $input_a, array $input_b, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::arrayDiff($input_a, $input_b) + ); + } + + /** + * Undocumented function + * + * @covers ::inArrayAny + * @dataProvider inArrayAnyProvider + * @testdox inArrayAny needle $input_a in haystack $input_b will be $expected [$_dataName] + * + * @param array $input_a + * @param array $input_b + * @param array|bool $expected + * @return void + */ + public function testInArrayAny(array $input_a, array $input_b, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::inArrayAny($input_a, $input_b) + ); + } + + /** + * Undocumented function + * + * @covers ::genAssocArray + * @dataProvider genAssocArrayProvider + * @testdox genAssocArray array $input with $key or $value and flag set only $flag will be $expected [$_dataName] + * + * @param array $input + * @param string|int|bool $key + * @param string|int|bool $value + * @param bool $flag + * @param array $expected + * @return void + */ + public function testGenAssocArray(array $input, $key, $value, bool $flag, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::genAssocArray($input, $key, $value, $flag) + ); + } + + /** + * Undocumented function + * + * @covers ::flattenArray + * @dataProvider flattenArrayProvider + * @testdox testFlattenArray array $input will be $expected [$_dataName] + * + * @param array $input + * @param array $expected + * @return void + */ + public function testFlattenyArray(array $input, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::flattenArray($input) + ); + } + + /** + * Undocumented function + * + * @covers ::flattenArrayKey + * @dataProvider flattenArrayKeyProvider + * @testdox flattenArrayKey array $input will be $expected [$_dataName] + * + * @param array $input + * @param array $expected + * @return void + */ + public function testFlattenArrayKey(array $input, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::flattenArrayKey($input) + ); + } + + /** + * Undocumented function + * + * @covers ::flattenArrayKeyLeavesOnly + * @dataProvider flattenArrayKeyLeavesOnlyProvider + * @testdox flattenArrayKeyLeavesOnly array $input will be $expected [$_dataName] + * + * @param array $input + * @param array $expected + * @return void + */ + public function testFlattenArrayKeyLeavesOnly(array $input, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::flattenArrayKeyLeavesOnly($input) + ); + } + + /** + * Undocumented function + * + * @covers ::arrayFlatForKey + * @dataProvider arrayFlatForKeyProvider + * @testdox arrayFlatForKey array $input will be $expected [$_dataName] + * + * @param array $input + * @param array $expected + * @return void + */ + public function testArrayFlatForKey(array $input, $search, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::arrayFlatForKey($input, $search) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCombinedDateTimeTest.php b/test/phpunit/CoreLibsCombinedDateTimeTest.php new file mode 100644 index 0000000..2dd2480 --- /dev/null +++ b/test/phpunit/CoreLibsCombinedDateTimeTest.php @@ -0,0 +1,785 @@ + [ + 1641515890, + false, + false, + '2022-01-07 09:38:10', + ], + 'valid timestamp with microtime' => [ + 1641515890, + true, + false, + '2022-01-07 09:38:10', + ], + 'valid timestamp with microtime float' => [ + 1641515890, + true, + true, + '2022-01-07 09:38:10', + ], + 'valid micro timestamp with microtime' => [ + 1641515890.123456, + true, + false, + '2022-01-07 09:38:10 1235ms', + ], + 'valid micro timestamp with microtime float' => [ + 1641515890.123456, + true, + true, + '2022-01-07 09:38:10.1235', + ], + 'valid micro timestamp no microtime' => [ + 1641515890.123456, + false, + false, + '2022-01-07 09:38:10', + ], + 'invalid timestamp' => [ + -123123, + false, + false, + '1969-12-30 22:47:57', + ], + ]; + } + + /** + * interval for both directions + * + * @return array + */ + public function intervalProvider(): array + { + return [ + 'interval no microtime' => [ + 1641515890, + false, + '18999d 0h 38m 10s', + ], + 'interval with microtime' => [ + 1641515890, + true, + '18999d 0h 38m 10s', + ], + 'micro interval no microtime' => [ + 1641515890.123456, + false, + '18999d 0h 38m 10s', + ], + 'micro interval with microtime' => [ + 1641515890.123456, + true, + '18999d 0h 38m 10s 1235ms', + ], + 'negative interval no microtime' => [ + -1641515890, + false, + '-18999d 0h 38m 10s', + ], + // short for mini tests + 'microtime only' => [ + 0.123456, + true, + '0s 1235ms', + ], + 'seconds only' => [ + 30.123456, + true, + '30s 1235ms', + ], + 'minutes only' => [ + 90.123456, + true, + '1m 30s 1235ms', + ], + 'hours only' => [ + 3690.123456, + true, + '1h 1m 30s 1235ms', + ], + 'days only' => [ + 90090.123456, + true, + '1d 1h 1m 30s 1235ms', + ], + 'already set' => [ + '1d 1h 1m 30s 1235ms', + true, + '1d 1h 1m 30s 1235ms', + ], + 'invalid data' => [ + 'xyz', + true, + '0s', + ], + 'out of bounds timestamp' => [ + 999999999999999, + false, + '1s' + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function reverseIntervalProvider(): array + { + return [ + 'interval no microtime' => [ + '18999d 0h 38m 10s', + 1641515890, + ], + 'micro interval with microtime' => [ + '18999d 0h 38m 10s 1235ms', + 1641515890.1235, + ], + 'micro interval with microtime' => [ + '18999d 0h 38m 10s 1234567890ms', + 1641515890.1234567, + ], + 'negative interval no microtime' => [ + '-18999d 0h 38m 10s', + -1641515890, + ], + // short for mini tests + 'microtime only' => [ + '0s 1235ms', + 0.1235, + ], + 'seconds only' => [ + '30s 1235ms', + 30.1235, + ], + 'minutes only' => [ + '1m 30s 1235ms', + 90.1235, + ], + 'hours only' => [ + '1h 1m 30s 1235ms', + 3690.1235, + ], + 'days only' => [ + '1d 1h 1m 30s 1235ms', + 90090.1235, + ], + 'already set' => [ + 1641515890, + 1641515890, + ], + 'invalid data' => [ + 'xyz', + 'xyz', + ], + 'out of bound data' => [ + '99999999999999999999d', + 8.64E+24 + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function dateProvider(): array + { + return [ + 'valid date with -' => [ + '2021-12-12', + true, + ], + 'valid date with /' => [ + '2021/12/12', + true, + ], + 'valid date time with -' => [ + '2021-12-12 12:12:12', + true, + ], + 'invalid date' => [ + '2021-31-31', + false, + ], + 'invalid date string' => [ + 'xyz', + false, + ], + 'out of bound date' => [ + '9999-12-31', + true + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function dateTimeProvider(): array + { + return [ + 'valid date time with -' => [ + '2021-12-12 12:12:12', + true, + ], + 'valid date time with /' => [ + '2021/12/12 12:12:12', + true, + ], + 'vald date time with hour/min' => [ + '2021/12/12 12:12', + true, + ], + 'valid date missing time' => [ + '2021-12-12', + false, + ], + 'valid date invalid time string' => [ + '2021-12-12 ab:cd', + false, + ], + 'invalid hour +' => [ + '2021-12-12 35:12', + false, + ], + 'invalid hour -' => [ + '2021-12-12 -12:12', + false, + ], + 'invalid minute +' => [ + '2021-12-12 23:65:12', + false, + ], + 'invalid minute -' => [ + '2021-12-12 23:-12:12', + false, + ], + 'invalid seconds +' => [ + '2021-12-12 23:12:99', + false, + ], + 'invalid seconds -' => [ + '2021-12-12 23:12:-12', + false, + ], + 'invalid seconds string' => [ + '2021-12-12 23:12:ss', + false, + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function dateCompareProvider(): array + { + return [ + 'first date smaller' => [ + '2020-12-12', + '2021-12-12', + -1, + ], + 'dates equal' => [ + '2020-12-12', + '2020-12-12', + 0, + ], + 'second date smaller' => [ + '2021-12-12', + '2020-12-12', + 1 + ], + 'dates equal with different time' => [ + '2020-12-12 12:12:12', + '2020-12-12 13:13:13', + 0, + ], + 'invalid dates --' => [ + '--', + '--', + false + ], + 'empty dates' => [ + '', + '', + false + ], + 'invalid dates' => [ + 'not a date', + 'not a date either', + false, + ], + 'out of bound dates' => [ + '1900-1-1', + '9999-12-31', + -1 + ] + ]; + } + + public function dateTimeCompareProvider(): array + { + return [ + 'first date smaller no time' => [ + '2020-12-12', + '2021-12-12', + -1, + ], + 'dates equal no timestamp' => [ + '2020-12-12', + '2020-12-12', + 0, + ], + 'second date smaller no timestamp' => [ + '2021-12-12', + '2020-12-12', + 1 + ], + 'date equal first time smaller' => [ + '2020-12-12 12:12:12', + '2020-12-12 13:13:13', + -1, + ], + 'date equal time equal' => [ + '2020-12-12 12:12:12', + '2020-12-12 12:12:12', + 0, + ], + 'date equal second time smaller' => [ + '2020-12-12 13:13:13', + '2020-12-12 12:12:12', + 1, + ], + 'valid date invalid time' => [ + '2020-12-12 13:99:13', + '2020-12-12 12:12:99', + false, + ], + 'invalid datetimes --' => [ + '--', + '--', + false, + ], + 'empty datetimess' => [ + '', + '', + false, + ], + 'invalid datetimes' => [ + 'not a date', + 'not a date either', + false, + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function daysIntervalProvider(): array + { + return [ + 'valid interval /, not named array' => [ + '2020/1/1', + '2020/1/30', + false, + [29, 22, 8], + ], + 'valid interval /, named array' => [ + '2020/1/1', + '2020/1/30', + true, + ['overall' => 29, 'weekday' => 22, 'weekend' => 8], + ], + 'valid interval -' => [ + '2020-1-1', + '2020-1-30', + false, + [29, 22, 8], + ], + 'valid interval switched' => [ + '2020/1/30', + '2020/1/1', + false, + [28, 0, 0], + ], + 'valid interval with time' => [ + '2020/1/1 12:12:12', + '2020/1/30 13:13:13', + false, + [28, 21, 8], + ], + 'invalid dates' => [ + 'abc', + 'xyz', + false, + [0, 0, 0] + ], + // this test will take a long imte + 'out of bound dates' => [ + '1900-1-1', + '9999-12-31', + false, + [2958463,2113189,845274], + ], + ]; + } + + /** + * date string convert test + * + * @covers ::dateStringFormat + * @dataProvider timestampProvider + * @testdox dateStringFormat $input (microtime $flag) will be $expected [$_dataName] + * + * @param int|float $input + * @param bool $flag + * @param string $expected + * @return void + */ + public function testDateStringFormat( + $input, + bool $flag_show_micro, + bool $flag_micro_as_float, + string $expected + ): void { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::dateStringFormat( + $input, + $flag_show_micro, + $flag_micro_as_float + ) + ); + } + + /** + * interval convert test + * + * @covers ::timeStringFormat + * @dataProvider intervalProvider + * @testdox timeStringFormat $input (microtime $flag) will be $expected [$_dataName] + * + * @param int|float $input + * @param bool $flag + * @param string $expected + * @return void + */ + public function testTimeStringFormat($input, bool $flag, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::timeStringFormat($input, $flag) + ); + } + + /** + * Undocumented function + * + * @covers ::stringToTime + * @dataProvider reverseIntervalProvider + * @testdox stringToTime $input will be $expected [$_dataName] + * + * @param string|int|float $input + * @param string|int|float $expected + * @return void + */ + public function testStringToTime($input, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::stringToTime($input) + ); + } + + /** + * Undocumented function + * + * @covers ::checkDate + * @dataProvider dateProvider + * @testdox checkDate $input will be $expected [$_dataName] + * + * @param string $input + * @param bool $expected + * @return void + */ + public function testCheckDate(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::checkDate($input) + ); + } + + /** + * Undocumented function + * + * @covers ::checkDateTime + * @dataProvider dateTimeProvider + * @testdox checkDateTime $input will be $expected [$_dataName] + * + * @param string $input + * @param bool $expected + * @return void + */ + public function testCheckDateTime(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::checkDateTime($input) + ); + } + + /** + * Undocumented function + * + * @covers ::compareDate + * @dataProvider dateCompareProvider + * @testdox compareDate $input_a compared to $input_b will be $expected [$_dataName] + * + * @param string $input_a + * @param string $input_b + * @param int|bool $expected + * @return void + */ + public function testCompareDate(string $input_a, string $input_b, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::compareDate($input_a, $input_b) + ); + } + + /** + * Undocumented function + * + * @covers ::compareDateTime + * @dataProvider dateTimeCompareProvider + * @testdox compareDateTime $input_a compared to $input_b will be $expected [$_dataName] + * + * @param string $input_a + * @param string $input_b + * @param int|bool $expected + * @return void + */ + public function testCompareDateTime(string $input_a, string $input_b, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::compareDateTime($input_a, $input_b) + ); + } + + /** + * Undocumented function + * + * @covers ::calcDaysInterval + * @dataProvider daysIntervalProvider + * @testdox calcDaysInterval $input_a compared to $input_b will be $expected [$_dataName] + * @medium + * + * @param string $input_a + * @param string $input_b + * @param bool $flag + * @param array $expected + * @return void + */ + public function testCalcDaysInterval( + string $input_a, + string $input_b, + bool $flag, + $expected + ): void { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::calcDaysInterval($input_a, $input_b, $flag) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function weekdayNumberProvider(): array + { + return [ + '0 invalid' => [0, null, 'Inv',], + '0 invalid long' => [0, true, 'Invalid',], + '1 short' => [1, null, 'Mon',], + '1 long' => [1, true, 'Monday',], + '2 short' => [2, null, 'Tue',], + '2 long' => [2, true, 'Tuesday',], + '3 short' => [3, null, 'Wed',], + '3 long' => [3, true, 'Wednesday',], + '4 short' => [4, null, 'Thu',], + '4 long' => [4, true, 'Thursday',], + '5 short' => [5, null, 'Fri',], + '5 long' => [5, true, 'Friday',], + '6 short' => [6, null, 'Sat',], + '6 long' => [6, true, 'Saturday',], + '7 short' => [7, null, 'Sun',], + '7 long' => [7, true, 'Sunday',], + '8 invalid' => [8, null, 'Inv',], + '8 invalid long' => [8, true, 'Invalid',], + ]; + } + + /** + * int weekday number to string weekday + * + * @covers ::setWeekdayNameFromIsoDow + * @dataProvider weekdayNumberProvider + * @testdox weekdayListProvider $input (short $flag) will be $expected [$_dataName] + * + * @param int $input + * @param bool|null $flag + * @param string $expected + * @return void + */ + public function testSetWeekdayNameFromIsoDow( + int $input, + ?bool $flag, + string $expected + ): void { + if ($flag === null) { + $output = \CoreLibs\Combined\DateTime::setWeekdayNameFromIsoDow($input); + } else { + $output = \CoreLibs\Combined\DateTime::setWeekdayNameFromIsoDow($input, $flag); + } + $this->assertEquals( + $expected, + $output + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function weekdayDateProvider(): array + { + return [ + 'invalid date' => ['2022-02-31', -1], + '1: monday' => ['2022-07-25', 1], + '2: tuesday' => ['2022-07-26', 2], + '3: wednesday' => ['2022-07-27', 3], + '4: thursday' => ['2022-07-28', 4], + '5: friday' => ['2022-07-29', 5], + '6: saturday' => ['2022-07-30', 6], + '7: sunday' => ['2022-07-31', 7], + ]; + } + + /** + * date to weekday number + * + * @covers ::setWeekdayNumberFromDate + * @dataProvider weekdayDateProvider + * @testdox setWeekdayNumberFromDate $input will be $expected [$_dataName] + * + * @param string $input + * @param int $expected + * @return void + */ + public function testSetWeekdayNumberFromDate( + string $input, + int $expected + ): void { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::setWeekdayNumberFromDate($input) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function weekdayDateNameProvider(): array + { + return [ + 'invalid date short' => ['2022-02-31', null, 'Inv'], + 'invalid date long' => ['2022-02-31', true, 'Invalid'], + 'Mon short' => ['2022-07-25', null, 'Mon'], + 'Monday long' => ['2022-07-25', true, 'Monday'], + 'Tue short' => ['2022-07-26', null, 'Tue'], + 'Tuesday long' => ['2022-07-26', true, 'Tuesday'], + 'Wed short' => ['2022-07-27', null, 'Wed'], + 'Wednesday long' => ['2022-07-27', true, 'Wednesday'], + 'Thu short' => ['2022-07-28', null, 'Thu'], + 'Thursday long' => ['2022-07-28', true, 'Thursday'], + 'Fri short' => ['2022-07-29', null, 'Fri'], + 'Friday long' => ['2022-07-29', true, 'Friday'], + 'Sat short' => ['2022-07-30', null, 'Sat'], + 'Saturday long' => ['2022-07-30', true, 'Saturday'], + 'Sun short' => ['2022-07-31', null, 'Sun'], + 'Sunday long' => ['2022-07-31', true, 'Sunday'], + ]; + } + + /** + * date to weekday name + * + * @covers ::setWeekdayNameFromDate + * @dataProvider weekdayDateNameProvider + * @testdox setWeekdayNameFromDate $input (short $flag) will be $expected [$_dataName] + * + * @param string $input + * @param bool|null $flag + * @param string $expected + * @return void + */ + public function testSetWeekdayNameFromDate( + string $input, + ?bool $flag, + string $expected + ): void { + if ($flag === null) { + $output = \CoreLibs\Combined\DateTime::setWeekdayNameFromDate($input); + } else { + $output = \CoreLibs\Combined\DateTime::setWeekdayNameFromDate($input, $flag); + } + $this->assertEquals( + $expected, + $output + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertByteTest.php b/test/phpunit/CoreLibsConvertByteTest.php new file mode 100644 index 0000000..bf49af3 --- /dev/null +++ b/test/phpunit/CoreLibsConvertByteTest.php @@ -0,0 +1,280 @@ + [ + 0 => '1024', + 1 => '1 KB', + 2 => '1.02 KiB', + 3 => '1KB', + 4 => '1.00 KB', + 5 => '1.02KiB', + ], + 'invalud string number' => [ + 0 => '1024 MB', + 1 => '1024 MB', + 2 => '1024 MB', + 3 => '1024 MB', + 4 => '1024 MB', + 5 => '1024 MB', + ], + 'negative number' => [ + 0 => -123123123, + 1 => '-117.42 MB', + 2 => '-123.12 MiB', + 3 => '-117.42MB', + 4 => '-117.42 MB', + 5 => '-123.12MiB', + ], + 'kilobyte minus one' => [ + 0 => 999999, // KB-1 + 1 => '976.56 KB', + 2 => '1 MiB', + 3 => '976.56KB', + 4 => '976.56 KB', + 5 => '1MiB', + ], + 'megabyte minus one' => [ + 0 => 999999999, // MB-1 + 1 => '953.67 MB', + 2 => '1 GiB', + 3 => '953.67MB', + 4 => '953.67 MB', + 5 => '1GiB', + ], + 'megabyte' => [ + 0 => 254779258, + 1 => '242.98 MB', + 2 => '254.78 MiB', + 3 => '242.98MB', + 4 => '242.98 MB', + 5 => '254.78MiB', + ], + 'terabyte minus one' => [ + 0 => 999999999999999, // TB-1 + 1 => '909.49 TB', + 2 => '1 PiB', + 3 => '909.49TB', + 4 => '909.49 TB', + 5 => '1PiB', + ], + 'terabyte' => [ + 0 => 588795544887632, // TB-n + 1 => '535.51 TB', + 2 => '588.8 TiB', + 3 => '535.51TB', + 4 => '535.51 TB', + 5 => '588.8TiB', + ], + 'petabyte minus one' => [ + 0 => 999999999999999999, // PB-1 + 1 => '888.18 PB', + 2 => '1 EiB', + 3 => '888.18PB', + 4 => '888.18 PB', + 5 => '1EiB', + ], + 'max int value' => [ + 0 => 9223372036854775807, // MAX INT + 1 => '8 EB', + 2 => '9.22 EiB', + 3 => '8EB', + 4 => '8.00 EB', + 5 => '9.22EiB', + ], + 'exabyte minus 1' => [ + 0 => 999999999999999999999, // EB-1 + 1 => '867.36 EB', + 2 => '1000 EiB', + 3 => '867.36EB', + 4 => '867.36 EB', + 5 => '1000EiB', + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function byteStringProvider(): array + { + return [ + 'negative number' => [ + 0 => '-117.42 MB', + 1 => -123123794, + 2 => -117420000, + ], + 'megabyte' => [ + 0 => '242.98 MB', + 1 => 254782996, + 2 => 242980000 + ], + 'megabyte si' => [ + 0 => '254.78 MiB', + 1 => 267156193, + 2 => 254780000 + ], + 'petabyte' => [ + 0 => '1 EiB', + 1 => 1152921504606846976, + 2 => 1000000000000000000, + ], + 'max int' => [ + 0 => '8 EB', + 1 => -9223372036854775807 - 1, + 2 => 8000000000000000000, + ], + 'exabyte, overflow' => [ + 0 => '867.36EB', + 1 => 3873816255479021568, + 2 => 363028535651074048, + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::humanReadableByteFormat + * @dataProvider byteProvider + * @testdox humanReadableByteFormat $input will be $expected, $expected_si SI, $expected_no_space no space, $expected_adjust adjust, $expected_si_no_space SI/no space [$_dataName] + * + * @param string|int|float $input + * @param string $expected + * @param string $expected_si + * @param string $expected_no_space + * @param string $expected_adjust + * @param string $expected_si_no_space + * @return void + */ + public function testHumanReadableByteFormat( + $input, + string $expected, + string $expected_si, + string $expected_no_space, + string $expected_adjust, + string $expected_si_no_space + ): void { + // 1024 + $this->assertEquals( + $expected, + \CoreLibs\Convert\Byte::humanReadableByteFormat($input) + ); + // 1000 + $this->assertEquals( + $expected_si, + \CoreLibs\Convert\Byte::humanReadableByteFormat($input, \CoreLibs\Convert\Byte::BYTE_FORMAT_SI) + ); + // no space + $this->assertEquals( + $expected_no_space, + \CoreLibs\Convert\Byte::humanReadableByteFormat($input, \CoreLibs\Convert\Byte::BYTE_FORMAT_NOSPACE) + ); + // always 2 decimals + $this->assertEquals( + $expected_adjust, + \CoreLibs\Convert\Byte::humanReadableByteFormat($input, \CoreLibs\Convert\Byte::BYTE_FORMAT_ADJUST) + ); + // combined si + no space + $this->assertEquals( + $expected_si_no_space, + \CoreLibs\Convert\Byte::humanReadableByteFormat( + $input, + \CoreLibs\Convert\Byte::BYTE_FORMAT_SI | \CoreLibs\Convert\Byte::BYTE_FORMAT_NOSPACE + ) + ); + } + + /** + * Undocumented function + * + * @covers ::stringByteFormat + * @dataProvider byteStringProvider + * @testdox stringByteFormat $input will be $expected and $expected_si SI [$_dataName] + * + * @param string|int|float $input + * @param string|int|float $expected + * @param string|int|float $expected_si + * @return void + */ + public function testStringByteFormat($input, $expected, $expected_si): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Byte::stringByteFormat($input) + ); + $this->assertEquals( + $expected_si, + \CoreLibs\Convert\Byte::stringByteFormat($input, \CoreLibs\Convert\Byte::BYTE_FORMAT_SI) + ); + } + + /** + * Exceptions tests + * + * @covers ::humanReadableByteFormat + * @testWith [99] + * @testdox Test exception for humanReadableByteFormat with flag $flag + * + * @param int $flag + * @return void + */ + public function testHumanReadableByteFormatException(int $flag): void + { + $this->expectException(\Exception::class); + \CoreLibs\Convert\Byte::humanReadableByteFormat(12, $flag); + } + + /** + * Exceptions tests + * can only be 4, try 1,2 and over + * + * @covers ::stringByteFormat + * @testWith [1] + * [2] + * [99] + * @testdox Test exception for stringByteFormat with flag $flag + * + * @param int $flag + * @return void + */ + public function testStringByteFormatException(int $flag): void + { + $this->expectException(\Exception::class); + \CoreLibs\Convert\Byte::stringByteFormat(12, $flag); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertColorsTest.php b/test/phpunit/CoreLibsConvertColorsTest.php new file mode 100644 index 0000000..7b288f0 --- /dev/null +++ b/test/phpunit/CoreLibsConvertColorsTest.php @@ -0,0 +1,420 @@ + [ + 0 => 10, + 1 => 100, + 2 => 200, + 3 => '#0a64c8', + 4 => '0a64c8' + ], + 'gray' => [ + 0 => 12, + 1 => 12, + 2 => 12, + 3 => '#0c0c0c', + 4 => '0c0c0c', + ], + 'black' => [ + 0 => 0, + 1 => 0, + 2 => 0, + 3 => '#000000', + 4 => '000000', + ], + 'white' => [ + 0 => 255, + 1 => 255, + 2 => 255, + 3 => '#ffffff', + 4 => 'ffffff', + ], + 'invalid color red & green' => [ + 0 => -12, + 1 => 300, + 2 => 12, + 3 => false, + 4 => false + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function hex2rgbColorProvider(): array + { + return [ + 'color' => [ + 0 => '#0a64c8', + 1 => ['r' => 10, 'g' => 100, 'b' => 200], + 2 => '10,100,200', + 3 => ';', + 4 => '10;100;200', + ], + 'gray, long' => [ + 0 => '0c0c0c', + 1 => ['r' => 12, 'g' => 12, 'b' => 12], + 2 => '12,12,12', + 3 => ';', + 4 => '12;12;12', + ], + 'gray, short' => [ + 0 => 'ccc', + 1 => ['r' => 204, 'g' => 204, 'b' => 204], + 2 => '204,204,204', + 3 => ';', + 4 => '204;204;204', + ], + 'hex string with #' => [ + 0 => '#0c0c0c', + 1 => ['r' => 12, 'g' => 12, 'b' => 12], + 2 => '12,12,12', + 3 => ';', + 4 => '12;12;12', + ], + 'a too long hex string' => [ + 0 => '#0c0c0c0c', + 1 => false, + 2 => false, + 3 => ';', + 4 => false, + ], + 'a too short hex string' => [ + 0 => '0c0c', + 1 => false, + 2 => false, + 3 => ';', + 4 => false, + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function rgb2hslAndhsbList(): array + { + // if hsb_from or hsl_from is set, this will be used in hsb/hsl convert + // hsb_rgb is used for adjusted rgb valus due to round error to in + return [ + 'valid gray' => [ + 'rgb' => [12, 12, 12], + 'hsb' => [0, 0, 5], + 'hsb_rgb' => [13, 13, 13], // should be rgb, but rounding in this + 'hsl' => [0.0, 0.0, 4.7], + 'valid' => true, + ], + 'valid color' => [ + 'rgb' => [10, 100, 200], + 'hsb' => [212, 95, 78.0], + 'hsb_rgb' => [10, 98, 199], // should be rgb, but rounding error + 'hsl' => [211.6, 90.5, 41.2], + 'valid' => true, + ], + // hsg/hsl with 360 which is seen as 0 + 'valid color hue 360' => [ + 'rgb' => [200, 10, 10], + 'hsb' => [0, 95, 78.0], + 'hsb_from' => [360, 95, 78.0], + 'hsb_rgb' => [199, 10, 10], // should be rgb, but rounding error + 'hsl' => [0.0, 90.5, 41.2], + 'hsl_from' => [360.0, 90.5, 41.2], + 'valid' => true, + ], + // invalid values + 'invalid color' => [ + 'rgb' => [-12, 300, 12], + 'hsb' => [-12, 300, 12], + 'hsl' => [-12, 300, 12], + 'valid' => false, + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function rgb2hsbColorProvider(): array + { + $list = []; + foreach ($this->rgb2hslAndhsbList() as $name => $values) { + $list[$name . ', rgb to hsb'] = [ + 0 => $values['rgb'][0], + 1 => $values['rgb'][1], + 2 => $values['rgb'][2], + 3 => $values['valid'] ? $values['hsb'] : false + ]; + } + return $list; + } + + /** + * Undocumented function + * + * @return array + */ + public function hsb2rgbColorProvider(): array + { + $list = []; + foreach ($this->rgb2hslAndhsbList() as $name => $values) { + $list[$name . ', hsb to rgb'] = [ + 0 => $values['hsb_from'][0] ?? $values['hsb'][0], + 1 => $values['hsb_from'][1] ?? $values['hsb'][1], + 2 => $values['hsb_from'][2] ?? $values['hsb'][2], + 3 => $values['valid'] ? $values['hsb_rgb'] : false + ]; + } + return $list; + } + + /** + * Undocumented function + * + * @return array + */ + public function rgb2hslColorProvider(): array + { + $list = []; + foreach ($this->rgb2hslAndhsbList() as $name => $values) { + $list[$name . ', rgb to hsl'] = [ + 0 => $values['rgb'][0], + 1 => $values['rgb'][1], + 2 => $values['rgb'][2], + 3 => $values['valid'] ? $values['hsl'] : false + ]; + } + return $list; + } + + /** + * Undocumented function + * + * @return array + */ + public function hsl2rgbColorProvider(): array + { + $list = []; + foreach ($this->rgb2hslAndhsbList() as $name => $values) { + $list[$name . ', hsl to rgb'] = [ + 0 => $values['hsl_from'][0] ?? $values['hsl'][0], + 1 => $values['hsl_from'][1] ?? $values['hsl'][1], + 2 => $values['hsl_from'][2] ?? $values['hsl'][2], + 3 => $values['valid'] ? $values['rgb'] : false + ]; + } + return $list; + } + + /** + * Undocumented function + * TODO: add cross convert check + * + * @covers ::rgb2hex + * @dataProvider rgb2hexColorProvider + * @testdox rgb2hex $input_r,$input_g,$input_b will be $expected [$_dataName] + * + * @param int $input_r + * @param int $input_g + * @param int $input_b + * @param string|bool $expected + * @return void + */ + public function testRgb2hex(int $input_r, int $input_g, int $input_b, $expected_hash, $expected) + { + // with # + $this->assertEquals( + $expected_hash, + \CoreLibs\Convert\Colors::rgb2hex($input_r, $input_g, $input_b) + ); + // without # + $this->assertEquals( + $expected, + \CoreLibs\Convert\Colors::rgb2hex($input_r, $input_g, $input_b, false) + ); + // cross convert must match + // $rgb = \CoreLibs\Convert\Colors::hex2rgb($expected_hash); + // if ($rgb === false) { + // $rgb = [ + // 'r' => $input_r, + // 'g' => $input_g, + // 'b' => $input_b, + // ]; + // } + // $this->assertEquals( + // $expected_hash, + // \CoreLibs\Convert\Colors::rgb2hex($rgb['r'], $rgb['g'], $rgb['b']) + // ); + } + + /** + * Undocumented function + * + * @covers ::hex2rgb + * @dataProvider hex2rgbColorProvider + * @testdox hex2rgb $input will be $expected, $expected_str str[,], $expected_str_sep str[$separator] [$_dataName] + * + * @param string $input + * @param array|bool $expected + * @param string|bool $expected_str + * @param string $separator + * @param string|bool $expected_str_sep + * @return void + */ + public function testHex2rgb( + string $input, + $expected, + $expected_str, + string $separator, + $expected_str_sep + ): void { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Colors::hex2rgb($input) + ); + $this->assertEquals( + $expected_str, + \CoreLibs\Convert\Colors::hex2rgb($input, true) + ); + $this->assertEquals( + $expected_str_sep, + \CoreLibs\Convert\Colors::hex2rgb($input, true, $separator) + ); + } + + /** + * Undocumented function + * + * @covers ::rgb2hsb + * @dataProvider rgb2hsbColorProvider + * @testdox rgb2hsb $input_r,$input_g,$input_b will be $expected [$_dataName] + * + * @param integer $input_r + * @param integer $input_g + * @param integer $input_b + * @param array|bool $expected + * @return void + */ + public function testRgb2hsb(int $input_r, int $input_g, int $input_b, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Colors::rgb2hsb($input_r, $input_g, $input_b) + ); + } + + /** + * Undocumented function + * + * @covers ::hsb2rgb + * @dataProvider hsb2rgbColorProvider + * @testdox hsb2rgb $input_h,$input_s,$input_b will be $expected [$_dataName] + * + * @param float $input_h + * @param float $input_s + * @param float $input_b + * @param array|bool $expected + * @return void + */ + public function testHsb2rgb(float $input_h, float $input_s, float $input_b, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Colors::hsb2rgb($input_h, $input_s, $input_b) + ); + } + + /** + * Undocumented function + * + * @covers ::rgb2hsl + * @dataProvider rgb2hslColorProvider + * @testdox rgb2hsl $input_r,$input_g,$input_b will be $expected [$_dataName] + * + * @param integer $input_r + * @param integer $input_g + * @param integer $input_b + * @param array|bool $expected + * @return void + */ + public function testRgb2hsl(int $input_r, int $input_g, int $input_b, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Colors::rgb2hsl($input_r, $input_g, $input_b) + ); + } + + /** + * Undocumented function + * + * @covers ::hsl2rgb + * @dataProvider hsl2rgbColorProvider + * @testdox hsl2rgb $input_h,$input_s,$input_l will be $expected [$_dataName] + * + * @param integer|float $input_h + * @param integer $input_s + * @param integer $input_l + * @param array|bool $expected + * @return void + */ + public function testHsl2rgb($input_h, float $input_s, float $input_l, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Colors::hsl2rgb($input_h, $input_s, $input_l) + ); + } + + /** + * edge case check hsl/hsb and hue 360 (= 0) + * + * @covers ::hsl2rgb + * @covers ::hsb2rgb + * @testdox hsl2rgb/hsb2rgb hue 360 valid check + * + * @return void + */ + public function testHslHsb360hue(): void + { + $this->assertNotFalse( + \CoreLibs\Convert\Colors::hsl2rgb(360.0, 90.5, 41.2), + 'HSL to RGB with 360 hue' + ); + $this->assertNotFalse( + \CoreLibs\Convert\Colors::hsb2rgb(360, 95, 78.0), + 'HSB to RGB with 360 hue' + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertEncodingTest.php b/test/phpunit/CoreLibsConvertEncodingTest.php new file mode 100644 index 0000000..d0a831f --- /dev/null +++ b/test/phpunit/CoreLibsConvertEncodingTest.php @@ -0,0 +1,102 @@ + [ + 'input string', + 'SJIS', + null, + null, + 'input string', + 'SJIS' + ], + 'kanji from UTF-8 to SJIS' => [ + '日本語', + 'SJIS', + null, + null, + '日本語', + 'SJIS' + ], + 'kanji from UTF-8 to SJIS with source' => [ + '日本語', + 'SJIS', + 'UTF-8', + null, + '日本語', + 'SJIS' + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::convertEncoding + * @dataProvider convertEncodingProvider + * @testdox convert encoding $target_encoding, source: $source_encoding, auto: $auto_check [$_dataName] + * + * @param string $input + * @param string $target_encoding + * @param string $source_encoding + * @param bool $auto_check + * @param string $expected + * @param string $expected_encoding + * @return void + */ + public function testConvertEncoding( + string $input, + string $target_encoding, + ?string $source_encoding, + ?bool $auto_check, + string $expected, + string $expected_encoding + ): void { + if ($source_encoding === null and $auto_check === null) { + $string = \CoreLibs\Convert\Encoding::convertEncoding($input, $target_encoding); + } elseif ($auto_check === null) { + $string = \CoreLibs\Convert\Encoding::convertEncoding($input, $target_encoding, $source_encoding); + } else { + $string = \CoreLibs\Convert\Encoding::convertEncoding( + $input, + $target_encoding, + $source_encoding, + $auto_check + ); + } + // because we can't store encoding in here anyway + $target = mb_convert_encoding($expected, $expected_encoding, 'UTF-8'); + // print "IN: $input, $target_encoding\n"; + $this->assertEquals( + $target, + $string + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertHtmlTest.php b/test/phpunit/CoreLibsConvertHtmlTest.php new file mode 100644 index 0000000..138eee9 --- /dev/null +++ b/test/phpunit/CoreLibsConvertHtmlTest.php @@ -0,0 +1,188 @@ + [ + 0 => 'I am some string', + 1 => 'I am some string', + ], + 'conversion' => [ + 0 => 'I have special <> inside', + 1 => 'I have special <> inside', + ], + 'skip number' => [ + 0 => 1234, + 1 => 1234, + ], + 'utf8' => [ + 0 => '日本語 <>', + 1 => '日本語 <>' + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function removeLBProvider(): array + { + return [ + 'nothing replaced, default' => [ + 0 => 'I am some string', + 1 => null, + 2 => 'I am some string', + ], + 'string with \n replace -' => [ + 0 => "I am\nsome string", + 1 => '-', + 2 => 'I am-some string', + ], + 'string with \r replace _' => [ + 0 => "I am\rsome string", + 1 => '_', + 2 => 'I am_some string', + ], + 'string with \n\r, default' => [ + 0 => "I am\n\rsome string", + 1 => null, + 2 => 'I am some string', + ], + 'string with \n\r replae ##BR##' => [ + 0 => "I am\n\rsome string", + 1 => '##BR##', + 2 => 'I am##BR##some string', + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function checkedProvider(): array + { + return [ + 'haystack is a string and matching selected' => [ + 0 => 'string', + 1 => 'string', + 2 => \CoreLibs\Convert\Html::SELECTED, + 3 => 'selected' + ], + 'haystack is a string and matching checked' => [ + 0 => 'string', + 1 => 'string', + 2 => \CoreLibs\Convert\Html::CHECKED, + 3 => 'checked' + ], + 'haystack is a string and not matching' => [ + 0 => 'string', + 1 => 'not matching', + 2 => \CoreLibs\Convert\Html::CHECKED, + 3 => null + ], + 'haystack is array and matching' => [ + 0 => ['a', 'b', 'c'], + 1 => 'a', + 2 => \CoreLibs\Convert\Html::SELECTED, + 3 => 'selected' + ], + 'haystack is array and not matching' => [ + 0 => ['a', 'b', 'c'], + 1 => 'not matching', + 2 => \CoreLibs\Convert\Html::SELECTED, + 3 => null + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::htmlent + * @dataProvider htmlentProvider + * @testdox htmlent $input will be $expected [$_dataName] + * + * @param mixed $input + * @param mixed $expected + * @return void + */ + public function testHtmlent($input, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Html::htmlent($input) + ); + } + + /** + * Undocumented function + * + * @covers ::removeLB + * @dataProvider removeLBProvider + * @testdox removeLB $input with replace $replace will be $expected [$_dataName] + * + * @param string $input + * @param string|null $replace + * @param string $expected + * @return void + */ + public function testRemoveLB(string $input, ?string $replace, string $expected): void + { + if ($replace !== null) { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Html::removeLB($input, $replace) + ); + } else { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Html::removeLB($input) + ); + } + } + + /** + * Undocumented function + * + * @covers ::checked + * @dataProvider checkedProvider + * @testdox checked find $needle in $haystack and return $type will be $expected [$_dataName] + * + * @param array|string $haystack + * @param string $needle + * @param integer $type + * @param string|null $expected + * @return void + */ + public function testChecked($haystack, string $needle, int $type, ?string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Html::checked($haystack, $needle, $type) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertJsonTest.php b/test/phpunit/CoreLibsConvertJsonTest.php new file mode 100644 index 0000000..af6e7e3 --- /dev/null +++ b/test/phpunit/CoreLibsConvertJsonTest.php @@ -0,0 +1,166 @@ + [ + '{"m":2,"f":"sub_2"}', + false, + [ + 'm' => 2, + 'f' => 'sub_2' + ] + ], + 'empty json' => [ + '', + false, + [] + ], + 'invalid json override' => [ + 'not valid', + true, + [ + 'not valid' + ] + ], + 'invalid json' => [ + 'not valid', + false, + [] + ], + 'null json' => [ + null, + false, + [] + ] + ]; + } + + /** + * json error list + * + * @return array JSON error list + */ + public function jsonErrorProvider(): array + { + return [ + 'no error' => [ + '{}', + JSON_ERROR_NONE, '' + ], + 'depth error' => [ + '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[' + . '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[' + . '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[' + . '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[' + . '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[' + . '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[' + . '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[' + . '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[' + . '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + . ']]]]', + JSON_ERROR_DEPTH, 'Maximum stack depth exceeded' + ], + // 'state mismatch error' => [ + // '{foo:}', + // JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch' + // ], + // 'ctrl char error' => [ + // ' {"data":"data","data":"data","data":"data","data":"data"}', + // JSON_ERROR_CTRL_CHAR, 'Unexpected control character found' + // ], + 'syntax error' => [ + 'not valid', + JSON_ERROR_SYNTAX, 'Syntax error, malformed JSON' + ], + // 'utf8 error' => [ + // '{"invalid":"\xB1\x31"}', + // JSON_ERROR_UTF8, 'Malformed UTF-8 characters, possibly incorrectly encoded' + // ], + // 'invalid property' => [ + // '{"\u0000":"abc"}', + // JSON_ERROR_INVALID_PROPERTY_NAME, 'A key starting with \u0000 character was in the string' + // ], + // 'utf-16 error' => [ + // '', + // JSON_ERROR_UTF16, 'Single unpaired UTF-16 surrogate in unicode escape' + // ], + // 'unknown error' => [ + // '', + // -999999, 'Unknown error' + // ] + ]; + } + + /** + * test json convert states + * + * @covers ::jsonConvertToArray + * @dataProvider jsonProvider + * @testdox jsonConvertToArray $input (Override: $flag) will be $expected [$_dataName] + * + * @param string|null $input + * @param bool $flag + * @param array $expected + * @return void + */ + public function testJsonConvertToArray(?string $input, bool $flag, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Json::jsonConvertToArray($input, $flag) + ); + } + + /** + * test json error states + * + * @covers ::jsonGetLastError + * @dataProvider jsonErrorProvider + * @testdox jsonGetLastError $input will be $expected_i/$expected_s [$_dataName] + * + * @param string|null $input + * @param string $expected + * @return void + */ + public function testJsonGetLastError(?string $input, int $expected_i, string $expected_s): void + { + \CoreLibs\Convert\Json::jsonConvertToArray($input); + $this->assertEquals( + $expected_i, + \CoreLibs\Convert\Json::jsonGetLastError() + ); + $this->assertEquals( + $expected_s, + \CoreLibs\Convert\Json::jsonGetLastError(true) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertMathTest.php b/test/phpunit/CoreLibsConvertMathTest.php new file mode 100644 index 0000000..9a97e37 --- /dev/null +++ b/test/phpunit/CoreLibsConvertMathTest.php @@ -0,0 +1,118 @@ + + */ + public function fceilProvider(): array + { + return [ + '5.5 must be 6' => [5.5, 6], + '5.1234567890 with 5 must be 6' => [5.1234567890, 6], + '6 must be 6' => [6, 6] + ]; + } + + /** + * Undocumented function + * + * @covers ::fceil + * @dataProvider fceilProvider + * @testdox fceil: Input $input must be $expected + * + * @param float $input + * @param int $expected + * @return void + */ + public function testMathFceilValue(float $input, int $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Math::fceil($input) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function floorProvider(): array + { + return [ + '5123456 with -3 must be 5123000' => [5123456, -3, 5123000], + '5123456 with -10 must be 5000000' => [5123456, -10, 5000000] + ]; + } + + /** + * Undocumented function + * + * @covers ::floorp + * @dataProvider floorProvider + * @testdox floor: Input $input with cutoff $cutoff must be $expected + * + * @param int $input + * @param int $cutoff + * @param int $expected + * @return void + */ + public function testMathFloorValue(int $input, int $cutoff, int $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Math::floorp($input, $cutoff) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function initNumericProvider(): array + { + return [ + '5 must be 5' => [5, 5, 'int'], + '5.123 must be 5.123' => [5.123, 5.123, 'float'], + "'5' must be 5" => ['5', 5, 'string'], + "'5.123' must be 5.123" => ['5.123', 5.123, 'string'], + ]; + } + + /** + * Undocumented function + * + * @covers ::initNumeric + * @dataProvider initNumericProvider + * @testdox initNumeric: Input $info $input must match $expected [$_dataName] + * + * @param int|float|string $input + * @param float $expected + * @param string $info + * @return void + */ + public function testMathInitNumericValue($input, float $expected, string $info): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Math::initNumeric($input) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertMimeAppNameTest.php b/test/phpunit/CoreLibsConvertMimeAppNameTest.php new file mode 100644 index 0000000..6284d7a --- /dev/null +++ b/test/phpunit/CoreLibsConvertMimeAppNameTest.php @@ -0,0 +1,60 @@ + [ + 0 => 'foo/bar', + 1 => 'FooBar Application', + 2 => 'FooBar Application', + ], + 'try to set empty mime type' => [ + 0 => '', + 1 => 'Some app', + 2 => 'Other file' + ], + 'try to set empty app name' => [ + 0 => 'some/app', + 1 => '', + 2 => 'Other file' + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::mimeGetAppName + * @covers ::mimeSetAppName + * @dataProvider mimeProvider + * @testdox mimeSetAppName set $mime with $app and will be $expected [$_dataName] + * + * @param string $mime + * @param string $app + * @return void + */ + public function testMimeSetAppName(string $mime, string $app, string $expected): void + { + \CoreLibs\Convert\MimeAppName::mimeSetAppName($mime, $app); + $this->assertEquals( + $expected, + \CoreLibs\Convert\MimeAppName::mimeGetAppName($mime) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertMimeEncodeTest.php b/test/phpunit/CoreLibsConvertMimeEncodeTest.php new file mode 100644 index 0000000..00b1055 --- /dev/null +++ b/test/phpunit/CoreLibsConvertMimeEncodeTest.php @@ -0,0 +1,101 @@ + [ + 'Test string', + 'UTF-8', + 'Test string' + ], + 'long text UTF-8' => [ + 'The quick brown fox jumps over the lazy sheep that sleeps in the ravine ' + . 'and has no idea what is going on here', + 'UTF-8', + 'The quick brown fox jumps over the lazy sheep that sleeps in the ravine ' + . 'and has no idea what is going on here' + ], + 'standard with special chars UTF-8' => [ + 'This is ümläßtと漢字もカタカナ!!^$%&', + 'UTF-8', + 'This is =?UTF-8?B?w7xtbMOkw59044Go5ryi5a2X44KC44Kr44K/44Kr44OK77yBIV4k?=' + . "\r\n" + . ' =?UTF-8?B?JQ==?=&' + ], + '35 chars and space at the end UTF-8' => [ + '12345678901234567890123456789012345 ' + . 'is there a space?', + 'UTF-8', + '12345678901234567890123456789012345 ' + . 'is there a =?UTF-8?B?c3BhY2U/?=' + ], + '36 chars and space at the end UTF-8' => [ + '123456789012345678901234567890123456 ' + . 'is there a space?', + 'UTF-8', + '123456789012345678901234567890123456 ' + . 'is there a =?UTF-8?B?c3BhY2U/?=' + ], + '36 kanji and space UTF-8' => [ + 'カタカナカタカナかなカタカナカタカナかなカタカナカタカナかなカタカナカタ ' + . 'is there a space?', + 'UTF-8', + "=?UTF-8?B?44Kr44K/44Kr44OK44Kr44K/44Kr44OK44GL44Gq44Kr44K/44Kr44OK44Kr?=\r\n" + . " =?UTF-8?B?44K/44Kr44OK?=\r\n" + . " =?UTF-8?B?44GL44Gq44Kr44K/44Kr44OK44Kr44K/44Kr44OK44GL44Gq44Kr44K/44Kr?=\r\n" + . " =?UTF-8?B?44OK44Kr44K/?= is there a =?UTF-8?B?c3BhY2U/?=" + ] + ]; + } + + /** + * mb mime header encoding test + * + * @covers ::__mbMimeEncode + * @dataProvider mbMimeEncodeProvider + * @testdox mb encoding target $encoding [$_dataName] + * + * @return void + */ + public function testUuMbMimeEncode(string $input, string $encoding, string $expected): void + { + // encode string first + $encoded = \CoreLibs\Convert\MimeEncode::__mbMimeEncode($input, $encoding); + // print "MIME: -" . $encoded . "-\n"; + $this->assertEquals( + $expected, + $encoded + ); + $decoded = mb_decode_mimeheader($encoded); + // print "INPUT : " . $input . "\n"; + // print "DECODED: " . $decoded . "\n"; + // back compare decoded + $this->assertEquals( + $input, + $decoded + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertSetVarTypeNullTest.php b/test/phpunit/CoreLibsConvertSetVarTypeNullTest.php new file mode 100644 index 0000000..88c638d --- /dev/null +++ b/test/phpunit/CoreLibsConvertSetVarTypeNullTest.php @@ -0,0 +1,652 @@ + [ + '', + null, + '' + ], + 'filled string' => [ + 'string', + null, + 'string' + ], + 'valid string, override set' => [ + 'string', + 'override', + 'string' + ], + 'int, no override' => [ + 1, + null, + null + ], + 'int, override set' => [ + 1, + 'not int', + 'not int' + ] + ]; + } + + /** + * Undocumented function + * @covers ::setStr + * @dataProvider varSetTypeStringProvider + * @testdox setStr $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param string|null $default + * @param string|null $expected + * @return void + */ + public function testSetString(mixed $input, ?string $default, ?string $expected): void + { + $set_var = SetVarTypeNull::setStr($input, $default); + if ($expected !== null) { + $this->assertIsString($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varMakeTypeStringProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'empty string' => [ + '', + null, + '' + ], + 'filled string' => [ + 'string', + null, + 'string' + ], + 'valid string, override set' => [ + 'string', + 'override', + 'string' + ], + 'int, no override' => [ + 1, + null, + '1' + ], + 'int, override set' => [ + 1, + 'not int', + '1' + ], + 'float, no override' => [ + 1.5, + null, + '1.5' + ], + // all the strange things here + 'function, override set' => [ + $foo = function () { + return ''; + }, + 'function', + 'function' + ], + 'function, no override' => [ + $foo = function () { + return ''; + }, + null, + null + ], + 'hex value, override set' => [ + 0x55, + 'hex', + '85' + ] + ]; + } + + /** + * Undocumented function + * @covers ::makeStr + * @dataProvider varMakeTypeStringProvider + * @testdox makeStr $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param string|null $default + * @param string|null $expected + * @return void + */ + public function testMakeString(mixed $input, ?string $default, ?string $expected): void + { + $set_var = SetVarTypeNull::makeStr($input, $default); + if ($expected !== null) { + $this->assertIsString($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varSetTypeIntProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'int' => [ + 1, + null, + 1 + ], + 'int, override set' => [ + 1, + -1, + 1 + ], + 'string, no override' => [ + 'string', + null, + null + ], + 'string, override' => [ + 'string', + -1, + -1 + ], + 'float' => [ + 1.5, + null, + null + ] + ]; + } + + /** + * Undocumented function + * @covers ::setInt + * @dataProvider varSetTypeIntProvider + * @testdox setInt $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param int|null $default + * @param int|null $expected + * @return void + */ + public function testSetInt(mixed $input, ?int $default, ?int $expected): void + { + $set_var = SetVarTypeNull::setInt($input, $default); + if ($expected !== null) { + $this->assertIsInt($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varMakeTypeIntProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'int' => [ + 1, + null, + 1 + ], + 'int, override set' => [ + 1, + -1, + 1 + ], + 'string, no override' => [ + 'string', + null, + 0 + ], + 'string, override' => [ + 'string', + -1, + 0 + ], + 'float' => [ + 1.5, + null, + 1 + ], + // all the strange things here + 'function, override set' => [ + $foo = function () { + return ''; + }, + -1, + -1 + ], + 'function, no override ' => [ + $foo = function () { + return ''; + }, + null, + null + ], + 'hex value, override set' => [ + 0x55, + -1, + 85 + ], + ]; + } + + /** + * Undocumented function + * @covers ::makeInt + * @dataProvider varMakeTypeIntProvider + * @testdox makeInt $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param int|null $default + * @param int|null $expected + * @return void + */ + public function testMakeInt(mixed $input, ?int $default, ?int $expected): void + { + $set_var = SetVarTypeNull::makeInt($input, $default); + if ($expected !== null) { + $this->assertIsInt($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varSetTypeFloatProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'float' => [ + 1.5, + null, + 1.5 + ], + 'float, override set' => [ + 1.5, + -1.5, + 1.5 + ], + 'string, no override' => [ + 'string', + null, + null + ], + 'string, override' => [ + 'string', + 1.5, + 1.5 + ], + 'int' => [ + 1, + null, + null + ] + ]; + } + + /** + * Undocumented function + * @covers ::setFloat + * @dataProvider varSetTypeFloatProvider + * @testdox setFloat $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param float|null $default + * @param float|null $expected + * @return void + */ + public function testSetFloat(mixed $input, ?float $default, ?float $expected): void + { + $set_var = SetVarTypeNull::setFloat($input, $default); + if ($expected !== null) { + $this->assertIsFloat($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varMakeTypeFloatProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'float' => [ + 1.5, + null, + 1.5 + ], + 'float, override set' => [ + 1.5, + -1.5, + 1.5 + ], + 'string, no override' => [ + 'string', + null, + 0.0 + ], + 'string, override' => [ + 'string', + 1.5, + 0.0 + ], + 'int' => [ + 1, + null, + 1.0 + ], + // all the strange things here + 'function, override set' => [ + $foo = function () { + return ''; + }, + -1.0, + -1.0 + ], + // all the strange things here + 'function, no override' => [ + $foo = function () { + return ''; + }, + null, + null + ], + 'hex value, override set' => [ + 0x55, + -1, + 85.0 + ], + ]; + } + + /** + * Undocumented function + * @covers ::makeFloat + * @dataProvider varMakeTypeFloatProvider + * @testdox makeFloat $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param float|null $default + * @param float|null $expected + * @return void + */ + public function testMakeFloat(mixed $input, ?float $default, ?float $expected): void + { + $set_var = SetVarTypeNull::makeFloat($input, $default); + if ($expected !== null) { + $this->assertIsFloat($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varSetTypeArrayProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'array, empty' => [ + [], + null, + [] + ], + 'array, filled' => [ + ['array'], + null, + ['array'] + ], + 'string, no override' => [ + 'string', + null, + null + ], + 'string, override' => [ + 'string', + ['string'], + ['string'] + ] + ]; + } + + /** + * Undocumented function + * @covers ::setArray + * @dataProvider varSetTypeArrayProvider + * @testdox setArray $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param array|null $default + * @param array|null $expected + * @return void + */ + public function testSetArray(mixed $input, ?array $default, ?array $expected): void + { + $set_var = SetVarTypeNull::setArray($input, $default); + if ($expected !== null) { + $this->assertIsArray($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varSetTypeBoolProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'bool true' => [ + true, + null, + true + ], + 'bool false' => [ + false, + null, + false + ], + 'string, no override' => [ + 'string', + null, + null + ], + 'string, override' => [ + 'string', + true, + true + ] + ]; + } + + /** + * Undocumented function + * @covers ::setBool + * @dataProvider varSetTypeBoolProvider + * @testdox setBool $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param bool|null $default + * @param bool|null $expected + * @return void + */ + public function testSetBool(mixed $input, ?bool $default, ?bool $expected): void + { + $set_var = SetVarTypeNull::setBool($input, $default); + if ($expected !== null) { + $this->assertIsBool($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varMakeTypeBoolProvider(): array + { + // 0: input + // 2: expected + return [ + 'true' => [ + true, + true + ], + 'false' => [ + false, + false + ], + 'string on' => [ + 'on', + true + ], + 'string off' => [ + 'off', + false + ], + 'invalid string' => [ + 'sulzenbacher', + null, + ], + 'invalid string, override' => [ + 'sulzenbacher', + null, + ], + 'array to default' => [ + [], + false + ], + ]; + } + + /** + * Undocumented function + * @covers ::setBool + * @dataProvider varMakeTypeBoolProvider + * @testdox setBool $input will be $expected [$_dataName] + * + * @param mixed $input + * @param bool|null $default + * @param bool|null $expected + * @return void + */ + public function testMakeBool(mixed $input, ?bool $expected): void + { + $set_var = SetVarTypeNull::makeBool($input); + if ($expected !== null) { + $this->assertIsBool($set_var); + } else { + $this->assertNull($set_var); + } + $this->assertEquals( + $expected, + $set_var + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertSetVarTypeTest.php b/test/phpunit/CoreLibsConvertSetVarTypeTest.php new file mode 100644 index 0000000..8afe861 --- /dev/null +++ b/test/phpunit/CoreLibsConvertSetVarTypeTest.php @@ -0,0 +1,632 @@ + [ + '', + null, + '' + ], + 'filled string' => [ + 'string', + null, + 'string' + ], + 'valid string, override set' => [ + 'string', + 'override', + 'string' + ], + 'int, no override' => [ + 1, + null, + '' + ], + 'int, override set' => [ + 1, + 'not int', + 'not int' + ] + ]; + } + + /** + * Undocumented function + * @covers ::setStr + * @dataProvider varSetTypeStringProvider + * @testdox setStr $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param string|null $default + * @param string $expected + * @return void + */ + public function testSetString(mixed $input, ?string $default, string $expected): void + { + if ($default === null) { + $set_var = SetVarType::setStr($input); + } else { + $set_var = SetVarType::setStr($input, $default); + } + $this->assertIsString($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varMakeTypeStringProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'empty string' => [ + '', + null, + '' + ], + 'filled string' => [ + 'string', + null, + 'string' + ], + 'valid string, override set' => [ + 'string', + 'override', + 'string' + ], + 'int, no override' => [ + 1, + null, + '1' + ], + 'int, override set' => [ + 1, + 'not int', + '1' + ], + // all the strange things here + 'function, override set' => [ + $foo = function () { + return ''; + }, + 'function', + 'function' + ], + 'hex value, override set' => [ + 0x55, + 'hex', + '85' + ] + ]; + } + + /** + * Undocumented function + * @covers ::makeStr + * @dataProvider varMakeTypeStringProvider + * @testdox makeStr $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param string|null $default + * @param string $expected + * @return void + */ + public function testMakeString(mixed $input, ?string $default, string $expected): void + { + if ($default === null) { + $set_var = SetVarType::makeStr($input); + } else { + $set_var = SetVarType::makeStr($input, $default); + } + $this->assertIsString($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varSetTypeIntProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'int' => [ + 1, + null, + 1 + ], + 'int, override set' => [ + 1, + -1, + 1 + ], + 'string, no override' => [ + 'string', + null, + 0 + ], + 'string, override' => [ + 'string', + -1, + -1 + ], + 'float' => [ + 1.5, + null, + 0 + ] + ]; + } + + /** + * Undocumented function + * @covers ::setInt + * @dataProvider varSetTypeIntProvider + * @testdox setInt $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param int|null $default + * @param int $expected + * @return void + */ + public function testSetInt(mixed $input, ?int $default, int $expected): void + { + if ($default === null) { + $set_var = SetVarType::setInt($input); + } else { + $set_var = SetVarType::setInt($input, $default); + } + $this->assertIsInt($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varMakeTypeIntProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'int' => [ + 1, + null, + 1 + ], + 'int, override set' => [ + 1, + -1, + 1 + ], + 'string, no override' => [ + 'string', + null, + 0 + ], + 'string, override' => [ + 'string', + -1, + 0 + ], + 'float' => [ + 1.5, + null, + 1 + ], + // all the strange things here + 'function, override set' => [ + $foo = function () { + return ''; + }, + -1, + -1 + ], + 'hex value, override set' => [ + 0x55, + -1, + 85 + ], + ]; + } + + /** + * Undocumented function + * @covers ::makeInt + * @dataProvider varMakeTypeIntProvider + * @testdox makeInt $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param int|null $default + * @param int $expected + * @return void + */ + public function testMakeInt(mixed $input, ?int $default, int $expected): void + { + if ($default === null) { + $set_var = SetVarType::makeInt($input); + } else { + $set_var = SetVarType::makeInt($input, $default); + } + $this->assertIsInt($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varSetTypeFloatProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'float' => [ + 1.5, + null, + 1.5 + ], + 'float, override set' => [ + 1.5, + -1.5, + 1.5 + ], + 'string, no override' => [ + 'string', + null, + 0.0 + ], + 'string, override' => [ + 'string', + 1.5, + 1.5 + ], + 'int' => [ + 1, + null, + 0.0 + ] + ]; + } + + /** + * Undocumented function + * @covers ::setFloat + * @dataProvider varSetTypeFloatProvider + * @testdox setFloat $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param float|null $default + * @param float $expected + * @return void + */ + public function testSetFloat(mixed $input, ?float $default, float $expected): void + { + if ($default === null) { + $set_var = SetVarType::setFloat($input); + } else { + $set_var = SetVarType::setFloat($input, $default); + } + $this->assertIsFloat($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varMakeTypeFloatProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'float' => [ + 1.5, + null, + 1.5 + ], + 'float, override set' => [ + 1.5, + -1.5, + 1.5 + ], + 'string, no override' => [ + 'string', + null, + 0.0 + ], + 'string, override' => [ + 'string', + 1.5, + 0.0 + ], + 'int' => [ + 1, + null, + 1.0 + ], + // all the strange things here + 'function, override set' => [ + $foo = function () { + return ''; + }, + -1.0, + -1.0 + ], + 'hex value, override set' => [ + 0x55, + -1, + 85.0 + ], + ]; + } + + /** + * Undocumented function + * @covers ::makeFloat + * @dataProvider varMakeTypeFloatProvider + * @testdox makeFloat $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param float|null $default + * @param float $expected + * @return void + */ + public function testMakeFloat(mixed $input, ?float $default, float $expected): void + { + if ($default === null) { + $set_var = SetVarType::makeFloat($input); + } else { + $set_var = SetVarType::makeFloat($input, $default); + } + $this->assertIsFloat($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varSetTypeArrayProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'array, empty' => [ + [], + null, + [] + ], + 'array, filled' => [ + ['array'], + null, + ['array'] + ], + 'string, no override' => [ + 'string', + null, + [] + ], + 'string, override' => [ + 'string', + ['string'], + ['string'] + ] + ]; + } + + /** + * Undocumented function + * @covers ::setArray + * @dataProvider varSetTypeArrayProvider + * @testdox setArray $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param array|null $default + * @param array $expected + * @return void + */ + public function testSetArray(mixed $input, ?array $default, array $expected): void + { + if ($default === null) { + $set_var = SetVarType::setArray($input); + } else { + $set_var = SetVarType::setArray($input, $default); + } + $this->assertIsArray($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varSetTypeBoolProvider(): array + { + // 0: input + // 1: default (null default) + // 2: expected + return [ + 'bool true' => [ + true, + null, + true + ], + 'bool false' => [ + false, + null, + false + ], + 'string, no override' => [ + 'string', + null, + false + ], + 'string, override' => [ + 'string', + true, + true + ] + ]; + } + + /** + * Undocumented function + * @covers ::setBool + * @dataProvider varSetTypeBoolProvider + * @testdox setBool $input with override $default will be $expected [$_dataName] + * + * @param mixed $input + * @param bool|null $default + * @param bool $expected + * @return void + */ + public function testSetBool(mixed $input, ?bool $default, bool $expected): void + { + if ($default === null) { + $set_var = SetVarType::setBool($input); + } else { + $set_var = SetVarType::setBool($input, $default); + } + $this->assertIsBool($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function varMakeTypeBoolProvider(): array + { + // 0: input + // 2: expected + return [ + 'true' => [ + true, + null, + true + ], + 'false' => [ + false, + null, + false + ], + 'string on' => [ + 'on', + null, + true + ], + 'string off' => [ + 'off', + null, + false + ], + 'invalid string' => [ + 'sulzenbacher', + null, + false, + ], + 'invalid string, override' => [ + 'sulzenbacher', + true, + true, + ], + 'array to default' => [ + [], + null, + false + ], + ]; + } + + /** + * Undocumented function + * @covers ::setBool + * @dataProvider varMakeTypeBoolProvider + * @testdox setBool $input will be $expected [$_dataName] + * + * @param mixed $input + * @param bool|null $default + * @param bool $expected + * @return void + */ + public function testMakeBool(mixed $input, ?bool $default, bool $expected): void + { + if ($default === null) { + $set_var = SetVarType::makeBool($input); + } else { + $set_var = SetVarType::makeBool($input, $default); + } + $this->assertIsBool($set_var); + $this->assertEquals( + $expected, + $set_var + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsConvertStringsTest.php b/test/phpunit/CoreLibsConvertStringsTest.php new file mode 100644 index 0000000..90415d2 --- /dev/null +++ b/test/phpunit/CoreLibsConvertStringsTest.php @@ -0,0 +1,261 @@ + [ + '', + '', + null, + '' + ], + 'empty input string' => [ + '', + '2-2', + null, + '' + ], + 'empty format string string' => [ + '1234', + '', + null, + '1234' + ], + 'string format match' => [ + '1234', + '2-2', + null, + '12-34' + ], + 'string format trailing match' => [ + '1234', + '2-2-', + null, + '12-34' + ], + 'string format leading match' => [ + '1234', + '-2-2', + null, + '12-34' + ], + 'string format double inside match' => [ + '1234', + '2--2', + null, + '12--34', + ], + 'string format short first' => [ + '1', + '2-2', + null, + '1' + ], + 'string format match first' => [ + '12', + '2-2', + null, + '12' + ], + 'string format short second' => [ + '123', + '2-2', + null, + '12-3' + ], + 'string format too long' => [ + '1234567', + '2-2', + null, + '12-34-567' + ], + 'string format invalid format string' => [ + '1234', + '2_2', + null, + '1234' + ], + 'different split character' => [ + '1234', + '2_2', + '_', + '12_34' + ], + 'mixed split characters' => [ + '123456', + '2-2_2', + '-_', + '12-34_56' + ], + 'length mixed' => [ + 'ABCD12345568ABC13', + '2-4_5-2#4', + '-_#', + 'AB-CD12_34556-8A#BC13' + ], + 'split with split chars in string' => [ + '12-34', + '2-2', + null, + '12--3-4' + ], + 'mutltibyte string' => [ + 'あいうえ', + '2-2', + null, + 'あいうえ' + ], + 'mutltibyte split string' => [ + '1234', + '2-2', + null, + '1234' + ], + ]; + } + + /** + * split format string + * + * @covers ::splitFormatString + * @dataProvider splitFormatStringProvider + * @testdox splitFormatString $input with format $format and splitters $split_characters will be $expected [$_dataName] + * + * @param string $input + * @param string $format + * @param string|null $split_characters + * @param string $expected + * @return void + */ + public function testSplitFormatString( + string $input, + string $format, + ?string $split_characters, + string $expected + ): void { + if ($split_characters === null) { + $output = \CoreLibs\Convert\Strings::splitFormatString( + $input, + $format + ); + } else { + $output = \CoreLibs\Convert\Strings::splitFormatString( + $input, + $format, + $split_characters + ); + } + $this->assertEquals( + $expected, + $output + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function countSplitPartsProvider(): array + { + return [ + '0 elements' => [ + '', + null, + 0 + ], + '1 element' => [ + '1', + null, + 1, + ], + '2 elements, trailing' => [ + '1-2-', + null, + 2 + ], + '2 elements, leading' => [ + '-1-2', + null, + 2 + ], + '2 elements, midde double' => [ + '1--2', + null, + 2 + ], + '4 elements' => [ + '1-2-3-4', + null, + 4 + ], + '3 elemenst, other splitter' => [ + '2-3_3', + '-_', + 3 + ], + 'illegal splitter' => [ + 'あsdf', + null, + 0 + ] + ]; + } + + /** + * count split parts + * + * @covers ::countSplitParts + * @dataProvider countSplitPartsProvider + * @testdox countSplitParts $input with splitters $split_characters will be $expected [$_dataName] + * + * @param string $input + * @param string|null $split_characters + * @param int $expected + * @return void + */ + public function testCountSplitParts( + string $input, + ?string $split_characters, + int $expected + ): void { + if ($split_characters === null) { + $output = \CoreLibs\Convert\Strings::countSplitParts( + $input + ); + } else { + $output = \CoreLibs\Convert\Strings::countSplitParts( + $input, + $split_characters + ); + } + $this->assertEquals( + $expected, + $output + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCreateEmailTest.php b/test/phpunit/CoreLibsCreateEmailTest.php new file mode 100644 index 0000000..c030100 --- /dev/null +++ b/test/phpunit/CoreLibsCreateEmailTest.php @@ -0,0 +1,700 @@ + DIRECTORY_SEPARATOR . 'tmp', + 'file_id' => 'CoreLibs-Create-Email-Test', + 'debug_all' => true, + 'echo_all' => false, + 'print_all' => true, + ]); + } + + /** + * Undocumented function + * + * @return array + */ + public function encodeEmailNameProvider(): array + { + // 0: email + // 1: name + // 2: encoding + // 3: kv_folding + // 4: expected + return [ + 'all empty' => [ + '', + null, + null, + null, + '' + ], + 'email only' => [ + 'test@test.com', + null, + null, + null, + 'test@test.com' + ], + 'email and name' => [ + 'test@test.com', + 'Test Name', + null, + null, + '"Test Name" ' + ], + 'name in mime encoded, default UTF-8' => [ + 'test@test.com', + '日本語', + null, + null, + '"=?UTF-8?B?5pel5pys6Kqe?=" ' + ], + 'name in mime encoded with half width Katakana, default UTF-8' => [ + 'test@test.com', + '日本語カタカナパ', + null, + null, + '"=?UTF-8?B?5pel5pys6Kqe7722776A7722776F776K776f?=" ' + ], + 'name in mime encoded with half width Katakana, folding on, default UTF-8' => [ + 'test@test.com', + '日本語カタカナパ', + 'UTF-8', + true, + '"=?UTF-8?B?5pel5pys6Kqe44Kr44K/44Kr44OK44OR?=" ' + ], + 'name in mime encoded, UTF-8 parameter' => [ + 'test@test.com', + '日本語', + 'UTF-8', + null, + '"=?UTF-8?B?5pel5pys6Kqe?=" ' + ], + // does internal UTF-8 to ISO-2022-JP convert + 'encoding in ISO-2022-JP' => [ + 'test@test.com', + '日本語', + 'ISO-2022-JP', + null, + '"=?ISO-2022-JP?B?GyRCRnxLXDhsGyhC?=" ' + ], + 'encoding with half width Katakana in ISO-2022-JP' => [ + 'test@test.com', + '日本語カタカナパ', + 'ISO-2022-JP', + null, + '"=?ISO-2022-JP?B?GyRCRnxLXDhsGyhCPz8/Pz8/?=" ' + ], + 'encoding with half width Katakana, folding on in ISO-2022-JP' => [ + 'test@test.com', + '日本語カタカナパ', + 'ISO-2022-JP', + true, + // was ok php 8.1 + // '"=?ISO-2022-JP?B?GyRCRnxLXDhsGyhCPz8/Pz8=?=" ' + // below ok php 8.1.12, 2022/12/9 + '"=?ISO-2022-JP?B?GyRCRnxLXDhsGyhCPz8/Pz8/?=" ' + ] + ]; + } + + /** + * Undocumented function + * + * @dataProvider encodeEmailNameProvider + * @testdox encode email $email, name $name, encoding $encoding, folding $kv_folding will be $expected [$_dataName] + * + * @param string $email + * @param string|null $name + * @param string|null $encoding + * @param bool|null $kv_folding + * @param string $expected + * @return void + */ + public function testEncodeEmailName( + string $email, + ?string $name, + ?string $encoding, + ?bool $kv_folding, + string $expected + ): void { + if ($name === null && $encoding === null && $kv_folding === null) { + $encoded_email = \CoreLibs\Create\Email::encodeEmailName($email); + } elseif ($encoding === null && $kv_folding === null) { + $encoded_email = \CoreLibs\Create\Email::encodeEmailName($email, $name); + } elseif ($kv_folding === null) { + $encoded_email = \CoreLibs\Create\Email::encodeEmailName($email, $name, $encoding); + } else { + $encoded_email = \CoreLibs\Create\Email::encodeEmailName($email, $name, $encoding, $kv_folding); + } + $this->assertEquals( + $expected, + $encoded_email + ); + } + + public function sendEmailProvider(): array + { + // 0: subject + // 1: body + // 2: from email + // 3: from name ('') + // 4: array for to email + // 5: replace content ([]/null) + // 6: encoding (UTF-8/null) + // 7: kv_folding + // 8: return status + // 9: expected content + return [ + 'all empty, fail -1' => [ + 'subject' => '', + 'body' => '', + 'from_email' => '', + 'from_name' => '', + 'to_email' => [], + 'replace' => null, + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => -1, + 'expected_content' => [], + ], + 'missing to entry, fail -2' => [ + 'subject' => 'SUBJECT', + 'body' => 'BODY', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [], + 'replace' => null, + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => -2, + 'expected_content' => [], + ], + 'bad encoding, fail -3' => [ + 'subject' => 'SUBJECT', + 'body' => 'BODY', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => ['to@test.com'], + 'replace' => null, + 'encoding' => 'IDONTEXISTENCODING', + 'kv_folding' => null, + 'expected_status' => -3, + 'expected_content' => [], + ], + 'sending email 1' => [ + 'subject' => 'SUBJECT', + 'body' => 'BODY', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + 'test@test.com' + ], + 'replace' => null, + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT', + 'body' => 'BODY', + ] + ], + ], + 'sending email 1, encoded' => [ + 'subject' => 'SUBJECT 日本語', + 'body' => 'BODY 日本語', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + 'test@test.com' + ], + 'replace' => null, + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT =?UTF-8?B?5pel5pys6Kqe?=', + 'body' => 'BODY 日本語', + ] + ], + ], + 'sending email 1, encoded, with half width katakanata' => [ + 'subject' => 'SUBJECT 日本語カタカナパ', + 'body' => 'BODY 日本語', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + 'test@test.com' + ], + 'replace' => null, + 'encoding' => 'UTF-8', + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT =?UTF-8?B?5pel5pys6Kqe7722776A7722776F776K776f?=', + 'body' => 'BODY 日本語', + ] + ], + ], + 'sending email 1, encoded, with half width katakanata, folding on' => [ + 'subject' => 'SUBJECT 日本語カタカナパ', + 'body' => 'BODY 日本語', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + 'test@test.com' + ], + 'replace' => null, + 'encoding' => 'UTF-8', + 'kv_folding' => true, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT =?UTF-8?B?5pel5pys6Kqe44Kr44K/44Kr44OK44OR?=', + 'body' => 'BODY 日本語', + ] + ], + ], + 'sending email 1, encoded subject ISO-2022-JP' => [ + 'subject' => 'SUBJECT 日本語', + 'body' => 'BODY 日本語', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + 'test@test.com' + ], + 'replace' => null, + 'encoding' => 'ISO-2022-JP', + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT =?ISO-2022-JP?B?GyRCRnxLXDhsGyhC?=', + // body is stored as UTF-8 in log and here, so both must be translated + 'body' => 'BODY 日本語', + ] + ], + ], + 'sending email 2' => [ + 'subject' => 'SUBJECT', + 'body' => 'BODY', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + 'e1@test.com', + 'e2@test.com' + ], + 'replace' => null, + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'e1@test.com', + 'subject' => 'SUBJECT', + 'body' => 'BODY', + ], + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'e2@test.com', + 'subject' => 'SUBJECT', + 'body' => 'BODY', + ] + ], + ], + 'sending email 1: dynamic' => [ + 'subject' => 'SUBJECT {FOO}', + 'body' => 'BODY {FOO} {VAR}', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + 'test@test.com' + ], + 'replace' => [ + 'FOO' => 'foo', + 'VAR' => 'bar', + ], + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT foo', + 'body' => 'BODY foo bar', + ] + ], + ], + 'sending email 1: dynamic encoded' => [ + 'subject' => 'SUBJECT 日本語 {FOO}', + 'body' => 'BODY 日本語 {FOO} {VAR}', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + 'test@test.com' + ], + 'replace' => [ + 'FOO' => 'foo', + 'VAR' => 'bar', + ], + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT =?UTF-8?B?5pel5pys6KqeIGZvbw==?=', + 'body' => 'BODY 日本語 foo bar', + ] + ], + ], + 'sending email 1: dynamic, to override' => [ + 'subject' => 'SUBJECT {FOO}', + 'body' => 'BODY {FOO} {VAR}', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + [ + 'email' => 'test@test.com', + 'replace' => [ + 'FOO' => 'foo to' + ] + ] + ], + 'replace' => [ + 'FOO' => 'foo', + 'VAR' => 'bar', + ], + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT foo to', + 'body' => 'BODY foo to bar', + ] + ], + ], + 'sending email 1: dynamic, to override encoded' => [ + 'subject' => 'SUBJECT 日本語 {FOO}', + 'body' => 'BODY 日本語 {FOO} {VAR}', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + [ + 'email' => 'test@test.com', + 'replace' => [ + 'FOO' => 'foo to' + ] + ] + ], + 'replace' => [ + 'FOO' => 'foo', + 'VAR' => 'bar', + ], + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 'test@test.com', + 'subject' => 'SUBJECT =?UTF-8?B?5pel5pys6KqeIGZvbyB0bw==?=', + 'body' => 'BODY 日本語 foo to bar', + ] + ], + ], + 'sending email 3: dynamic, to mixed override' => [ + 'subject' => 'SUBJECT {FOO}', + 'body' => 'BODY {FOO} {VAR}', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + [ + 'email' => 't1@test.com', + 'replace' => [ + 'FOO' => 'foo to 1' + ] + ], + [ + 'email' => 't2@test.com', + 'replace' => [ + 'FOO' => 'foo to 2' + ] + ], + [ + 'email' => 't3@test.com', + ], + ], + 'replace' => [ + 'FOO' => 'foo', + 'VAR' => 'bar', + ], + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 't1@test.com', + 'subject' => 'SUBJECT foo to 1', + 'body' => 'BODY foo to 1 bar', + ], + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 't2@test.com', + 'subject' => 'SUBJECT foo to 2', + 'body' => 'BODY foo to 2 bar', + ], + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 't3@test.com', + 'subject' => 'SUBJECT foo', + 'body' => 'BODY foo bar', + ], + ], + ], + 'sending email 3: dynamic, to mixed override encoded' => [ + 'subject' => 'SUBJECT 日本語 {FOO}', + 'body' => 'BODY 日本語 {FOO} {VAR}', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [ + [ + 'email' => 't1@test.com', + 'replace' => [ + 'FOO' => 'foo to 1' + ] + ], + [ + 'email' => 't2@test.com', + 'replace' => [ + 'FOO' => 'foo to 2' + ] + ], + [ + 'email' => 't3@test.com', + ], + ], + 'replace' => [ + 'FOO' => 'foo', + 'VAR' => 'bar', + ], + 'encoding' => null, + 'kv_folding' => null, + 'expected_status' => 2, + 'expected_content' => [ + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 't1@test.com', + 'subject' => 'SUBJECT =?UTF-8?B?5pel5pys6KqeIGZvbyB0byAx?=', + 'body' => 'BODY 日本語 foo to 1 bar', + ], + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 't2@test.com', + 'subject' => 'SUBJECT =?UTF-8?B?5pel5pys6KqeIGZvbyB0byAy?=', + 'body' => 'BODY 日本語 foo to 2 bar', + ], + [ + 'header' => [ + 'From' => 'test@test.com' + ], + 'to' => 't3@test.com', + 'subject' => 'SUBJECT =?UTF-8?B?5pel5pys6KqeIGZvbw==?=', + 'body' => 'BODY 日本語 foo bar', + ], + ], + ], + ]; + } + + /** + * Undocumented function + * + * @dataProvider sendEmailProvider + * @testdox email sending with expected status $expected_status [$_dataName] + * + * @param string $subject + * @param string $body + * @param string $from_email + * @param string $from_name + * @param array $to_email + * @param array|null $replace + * @param string|null $encoding + * @param bool|null $kv_folding + * @param int $expected_status + * @param array $expected_content + * @return void + */ + public function testSendEmail( + string $subject, + string $body, + string $from_email, + string $from_name, + array $to_email, + ?array $replace, + ?string $encoding, + ?bool $kv_folding, + int $expected_status, + array $expected_content + ): void { + if ($replace === null) { + $replace = []; + } + if ($encoding === null) { + $encoding = 'UTF-8'; + } + if ($kv_folding === null) { + $kv_folding = false; + } + // force new set for each run + self::$log->setLogUniqueId(true); + // set on of unique log id + self::$log->setLogPer('run', true); + // init logger + $status = \CoreLibs\Create\Email::sendEmail( + $subject, + $body, + $from_email, + $from_name, + $to_email, + $replace, + $encoding, + $kv_folding, + true, + self::$log + ); + $this->assertEquals( + $expected_status, + $status, + 'Assert sending status' + ); + // assert content: must load JSON from log file + if ($status == 2) { + // open file, get last entry with 'SEND EMAIL JSON' key + $file = file_get_contents(self::$log->getLogFileName()); + if ($file !== false) { + // extract SEND EMAIL JSON line + $found = preg_match_all("/^.* - (.*)$/m", $file, $matches); + // print "Found: $found | EMAIL: " . print_r($matches, true) . "\n"; + if (!empty($matches[1])) { + foreach ($matches[1] as $pos => $email_json) { + $email = \CoreLibs\Convert\Json::jsonConvertToArray($email_json); + // print "EMAIL: " . print_r($email, true) . "\n"; + $this->assertEquals( + $expected_content[$pos]['header']['From'] ?? 'MISSING FROM', + $email['header']['From'] ?? '', + 'Email check: assert header from' + ); + $this->assertEquals( + 'text/plain; charset=' . $encoding ?? 'UTF-8', + $email['header']['Content-type'] ?? '', + 'Email check: assert header content type' + ); + $this->assertEquals( + '1.0', + $email['header']['MIME-Version'] ?? '', + 'Email check: assert header mime version' + ); + $this->assertEquals( + $expected_content[$pos]['to'] ?? 'MISSING TO', + $email['to'] ?? '', + 'Email check: assert to' + ); + $this->assertEquals( + $expected_content[$pos]['subject'] ?? 'MISSING SUBJECT', + $email['subject'] ?? '', + 'Email check: assert subject' + ); + // body must be translated back to encoding if encoding is not UTF-8 + $this->assertEquals( + $encoding != 'UTF-8' ? + mb_convert_encoding($expected_content[$pos]['body'] ?? '', $encoding, 'UTF-8') : + $expected_content[$pos]['body'] ?? 'MISSING BODY', + $email['encoding'] != 'UTF-8' ? + mb_convert_encoding($email['body'] ?? '', $email['encoding'], 'UTF-8') : + $email['body'] ?? '', + 'Email check: assert body' + ); + } + } + } + } + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCreateHashTest.php b/test/phpunit/CoreLibsCreateHashTest.php new file mode 100644 index 0000000..fcab739 --- /dev/null +++ b/test/phpunit/CoreLibsCreateHashTest.php @@ -0,0 +1,212 @@ + [ + 'text' => 'Some String Text', + 'crc32b_reverse' => 'c5c21d91', // crc32b (in revere) + 'sha1Short' => '4d2bc9ba0', // sha1Short + // via hash + 'crc32b' => '911dc2c5', // hash: crc32b + 'adler32' => '31aa05f1', // hash: alder32 + 'fnv132' => '9df444f9', // hash: fnv132 + 'fnv1a32' => '2c5f91b9', // hash: fnv1a32 + 'joaat' => '50dab846', // hash: joaat + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function crc32bProvider(): array + { + $list = []; + foreach ($this->hashData() as $name => $values) { + $list[$name . ' to crc32b reverse'] = [ + 0 => $values['text'], + 1 => $values['crc32b_reverse'], + ]; + } + return $list; + } + + /** + * Undocumented function + * + * @return array + */ + public function sha1ShortProvider(): array + { + $list = []; + foreach ($this->hashData() as $name => $values) { + $list[$name . ' to sha1 short'] = [ + 0 => $values['text'], + 1 => $values['crc32b_reverse'], + 2 => $values['sha1Short'], + ]; + } + return $list; + } + + /** + * test all hash functions + * NOTE: if we add new hash functions in the __hash method + * they need to be added here too (and in the master hashData array too) + * + * @return array + */ + public function hashProvider(): array + { + $list = []; + foreach ($this->hashData() as $name => $values) { + foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat'] as $_hash_type) { + // default value test + if ($_hash_type === null) { + $hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT; + } else { + $hash_type = $_hash_type; + } + $list[$name . ' to ' . $hash_type] = [ + 0 => $values['text'], + 1 => $_hash_type, + 2 => $values[$hash_type] + ]; + } + } + return $list; + } + + /** + * Undocumented function + * + * @return array + */ + public function hashLongProvider(): array + { + $hash_source = 'Some String Text'; + return [ + 'Long Hash check: ' . \CoreLibs\Create\Hash::STANDARD_HASH_LONG => [ + $hash_source, + hash(\CoreLibs\Create\Hash::STANDARD_HASH_LONG, $hash_source) + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::__crc32b + * @dataProvider crc32bProvider + * @testdox __crc32b $input will be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testCrc32b(string $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::__crc32b($input) + ); + } + + /** + * Undocumented function + * + * @covers ::__sha1Short + * @dataProvider sha1ShortProvider + * @testdox __sha1Short $input will be $expected (crc32b) and $expected_sha1 (sha1 short) [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testSha1Short(string $input, string $expected, string $expected_sha1): void + { + // uses crc32b + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::__sha1Short($input) + ); + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::__sha1Short($input, false) + ); + // sha1 type + $this->assertEquals( + $expected_sha1, + \CoreLibs\Create\Hash::__sha1Short($input, true) + ); + } + + /** + * Undocumented function + * + * @covers ::__hash + * @dataProvider hashProvider + * @testdox __hash $input with $hash_type will be $expected [$_dataName] + * + * @param string $input + * @param string|null $hash_type + * @param string $expected + * @return void + */ + public function testHash(string $input, ?string $hash_type, string $expected): void + { + if ($hash_type === null) { + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::__hash($input) + ); + } else { + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::__hash($input, $hash_type) + ); + } + } + + /** + * Undocumented function + * + * @covers ::__hashLong + * @dataProvider hashLongProvider + * @testdox __hashLong $input will be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testHashLong(string $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::__hashLong($input) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCreateRandomKeyTest.php b/test/phpunit/CoreLibsCreateRandomKeyTest.php new file mode 100644 index 0000000..3662aa6 --- /dev/null +++ b/test/phpunit/CoreLibsCreateRandomKeyTest.php @@ -0,0 +1,205 @@ + [ + 0 => 6, + 1 => true, + 2 => 6, + ], + 'negative key length' => [ + 0 => -1, + 1 => false, + 2 => 4, + ], + 'tpp big key length' => [ + 0 => 300, + 1 => false, + 2 => 4, + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function randomKeyGenProvider(): array + { + return [ + 'default key length' => [ + 0 => null, + 1 => 4 + ], + 'set -1 key length default' => [ + 0 => -1, + 1 => 4, + ], + 'set too large key length' => [ + 0 => 300, + 1 => 4, + ], + 'set override key lenght' => [ + 0 => 6, + 1 => 6, + ], + ]; + } + + /** + * 1 + * + * @return array + */ + public function keepKeyLengthProvider(): array + { + return [ + 'set too large' => [ + 0 => 6, + 1 => 300, + 2 => 6, + ], + 'set too small' => [ + 0 => 8, + 1 => -2, + 2 => 8, + ], + 'change valid' => [ + 0 => 10, + 1 => 6, + 2 => 6, + ] + ]; + } + + /** + * run before each test and reset to default 4 + * + * @before + * + * @return void + */ + public function resetKeyLength(): void + { + \CoreLibs\Create\RandomKey::setRandomKeyLength(4); + } + + /** + * check that first length is 4 + * + * @covers ::getRandomKeyLength + * @testWith [4] + * @testdox getRandomKeyLength on init will be $expected [$_dataName] + * + * @param integer $expected + * @return void + */ + public function testGetRandomKeyLengthInit(int $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Create\RandomKey::getRandomKeyLength() + ); + } + + /** + * Undocumented function + * + * @covers ::setRandomKeyLength + * @covers ::getRandomKeyLength + * @dataProvider keyLenghtProvider + * @testdox setRandomKeyLength $input will be $expected, compare to $compare [$_dataName] + * + * @param integer $input + * @param boolean $expected + * @param integer $compare + * @return void + */ + public function testSetRandomKeyLength(int $input, bool $expected, int $compare): void + { + // set + $this->assertEquals( + $expected, + \CoreLibs\Create\RandomKey::setRandomKeyLength($input) + ); + // read test, if false, use compare check + if ($expected === false) { + $input = $compare; + } + $this->assertEquals( + $input, + \CoreLibs\Create\RandomKey::getRandomKeyLength() + ); + } + + /** + * Undocumented function + * + * @covers ::randomKeyGeyn + * @dataProvider randomKeyGenProvider + * @testdox randomKeyGen use $input key length $expected [$_dataName] + * + * @param integer|null $input + * @param integer $expected + * @return void + */ + public function testRandomKeyGen(?int $input, int $expected): void + { + if ($input === null) { + $this->assertEquals( + $expected, + strlen(\CoreLibs\Create\RandomKey::randomKeyGen()) + ); + } else { + $this->assertEquals( + $expected, + strlen(\CoreLibs\Create\RandomKey::randomKeyGen($input)) + ); + } + } + + /** + * Check that if set to n and then invalid, it keeps the previous one + * or if second change valid, second will be shown + * + * @covers ::setRandomKeyLength + * @dataProvider keepKeyLengthProvider + * @testdox keep setRandomKeyLength set with $input_valid and then $input_invalid will be $expected [$_dataName] + * + * @param integer $input_valid + * @param integer $input_invalid + * @param integer $expected + * @return void + */ + public function testKeepKeyLength(int $input_valid, int $input_invalid, int $expected): void + { + \CoreLibs\Create\RandomKey::setRandomKeyLength($input_valid); + \CoreLibs\Create\RandomKey::setRandomKeyLength($input_invalid); + $this->assertEquals( + $expected, + \CoreLibs\Create\RandomKey::getRandomKeyLength() + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCreateSessionTest.php b/test/phpunit/CoreLibsCreateSessionTest.php new file mode 100644 index 0000000..ba97d1a --- /dev/null +++ b/test/phpunit/CoreLibsCreateSessionTest.php @@ -0,0 +1,472 @@ + [ + 'sessionNameParameter', + 'p', + [ + 'checkCliStatus' => false, + 'getSessionStatus' => PHP_SESSION_NONE, + 'setSessionName' => true, + 'checkActiveSession' => [false, true], + 'getSessionId' => '1234abcd4567' + ], + 'sessionNameParameter', + '' + ], + 'session globals' => [ + 'sessionNameGlobals', + 'g', + [ + 'checkCliStatus' => false, + 'getSessionStatus' => PHP_SESSION_NONE, + 'setSessionName' => true, + 'checkActiveSession' => [false, true], + 'getSessionId' => '1234abcd4567' + ], + 'sessionNameGlobals', + '' + ], + 'session name default' => [ + '', + 'd', + [ + 'checkCliStatus' => false, + 'getSessionStatus' => PHP_SESSION_NONE, + 'setSessionName' => true, + 'checkActiveSession' => [false, true], + 'getSessionId' => '1234abcd4567' + ], + '', + '' + ], + // error checks + // 1: we are in cli + 'on cli error' => [ + '', + 'd', + [ + 'checkCliStatus' => true, + 'getSessionStatus' => PHP_SESSION_NONE, + 'setSessionName' => true, + 'checkActiveSession' => [false, true], + 'getSessionId' => '1234abcd4567' + ], + '', + '[SESSION] No sessions in php cli' + ], + // 2: session disabled + 'session disabled error' => [ + '', + 'd', + [ + 'checkCliStatus' => false, + 'getSessionStatus' => PHP_SESSION_DISABLED, + 'setSessionName' => true, + 'checkActiveSession' => [false, true], + 'getSessionId' => '1234abcd4567' + ], + '', + '[SESSION] Sessions are disabled' + ], + // 3: invalid session name: string + 'invalid name chars error' => [ + '1invalid$session#;', + 'p', + [ + 'checkCliStatus' => false, + 'getSessionStatus' => PHP_SESSION_NONE, + 'setSessionName' => false, + 'checkActiveSession' => [false, true], + 'getSessionId' => '1234abcd4567' + ], + '', + '[SESSION] Invalid session name: 1invalid$session#;' + ], + // 3: invalid session name: only numbers + 'invalid name numbers only error' => [ + '123', + 'p', + [ + 'checkCliStatus' => false, + 'getSessionStatus' => PHP_SESSION_NONE, + 'setSessionName' => false, + 'checkActiveSession' => [false, true], + 'getSessionId' => '1234abcd4567' + ], + '', + '[SESSION] Invalid session name: 123' + ], + // 3: invalid session name: invalid name short + // 3: invalid session name: too long (128) + // 4: failed to start session (2nd false on check active session) + 'invalid name numbers only error' => [ + '', + 'd', + [ + 'checkCliStatus' => false, + 'getSessionStatus' => PHP_SESSION_NONE, + 'setSessionName' => true, + 'checkActiveSession' => [false, false], + 'getSessionId' => '1234abcd4567' + ], + '', + '[SESSION] Failed to activate session' + ], + // 5: get session id return false + 'invalid name numbers only error' => [ + '', + 'd', + [ + 'checkCliStatus' => false, + 'getSessionStatus' => PHP_SESSION_NONE, + 'setSessionName' => true, + 'checkActiveSession' => [false, true], + 'getSessionId' => false + ], + '', + '[SESSION] getSessionId did not return a session id' + ], + ]; + } + + /** + * Test session start + * + * @covers ::startSession + * @dataProvider sessionProvider + * @testdox startSession $input name for $type will be $expected (error: $expected_error) [$_dataName] + * + * @param string $input + * @param string $type + * @param array $mock_data + * @param string $expected + * @param string $expected_error + * @return void + */ + public function testStartSession( + string $input, + string $type, + array $mock_data, + string $expected, + string $expected_error + ): void { + // override expected + if ($type == 'd') { + $expected = ini_get('session.name'); + } + /** @var \CoreLibs\Create\Session&MockObject $session_mock */ + $session_mock = $this->createPartialMock( + \CoreLibs\Create\Session::class, + [ + 'checkCliStatus', 'getSessionStatus', 'checkActiveSession', + 'setSessionName', 'startSessionCall', 'getSessionId', + 'getSessionName' + ] + ); + // set return values based requested input values + // OK: true + // error: false + $session_mock->method('checkCliStatus')->willReturn($mock_data['checkCliStatus']); + // OK: PHP_SESSION_ACTIVE, PHP_SESSION_NONE + // error: PHP_SESSION_DISABLED + $session_mock->method('getSessionStatus')->willReturn($mock_data['getSessionStatus']); + // false: try start + // true: skip start + // note that on second call if false -> error + $session_mock->method('checkActiveSession') + ->willReturnOnConsecutiveCalls( + $mock_data['checkActiveSession'][0], + $mock_data['checkActiveSession'][1], + ); + // dummy set for session name + $session_mock->method('setSessionName')->with($input)->willReturn($mock_data['setSessionName']); + // set session name & return bsed on request data + $session_mock->method('getSessionName')->willReturn($expected); + // will not return anything + $session_mock->method('startSessionCall'); + // in test case only return string + // false: will return false + $session_mock->method('getSessionId')->willReturn($mock_data['getSessionId']); + + // regex for session id + $ression_id_regex = "/^\w+$/"; + + unset($GLOBALS['SET_SESSION_NAME']); + $session_id = ''; + switch ($type) { + case 'p': + $session_id = $session_mock->startSession($input); + break; + case 'g': + $GLOBALS['SET_SESSION_NAME'] = $input; + $session_id = $session_mock->startSession(); + break; + case 'd': + $session_id = $session_mock->startSession(); + break; + } + // asert checks + if (!empty($session_id)) { + $this->assertMatchesRegularExpression( + $ression_id_regex, + (string)$session_id, + 'session id regex from retrun' + ); + $this->assertMatchesRegularExpression( + $ression_id_regex, + (string)$session_mock->getSessionId() + ); + $this->assertEquals( + $expected, + $session_mock->getSessionName() + ); + } else { + // false checks + $this->assertEquals( + $expected_error, + $session_mock->getErrorStr(), + 'error assert' + ); + } + } + + /** + * provider for session name check + * + * @return array + */ + public function sessionNameProvider(): array + { + // 0: string for session + // 1: expected return as bool + return [ + 'valid name' => [ + 'abc', + true + ], + 'valid name longer' => [ + 'something-abc-123', + true + ], + 'invalid name' => [ + 'abc#abc', + false + ], + 'only numbers' => [ + '123', + false + ], + 'longer than 128 chars' => [ + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' + . 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' + . 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', + false + ], + 'too short' => [ + '', + false + ], + ]; + } + + /** + * test valid session name + * + * @covers ::checkValidSessionName + * @dataProvider sessionNameProvider + * @testdox checkValidSessionName $input seessionn name is $expected [$_dataName] + * + * @param string $input + * @param bool $expected + * @return void + */ + public function testCheckValidSessionName(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Create\Session::checkValidSessionName($input) + ); + } + + /** + * provider for set/get tests + * + * @return array + */ + public function sessionDataProvider(): array + { + return [ + 'test' => [ + 'foo', + 'bar', + 'bar', + ], + 'int key test' => [ + 123, + 'bar', + 'bar', + ], + // more complex value tests + 'array values' => [ + 'array', + [1, 2, 3], + [1, 2, 3], + ] + ]; + } + + /** + * method call test + * + * @covers ::setS + * @covers ::getS + * @covers ::issetS + * @covers ::unsetS + * @dataProvider sessionDataProvider + * @testdox setS/getS/issetS/unsetS $name with $input is $expected [$_dataName] + * + * @param string|int $name + * @param mixed $input + * @param mixed $expected + * @return void + */ + public function testMethodSetGet($name, $input, $expected): void + { + $session = new \CoreLibs\Create\Session(); + $session->setS($name, $input); + $this->assertEquals( + $expected, + $session->getS($name), + 'method set assert' + ); + // isset true + $this->assertTrue( + $session->issetS($name), + 'method isset assert ok' + ); + $session->unsetS($name); + $this->assertEquals( + '', + $session->getS($name), + 'method unset assert' + ); + // iset false + $this->assertFalse( + $session->issetS($name), + 'method isset assert false' + ); + } + + /** + * magic call test + * + * @covers ::__set + * @covers ::__get + * @covers ::__isset + * @covers ::__unset + * @dataProvider sessionDataProvider + * @testdox __set/__get/__iseet/__unset $name with $input is $expected [$_dataName] + * + * @param string|int $name + * @param mixed $input + * @param mixed $expected + * @return void + */ + public function testMagicSetGet($name, $input, $expected): void + { + $session = new \CoreLibs\Create\Session(); + $session->$name = $input; + $this->assertEquals( + $expected, + $session->$name, + 'magic set assert' + ); + // isset true + $this->assertTrue( + isset($session->$name), + 'magic isset assert ok' + ); + unset($session->$name); + $this->assertEquals( + '', + $session->$name, + 'magic unset assert' + ); + // isset true + $this->assertFalse( + isset($session->$name), + 'magic isset assert false' + ); + } + + /** + * unset all test + * + * @covers ::unsetAllS + * @testdox unsetAllS test + * + * @return void + */ + public function testUnsetAll(): void + { + $test_values = [ + 'foo' => 'abc', + 'bar' => '123' + ]; + $session = new \CoreLibs\Create\Session(); + foreach ($test_values as $name => $value) { + $session->setS($name, $value); + // confirm set + $this->assertEquals( + $value, + $session->getS($name), + 'set assert: ' . $name + ); + } + // unset all + $session->unsetAllS(); + // check unset + foreach (array_keys($test_values) as $name) { + $this->assertEquals( + '', + $session->getS($name), + 'unsert assert: ' . $name + ); + } + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsCreateUidsTest.php b/test/phpunit/CoreLibsCreateUidsTest.php new file mode 100644 index 0000000..a914cb9 --- /dev/null +++ b/test/phpunit/CoreLibsCreateUidsTest.php @@ -0,0 +1,186 @@ + [ + 0 => 'md5', + 1 => 32, + ], + 'sha256 hash' => [ + 0 => 'sha256', + 1 => 64 + ], + 'ripemd160 hash' => [ + 0 => 'ripemd160', + 1 => 40 + ], + 'adler32 hash' => [ + 0 => 'adler32', + 1 => 8 + ], + 'not in list hash but valid' => [ + 0 => 'sha3-512', + 1 => strlen(hash('sha3-512', 'A')) + ], + 'default hash not set' => [ + 0 => null, + 1 => 64, + ], + 'invalid name' => [ + 0 => 'iamnotavalidhash', + 1 => 64, + ], + 'auto: ' . \CoreLibs\Create\Uids::DEFAULT_HASH => [ + 0 => \CoreLibs\Create\Uids::DEFAULT_HASH, + 1 => strlen(hash(\CoreLibs\Create\Uids::DEFAULT_HASH, 'A')) + ], + 'auto: ' . \CoreLibs\Create\Uids::STANDARD_HASH_LONG => [ + 0 => \CoreLibs\Create\Uids::STANDARD_HASH_LONG, + 1 => strlen(hash(\CoreLibs\Create\Uids::STANDARD_HASH_LONG, 'A')) + ], + 'auto: ' . \CoreLibs\Create\Uids::STANDARD_HASH_SHORT => [ + 0 => \CoreLibs\Create\Uids::STANDARD_HASH_SHORT, + 1 => strlen(hash(\CoreLibs\Create\Uids::STANDARD_HASH_SHORT, 'A')) + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function uniqIdLongProvider(): array + { + return [ + 'uniq id long: ' . \CoreLibs\Create\Uids::STANDARD_HASH_LONG => [ + strlen(hash(\CoreLibs\Create\Uids::STANDARD_HASH_LONG, 'A')) + ], + ]; + } + + + /** + * must match 7e78fe0d-59b8-4637-af7f-e88d221a7d1e + * + * @covers ::uuidv4 + * @testdox uuidv4 check that return is matching regex [$_dataName] + * + * @return void + */ + public function testUuidv4(): void + { + $uuid = \CoreLibs\Create\Uids::uuidv4(); + $this->assertMatchesRegularExpression( + '/^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/', + $uuid + ); + // $this->assertStringMatchesFormat( + // '%4s%4s-%4s-%4s-%4s-%4s%4s%4s', + // $uuid + // ); + } + + /** + * Undocumented function + * + * @covers ::uniqId + * @dataProvider uniqIdProvider + * @testdox uniqId $input will be length $expected [$_dataName] + * + * @param string|null $input + * @param string $expected + * @return void + */ + public function testUniqId(?string $input, int $expected): void + { + if ($input === null) { + $this->assertEquals( + $expected, + strlen(\CoreLibs\Create\Uids::uniqId()) + ); + } else { + $this->assertEquals( + $expected, + strlen(\CoreLibs\Create\Uids::uniqId($input)) + ); + } + } + + /** + * Because we set a constant here, we can only run one test + * so we test invalid one to force check + * + * @covers ::uniqId + * @#dataProvider uniqIdProvider + * @testWith ["invalidhash", 64] + * @testdox uniqId use DEFAULT_HASH set $input with length $expected [$_dataName] + * + * @return void + */ + public function testUnidIdDefaultHash(string $input, int $expected): void + { + define('DEFAULT_HASH', $input); + $this->assertEquals( + $expected, + strlen(\CoreLibs\Create\Uids::uniqId()) + ); + } + + /** + * Short id, always 8 in length + * + * @covers ::uniqIdShort + * @testWith [8] + * @testdox uniqIdShort will be length $expected [$_dataName] + * + * @param integer $expected + * @return void + */ + public function testUniqIdShort(int $expected): void + { + $this->assertEquals( + $expected, + strlen(\CoreLibs\Create\Uids::uniqIdShort()) + ); + } + + /** + * Long Id, length can change + * + * @covers ::uniqIdLong + * @dataProvider uniqIdLongProvider + * @testdox uniqIdLong will be length $expected [$_dataName] + * + * @param integer $expected + * @return void + */ + public function testUniqIdLong(int $expected): void + { + $this->assertEquals( + $expected, + strlen(\CoreLibs\Create\Uids::uniqIdLong()) + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsDBExtendedArrayIOTest.php b/test/phpunit/CoreLibsDBExtendedArrayIOTest.php new file mode 100644 index 0000000..f5204ed --- /dev/null +++ b/test/phpunit/CoreLibsDBExtendedArrayIOTest.php @@ -0,0 +1,48 @@ +markTestSkipped( + 'The PgSQL extension is not available.' + ); + } + } + + /** + * Undocumented function + * + * @testdox DB\Extended\ArrayIO Class tests + * + * @return void + */ + public function testArrayDBIO() + { + // $this->assertTrue(true, 'DB Extended ArrayIO Tests not implemented'); + $this->markTestIncomplete( + 'DB\Extended\ArrayIO Tests have not yet been implemented' + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsDBIOTest.php b/test/phpunit/CoreLibsDBIOTest.php new file mode 100644 index 0000000..8b4ba09 --- /dev/null +++ b/test/phpunit/CoreLibsDBIOTest.php @@ -0,0 +1,4470 @@ + [ + '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, + ], + // valid with no schema set + 'valid_no_schema' => [ + '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' => '', + 'db_type' => 'pgsql', + 'db_encoding' => '', + '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 for DB\IO test.' + ); + } + // 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"); + } + // uid is for internal reference tests + $base_table = <<dbExec( + // primary key name is table + '_id' + <<dbExec( + <<dbExec( + <<dbExec("CREATE SCHEMA IF NOT EXISTS testschema"); + // end connection + $db->dbClose(); + } + + /** + * Check that we can actually do these tests + * + * @return void + */ + protected function setUp(): void + { + // print_r(self::$db_config); + } + + /** + * For all Warning and Error checks in all tests + * DB Warning/Error checks + * + * @param \CoreLibs\DB\IO $db + * @param string $warning + * @param string $error + * @return array + */ + private function subAssertErrorTest( + \CoreLibs\DB\IO $db, + string $warning, + string $error + ): array { + // 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, + 'Assert query warning' + ); + $this->assertEquals( + $error, + $last_error, + 'Assert query warning' + ); + return [$last_warning, $last_error]; + } + + // - connected version test + // dbVerions, dbVersionNum, dbVersionInfo, dbVersionInfoParameters, + // dbCompareVersion + + /** + * Just checks that the return value of dbVersion matches basic regex + * + * @covers ::dbVersion + * @testdox test db version string return matching retex + * + * @return void + */ + public function testDbVersion(): void + { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + $this->assertMatchesRegularExpression( + "/^\d+\.\d+(\.\d+)?$/", + $db->dbVersion() + ); + + $db->dbClose(); + } + + /** + * Undocumented function + * + * @covers ::dbVersionNumeric + * @testdox test db version numeric return matching retex + * + * @return void + */ + public function testDbVersionNumeric(): void + { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + $version = $db->dbVersionNumeric(); + // must be int + $this->assertIsInt($version); + // assume 90606 or 130006 + // should this change, the regex below must change + $this->assertMatchesRegularExpression( + "/^\d{5,6}?$/", + (string)$version + ); + + $db->dbClose(); + } + + /** + * Undocumented function + * + * @covers ::dbVersionInfoParameters + * @testdox test db version parameters are returned as array + * + * @return void + */ + public function testDbVersionInfoParameters(): void + { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + $parameters = $db->dbVersionInfoParameters(); + + $this->assertIsArray($parameters); + $this->assertGreaterThan( + 1, + count($parameters) + ); + // must have at least this + $this->assertContains( + 'server', + $parameters + ); + + $db->dbClose(); + } + + /** + * Undocumented function + * + * @return array + */ + public function versionInfoProvider(): array + { + return [ + 'client' => [ + 'client', + "/^\d+\.\d+/" + ], + 'session authorization' => [ + 'session_authorization', + "/^\w+$/" + ], + 'test non existing' => [ + 'non_existing', + '/^$/' + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::dbVersionInfo + * @dataProvider versionInfoProvider + * @testdox Version Info $parameter matches as $expected [$_dataName] + * + * @param string $parameter + * @param string $expected + * @return void + */ + public function testDbVersionInfo(string $parameter, string $expected): void + { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + $this->assertMatchesRegularExpression( + $expected, + $db->dbVersionInfo($parameter) + ); + + $db->dbClose(); + } + + /** + * Returns test list for dbCompareVersion check + * NOTE: unless we fully mock the =version check needs to be updated + * + * @return array + */ + public function versionProvider(): array + { + // 1: vesion to compare to + // 2: expected outcome + return [ + 'compare = ok' => [ '=13.6.0', true ], + 'compare = bad' => [ '=9.2.0', false ], + 'compare < ok' => [ '<99.0.0', true ], + 'compare < bad' => [ '<9.2.0', false ], + 'compare <= ok a' => [ '<=99.0.0', true ], + 'compare <= ok b' => [ '<=13.6.0', true ], + 'compare <= false' => [ '<=9.2.0', false ], + 'compare > ok' => [ '>9.2.0', true ], + 'compare > bad' => [ '>99.2.0', false ], + 'compare >= ok a' => [ '>=13.6.0', true ], + 'compare >= ok b' => [ '>=9.2.0', true ], + 'compare >= bad' => [ '>=99.0.0', false ], + ]; + } + + /** + * NOTE + * Version tests will fail if versions change + * Current base as Version 13.6 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 + { + /** @var \CoreLibs\DB\IO&MockObject $db_io_mock */ + $db_io_mock = $this->createPartialMock(\CoreLibs\DB\IO::class, ['dbVersion']); + $db_io_mock->method('dbVersion')->willReturn('13.6.0'); + + $this->assertEquals( + $expected, + // $db->dbCompareVersion($input) + $db_io_mock->dbCompareVersion($input) + ); + } + + // - 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(); + } + + // - 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), + 'Assert error to bytea' + ); + $this->assertEquals( + $input, + $db->dbUnescapeBytea($expected), + 'Assert error from bytes' + ); + + $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] + // TODO failed db connection + // connection busy [async] (41) + // TODO connection busy + // 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 + // FIX with socket check type + '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(); + + // assert never called query is 0 + $this->assertEquals( + 0, + $db->dbGetQueryCalled($query), + 'Assert never called query is null' + ); + + // 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) + ); + } else { + $result = $pk_name === null ? + $db->dbExec($query) : + $db->dbExec($query, $pk_name); + // if PHP or newer, must be Object PgSql\Result + if (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { + $this->assertIsObject( + $result + ); + // also check that this is correct instance type + $this->assertInstanceOf( + 'PgSql\Result', + $result + ); + } else { + $this->assertIsResource( + $result + ); + } + } + // 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) + ); + // 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->subAssertErrorTest($db, $warning, $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 + // if string for warning or error is not empty check + $this->subAssertErrorTest($db, $warning, $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 + // if string for warning or error is not empty check + $this->subAssertErrorTest($db, $warning, $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"; + $row_a = [ + 'row_int' => 1, + 0 => 1, + 'uid' => 'A', + 1 => 'A' + ]; + $row_a_assoc = [ + 'row_int' => 1, + 'uid' => 'A', + ]; + $row_b = [ + 'row_int' => 2, + 0 => 2, + 'uid' => 'B', + 1 => 'B' + ]; + $row_b_assoc = [ + 'row_int' => 2, + 'uid' => 'B', + ]; + // 0: read query + // 1: reset flag, null for default + // 2: assoc flag, null for default + // 3: expected return (cursor_ext/data) + // 4: step through, or normal loop read + // 5: cursor ext compare array + // 6: first only, extended cursor (for each step) + // 7: warning + // 8: error + // 9: insert data + return [ + // *** READ STEP BY STEP + // default cache: USE_CACHE + 'valid select, default cache settings' => [ + $read_query, + null, + null, + $row_a, + true, + // check cursor_ext + [ + // if <8.1 check against resource + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + // extended cursor per step (first only true) + [ + // second row + [ + 'data' => [ + 0 => $row_b, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a, + 1 => $row_b, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a, + 1 => $row_b, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], + '', + '', + $insert_query + ], + 'valid select, use cache, assoc only' => [ + $read_query, + \CoreLibs\DB\IO::USE_CACHE, + true, + $row_a_assoc, + true, + // is same as default + [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + [ + // second row + [ + 'data' => [ + 0 => $row_b_assoc, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], + '', + '', + $insert_query + ], + 'valid select, read new, assoc only' => [ + $read_query, + \CoreLibs\DB\IO::READ_NEW, + true, + $row_a_assoc, + true, + [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::READ_NEW, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + [ + // second row + [ + 'data' => [ + 0 => $row_b_assoc, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::READ_NEW, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::READ_NEW, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], + '', + '', + $insert_query + ], + 'valid select, clear cache, assoc only' => [ + $read_query, + \CoreLibs\DB\IO::CLEAR_CACHE, + true, + $row_a_assoc, + true, + [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::CLEAR_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + [ + // second row + [ + 'data' => [ + 0 => $row_b_assoc, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::CLEAR_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::CLEAR_CACHE, + 'assoc_flag' => true, + 'cached' => false, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], + '', + '', + $insert_query + ], + 'valid select, no cache, assoc only' => [ + $read_query, + \CoreLibs\DB\IO::NO_CACHE, + true, + $row_a_assoc, + true, + [ + 'cursor' => 'PgSql\Result', + 'data' => [], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::NO_CACHE, + 'assoc_flag' => true, + 'cached' => false, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + [ + // second row + [ + 'data' => [ + 0 => $row_b_assoc, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::NO_CACHE, + 'assoc_flag' => true, + 'cached' => false, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::NO_CACHE, + 'assoc_flag' => true, + 'cached' => false, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], + '', + '', + $insert_query + ], + // *** READ STEP BY STEP, ERROR TRIGGER + 'empty select error' => [ + '', + null, + null, + false, + true, + [], + [], + '', + '11', + $insert_query, + ], + 'insert query error' => [ + $insert_query, + null, + null, + false, + true, + [], + [], + '', + '17', + $insert_query + ], + // *** READ AS LOOP + // from here on a complex read all full tests + 'valid select, full read DEFAULT CACHE' => [ + $read_query, + null, + null, + [$row_a, $row_b,], + false, + [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a, + 1 => $row_b, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ], + [], + '', + '', + $insert_query + ], + // READ_NEW + 'valid select, full read READ NEW' => [ + $read_query, + \CoreLibs\DB\IO::READ_NEW, + null, + [$row_a, $row_b,], + false, + [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a, + 1 => $row_b, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::READ_NEW, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ], + [], + '', + '', + $insert_query + ], + // CLEAR_CACHE + 'valid select, full read CLEAR CACHE' => [ + $read_query, + \CoreLibs\DB\IO::CLEAR_CACHE, + null, + [$row_a, $row_b,], + false, + [ + 'cursor' => 1, + 'data' => [ + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::CLEAR_CACHE, + 'assoc_flag' => false, + 'cached' => false, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ], + [], + '', + '', + $insert_query + ], + 'valid select, full read NO CACHE' => [ + $read_query, + \CoreLibs\DB\IO::NO_CACHE, + null, + [$row_a, $row_b,], + false, + [ + 'cursor' => 1, + 'data' => [ + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::NO_CACHE, + 'assoc_flag' => false, + 'cached' => false, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ], + [], + '', + '', + $insert_query + ], + ]; + } + + /** + * dbReturn cursor extended checks + * + * @param \CoreLibs\DB\IO $db + * @param string $query + * @param array $cursor_ext_checks + * @return void + */ + private function subAssertCursorExtTestDbReturnFunction( + \CoreLibs\DB\IO $db, + string $query, + array $cursor_ext_checks + ): void { + // cursor check + if ( + empty($db->dbGetLastWarning()) && + empty($db->dbGetLastError()) && + count($cursor_ext_checks) + ) { + $cursor_ext = $db->dbGetCursorExt($query); + foreach ($cursor_ext_checks as $key => $expected) { + if ($key != 'cursor') { + $this->assertEquals( + $expected, + $cursor_ext[$key], + 'assert equal cursor ext for ' . $key + ); + } else { + // for int, it has to be one + // else depends on PHP version, either object or REsource + if (is_int($expected)) { + $this->assertEquals( + 1, + $cursor_ext[$key], + 'assert equal cursor ext cursor int 1' + ); + } elseif (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { + $this->assertIsObject( + $cursor_ext[$key], + 'assert is object cursor ext cursor' + ); + // also check that this is correct instance type + $this->assertInstanceOf( + $expected, + $cursor_ext[$key], + 'assert is instance of cursor ext cursor' + ); + } else { + $this->assertIsResource( + $cursor_ext[$key], + 'assert is resource cursor ext cursor' + ); + } + } + } + } + } + + /** + * dbReturn Function Test + * + * @covers ::dbReturn + * @covers ::dbCacheReset + * @covers ::dbGetCursorExt + * @covers ::dbCursorPos + * @covers ::dbCursorNumRows + * @dataProvider dbReturnProvider + * @testdox dbReturn Read Frist $read_first_only only and cache $flag_cache and assoc $flag_assoc with (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 array $cursor_ext_checks_step + * @param string $warning + * @param string $error + * @param string $insert_data + * @return void + */ + public function testDbReturnFunction( + string $query, + ?int $flag_cache, + ?bool $flag_assoc, + $expected, + bool $read_first_only, + array $cursor_ext_checks, + array $cursor_ext_checks_step, + 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 data + $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) + ), + 'Assert dbReturn first only equal' + ); + $this->subAssertErrorTest($db, $warning, $error); + $this->subAssertCursorExtTestDbReturnFunction($db, $query, $cursor_ext_checks); + // extended checks. read until end of data and check per steck cursor data + foreach ($cursor_ext_checks_step as $_cursor_ext_checks) { + // each step matches a read step + } + } else { + // read all, and then do result compare + // 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), + 'Assert dbReturn pos' + ); + } + // does count match for returned data and the cursor num rows + $this->assertEquals( + count($data), + $db->dbGetCursorNumRows($query), + 'Assert dbReturn num rows' + ); + // run cursor ext checks after first run + $this->subAssertCursorExtTestDbReturnFunction($db, $query, $cursor_ext_checks); + // 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), + 'Assert dbReturn reset cache' + ); + // New run after reset + // for read all, test that two reads result in same data + $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), + 'Assert dbReturn double read 1 pos: ' . $flag_cache + ); + } + $this->subAssertCursorExtTestDbReturnFunction($db, $query, $cursor_ext_checks); + $data_second = []; + $pos_second = 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_second[] = $res; + $pos_second++; + // check cursor pos + $this->assertEquals( + $pos_second, + $db->dbGetCursorPos($query), + 'Assert dbReturn double read 2 pos: ' . $flag_cache + ); + } + $this->assertEquals( + $data, + $data_second, + 'Assert first and second run are equal: return data' + ); + $this->assertEquals( + $pos, + $pos_second, + 'Assert first and second run are equal: count' + ); + // run cursor ext checks after second run + $this->subAssertCursorExtTestDbReturnFunction($db, $query, $cursor_ext_checks); + } + + // 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, dbGetPrepareCursorValue + + /** + * Undocumented function + * + * @return array + */ + public function preparedProvider(): 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: statement name + // 1: query to prepare + // 2: primary key name: null for default run + // 3: arguments for query (single array for all) + // 4: expected prepare return + // 5: prepare warning + // 6: prepare error + // 7: expected execute return + // 8: execute warning + // 9: execute error + // 11: read query (if insert/update) + // 11: execute data to check (array) + // 12: insert data + // 13: prepated cursor array data match values + return [ + // insert + 'prepare query insert' => [ + // base statements 0-3 + 'insert', + "INSERT INTO table_with_primary_key (row_int, uid) VALUES " + . "($1, $2)", + null, + [990, 'TEST A'], + // prepare (4-6) + 'result', '', '', + // execute + 'result', '', '', + // check query and compare data (for insert/update) + $read_query, + [ + [ + 'row_int' => 990, + 'uid' => 'TEST A', + ], + ], + // insert data (for select) + '', + // get prepared data + [ + 'pk_name' => 'table_with_primary_key_id', + 'count' => 2, + 'query' => 'INSERT INTO table_with_primary_key (row_int, uid) ' + . 'VALUES ($1, $2) RETURNING table_with_primary_key_id', + 'returning_id' => true, + ], + ], + // update + 'prepare query update' => [ + 'update', + "UPDATE table_with_primary_key SET " + . "row_int = $1, " + . "row_varchar = $2 " + . "WHERE uid = $3", + null, + [550, 'I AM NEW TEXT', 'TEST A'], + // + 'result', '', '', + // + 'result', '', '', + // + "SELECT row_int, row_varchar FROM table_with_primary_key " + . "WHERE uid = 'TEST A'", + [ + [ + 'row_int' => 550, + 'row_varchar' => 'I AM NEW TEXT', + ], + ], + // + "INSERT INTO table_with_primary_key (row_int, uid) VALUES " + . "(111, 'TEST A')", + // + [ + 'pk_name' => '', + 'count' => 3, + 'query' => 'UPDATE table_with_primary_key SET row_int = $1, ' + . 'row_varchar = $2 WHERE uid = $3', + 'returning_id' => false, + ], + ], + // select + 'prepare select query' => [ + 'select', + $read_query + . " WHERE uid = $1", + null, + ['A'], + // + 'result', '', '', + // execute here needs to read too + 'result', '', '', + // + '', + [ + [ + 'row_int' => 1, + 'uid' => 'A', + ], + ], + // + $insert_query, + // + [ + 'pk_name' => '', + 'count' => 1, + 'query' => 'SELECT row_int, uid FROM table_with_primary_key WHERE uid = $1', + 'returning_id' => false, + ], + ], + // any query but with no parameters + 'prepare select query no parameter' => [ + 'select_noparam', + $read_query, + null, + null, + // + 'result', '', '', + // execute here needs to read too + 'result', '', '', + // + '', + [ + [ + 'row_int' => 1, + 'uid' => 'A', + ], + [ + 'row_int' => 2, + 'uid' => 'B', + ], + ], + // + $insert_query, + // + [ + 'pk_name' => '', + 'count' => 0, + 'query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'returning_id' => false, + ], + ], + // no statement name (25) + 'empty statement' => [ + '', + 'SELECT', + null, + [], + // + false, '', '25', + // + false, '', '25', + // + '', + [], + // + '', + // + [ + 'pk_name' => '', + 'count' => 0, + 'query' => '', + 'returning_id' => false, + ], + ], + // no query (prepare 11) + // no prepared cursor found with statement name (execute 24) + 'empty query' => [ + 'Empty Query', + '', + null, + [], + // + false, '', '11', + // + false, '', '24', + // + '', + [], + // + '', + // + [ + 'pk_name' => '', + 'count' => 0, + 'query' => '', + 'returning_id' => false, + ], + ], + // no db connection (prepare/execute 16) + // TODO no db connection test + // connection busy (prepare/execute 41) + // TODO connection busy test + // query could not be prepare (prepare 21) + // TODO query could not be prepared test + // some query with same statement name exists (prepare W20) + 'prepare query with same statement name' => [ + 'double', + $read_query, + null, + null, + // + true, '20', '', + // + 'result', '', '', + // no query but data for data only compare + '', + [], + // + $insert_query, + // + [ + 'pk_name' => '', + 'count' => 0, + 'query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'returning_id' => false, + ], + ], + // insert wrong data count compared to needed (execute 23) + 'wrong parmeter count' => [ + 'wrong_param_count', + "INSERT INTO table_with_primary_key (row_int, uid) VALUES " + . "($1, $2)", + null, + [], + // + 'result', '', '', + // + false, '', '23', + // + '', + [], + // + '', + // + [ + 'pk_name' => 'table_with_primary_key_id', + 'count' => 2, + 'query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES ' + . '($1, $2) RETURNING table_with_primary_key_id', + 'returning_id' => true, + ], + ], + // execute does not return a result (22) + // TODO execute does not return a result + ]; + } + + /** + * Undocumented function + * + * @covers ::dbPrepare + * @covers ::dbExecute + * @covers ::dbFetchArray + * @covers ::dbGetPrepareCursorValue + * @dataProvider preparedProvider + * @testdox prepared query $stm_name with $expected_prepare (warning $warning_prepare/error $error_prepare) and $expected_execute (warning $warning_execute/error $error_execute) [$_dataName] + * + * @param string $stm_name + * @param string $query + * @param string|null $pk_name + * @param array|null $query_data + * @param bool|string $expected_prepare + * @param string $warning_prepare + * @param string $error_prepare + * @param bool|string $expected_execute + * @param string $warning_execute + * @param string $error_execute + * @param string $expected_data_query + * @param array $expected_data + * @param string $insert_data + * @param array $prepare_cursor + * @return void + */ + 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, + string $expected_data_query, + array $expected_data, + string $insert_data, + array $prepare_cursor, + ): 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 + if (!empty($insert_data)) { + $db->dbExec($insert_data); + } + + // test prepare + $prepare_result = $pk_name === null ? + $db->dbPrepare($stm_name, $query) : + $db->dbPrepare($stm_name, $query, $pk_name); + // if warning is 20, call prepare again + if ($warning_prepare == '20') { + $prepare_result = $pk_name === null ? + $db->dbPrepare($stm_name, $query) : + $db->dbPrepare($stm_name, $query, $pk_name); + } + // if result type, or if forced bool + if (is_string($expected_prepare) && $expected_prepare == 'result') { + // if PHP or newer, must be Object PgSql\Result + if (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { + $this->assertIsObject( + $prepare_result + ); + // also check that this is correct instance type + $this->assertInstanceOf( + 'PgSql\Result', + $prepare_result + ); + } else { + $this->assertIsResource( + $prepare_result + ); + } + } else { + $this->assertEquals( + $expected_prepare, + $prepare_result + ); + } + // error/warning check + $this->subAssertErrorTest($db, $warning_prepare, $error_prepare); + + // for non fail prepare test exec + // check test result + $execute_result = $query_data === null ? + $db->dbExecute($stm_name) : + $db->dbExecute($stm_name, $query_data); + if ($expected_execute == 'result') { + // if PHP or newer, must be Object PgSql\Result + if (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { + $this->assertIsObject( + $execute_result + ); + // also check that this is correct instance type + $this->assertInstanceOf( + 'PgSql\Result', + $execute_result + ); + // if this is an select use dbFetchArray to get data and test + } else { + $this->assertIsResource( + $execute_result + ); + } + } else { + $this->assertEquals( + $expected_execute, + $execute_result + ); + } + // error/warning check + $this->subAssertErrorTest($db, $warning_execute, $error_execute); + // now check test result if expected return is result + if ( + $expected_execute == 'result' && + !empty($expected_data_query) + ) { + // $expected_data_query + // $expected_data + $rows = $db->dbReturnArray($expected_data_query); + $this->assertEquals( + $expected_data, + $rows + ); + } + if ( + $expected_execute == 'result' && + $execute_result !== false && + empty($expected_data_query) && + count($expected_data) + ) { + // compare previously read data to compare data + $compare_data = []; + // read in the query data + while (is_array($row = $db->dbFetchArray($execute_result, true))) { + $compare_data[] = $row; + } + $this->assertEquals( + $expected_data, + $compare_data + ); + } + + // check dbGetPrepareCursorValue + foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) { + $this->assertEquals( + $prepare_cursor[$key], + $db->dbGetPrepareCursorValue($stm_name, $key), + 'Prepared cursor: ' . $key . ': failed assertion' + ); + } + + // reset all data + $db->dbExec("TRUNCATE table_with_primary_key"); + $db->dbExec("TRUNCATE table_without_primary_key"); + // close connection + $db->dbClose(); + } + + // dedicated error checks for prepare cursor return + + /** + * Undocumented function + * + * @return array + */ + public function preparedProviderValue(): array + { + // 1: query (can be empty for do not set) + // 2: stm name + // 3: key + // 4: expected error return + return [ + 'no error' => [ + "SELECT row_int, uid FROM table_with_primary_key", + 'read', + 'pk_name', + '' + ], + 'statement name empty' => [ + '', + '', + '', + '101', + ], + 'key empty' => [ + '', + 'read', + '', + '102', + ], + 'key invalid' => [ + '', + 'read', + 'invalid', + '102', + ], + 'statement name not found' => [ + '', + 'invalid', + 'pk_name', + '103', + ], + ]; + } + + /** + * test return prepare cursor errors + * + * @covers ::dbGetPrepareCursorValue + * @dataProvider preparedProviderValue + * @testdox prepared query $stm_name with $key expect error id $error_id [$_dataName] + * + * @param string $query + * @param string $stm_name + * @param string $key + * @param string $error_id + * @return void + */ + public function testDbGetPrepareCursorValue( + string $query, + string $stm_name, + string $key, + $error_id + ): void { + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + if (!empty($query)) { + $db->dbPrepare($stm_name, $query); + $db->dbExecute($stm_name); + } + $db->dbGetPrepareCursorValue($stm_name, $key); + // match check error + $last_error = $db->dbGetLastError(); + $this->assertEquals( + $error_id, + $last_error, + 'get prepare cursor value error check' + ); + } + + // - schema set/get tests + // dbGetSchema, dbSetSchema + + /** + * Undocumented function + * + * @return array + */ + public function schemaProvider(): array + { + // 0: db connection + // 1: schema to set + // 2: set result + // 3: set error + // 4: get result flagged + // 5: get result DB + return [ + 'schema get check only' => [ + 'valid', + null, + true, + '', + 'public', + 'public', + ], + 'new schema set' => [ + 'valid', + 'public', + true, + '', + 'public', + 'public', + ], + 'no schema set, set new schema' => [ + 'valid_no_schema', + 'public', + true, + '', + 'public', + 'public', + ], + 'try to set empty schema' => [ + 'valid', + '', + false, + '70', + 'public', + 'public', + ], + // invalid schema (does not throw error) + 'try to set empty schema' => [ + 'valid', + 'invalid', + false, + '71', + 'public', + 'public', + ], + // valid different schema + 'try to set new valid schema' => [ + 'valid', + 'testschema', + true, + '', + 'testschema', + 'testschema', + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::dbSetSchema + * @covers ::dbGetSchema + * @dataProvider schemaProvider + * @testdox set schema $schema on $connection with $expected_set (error $error_set) and get $expected_get_var/$expected_get_db [$_dataName] + * + * @param string $connection + * @param string|null $schema + * @param boolean $expected_set + * @param string $error_set + * @param string $expected_get_var + * @param string $expected_get_db + * @return void + */ + public function testDbSchema( + string $connection, + ?string $schema, + bool $expected_set, + string $error_set, + string $expected_get_var, + string $expected_get_db + ): void { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config[$connection], + self::$log + ); + + // schema is not null, we do set testing + if ($schema !== null) { + $result_set = $db->dbSetSchema($schema); + $last_error = $db->dbGetLastError(); + $this->assertEquals( + $expected_set, + $result_set + ); + // error/warning check + $this->assertEquals( + $error_set, + $last_error, + ); + } + + // get current set from db + $result_get_var = $db->dbGetSchema(true); + $this->assertEquals( + $expected_get_var, + $result_get_var + ); + $result_get_db = $db->dbGetSchema(); + $this->assertEquals( + $expected_get_db, + $result_get_db + ); + + // close connection + $db->dbClose(); + } + + // - check error and warning handling + // dbGetCombinedErrorHistory, dbGetLastError, dbGetLastWarning + + /** + * Undocumented function + * + * @return array + */ + public function errorHandlingProvider(): array + { + // 0: some error call + // 1: type (error/warning) + // 2: error/warning code + // 3: return array matcher (excluding time) + return [ + 'trigger error' => [ + 0, + 'error', + '51', + [ + 'timestamp' => "/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{1,}$/", + 'level' => 'error', + 'id' => '51', + 'error' => 'Max query call needs to be set to at least 1', + // run:: can be +1 if called in set and not direct + 'source' => "/^main::run::run::run::run::run::run::(run::)?runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall$/", + 'pg_error' => '', + 'msg' => '', + ] + ], + 'trigger warning' => [ + -1, + 'warning', + '50', + [] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::dbGetLastError + * @covers ::dbGetLastWarning + * @covers ::dbGetCombinedErrorHistory + * @dataProvider errorHandlingProvider + * @testdox error $call_value for type $type with $error_id [$_dataName] + * + * @param integer $call_value + * @param string $type + * @param string $error_id + * @param array $error_history + * @return void + */ + public function testDbErrorHandling( + int $call_value, + string $type, + string $error_id, + array $expected_history + ): void { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + // trigger the error call + $db->dbSetMaxQueryCall($call_value); + if ($type == 'error') { + $last_error = $db->dbGetLastError(); + } else { + $last_error = $db->dbGetLastWarning(); + } + $this->assertEquals( + $error_id, + $last_error + ); + + $error_history = $db->dbGetCombinedErrorHistory(); + // pop first error off + $first_error_element = array_shift($error_history); + // get first row element + // comarep all, except timestamp that is a regex + foreach ($expected_history as $key => $value) { + // check if starts with / because this is regex (timestamp) + // if (substr($expected_2, 0, 1) == '/) { + if (strpos($value, '/') === 0) { + // this is regex + $this->assertMatchesRegularExpression( + $value, + $first_error_element[0][$key] + ); + } else { + // assert equal + $this->assertEquals( + $value, + $first_error_element[0][$key], + ); + } + } + + // close connection + $db->dbClose(); + } + + // - 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 + // 3: error id + 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', + '81', + ], + 'set to empty' => [ + 'valid', + '', + false, + 'UTF8', + '80', + ] + ]; + } + + /** + * 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 { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config[$connection], + self::$log + ); + $this->assertEquals( + $expected_set_flag, + // avoid bubbling up error + @$db->dbSetEncoding($set_encoding) + ); + // show query + $this->assertEquals( + $expected_get_encoding, + $db->dbGetEncoding() + ); + // pg_version + $this->assertEquals( + $expected_get_encoding, + $db->dbVersionInfo('client_encoding') + ); + $db->dbClose(); + } + + // - encoding conversion on read and test encoding conversion on db connection + // dbSetToEncoding, dbGetToEncoding + // [and test encoding transfer with both types] + // dbSetEncoding, dbGetEncoding + + /** + * Undocumented function + * + * @return array + */ + public function encodingConversionProvider(): array + { + // 0: connection + // 1: target encoding (or alias) + // 2: optional name for php for proper alias matching + // 3: text to check + return [ + 'convert from UTF8 to SJIS' => [ + 'valid', + 'SJIS', + null, + '日本語カタカナひらがな' + ], + // SHIFT_JIS_2004/SJIS-win + // EUC_JP/EUC-JP + // + ]; + } + + /** + * tests actually text conversion and not only just setting + * NOTE: database is always stored as UTF8 in our case so all + * tests check conversion FROM utf8 to a target. + * Also because only SJIS is of interest, only this one is tested + * https://www.postgresql.org/docs/current/multibyte.html#MULTIBYTE-CHARSET-SUPPORTED + * SHIFT_JIS_2004 + * SJIS (Mskanji, ShiftJIS, WIN932, Windows932) + * EUC_JP + * EUC_JIS_2004 + * + * @covers ::dbSetToEncoding + * @covers ::dbGetToEncoding + * @covers ::dbSetEncoding + * @covers ::dbGetEncoding + * @dataProvider encodingConversionProvider + * @testdox Check encoding on $connection with $encoding [$_dataName] + * + * @param string $connection + * @param string $encoding + * @param string|null $encoding_php + * @param string $text + * @return void + */ + public function testEncodingConversion( + string $connection, + string $encoding, + ?string $encoding_php, + string $text + ): void { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config[$connection], + self::$log + ); + + // convert in php unless encoding is the smae + if (strtolower($encoding) != 'utf8') { + $encoded = mb_convert_encoding($text, $encoding_php ?? $encoding, 'UTF-8'); + } else { + $encoded = $text; + } + + // insert data + $insert_query = "INSERT INTO table_with_primary_key (row_varchar, uid) VALUES " + . "(" . $db->dbEscapeLiteral($text) . ", 'A')"; + $db->dbExec($insert_query); + // for check read + $read_query = "SELECT row_varchar, uid FROM table_with_primary_key WHERE uid = 'A'"; + + // TEST 1 in class + // test to encoding (conversion with mb_convert_encoding) + $db->dbSetToEncoding($encoding); + $this->assertEquals( + $encoding, + $db->dbGetToEncoding() + ); + // read query, check that row_varchar matches + $row = $db->dbReturnRow($read_query, true); + $this->assertEquals( + $encoded, + $row['row_varchar'] + ); + // reset to encoding to empty + $db->dbSetToEncoding(''); + // and check + $this->assertEquals( + '', + $db->dbGetToEncoding() + ); + + // TEST 2 DB side + // same test with setting database encoding + // if connection encoding differts + if (strtolower($db->dbGetSetting('encoding')) != strtolower($encoding)) { + $db->dbSetEncoding($encoding); + } + // read from DB and check encoding + $row = $db->dbReturnRow($read_query, true); + $this->assertEquals( + $encoded, + $row['row_varchar'] + ); + + // reset all data + $db->dbExec("TRUNCATE table_with_primary_key"); + $db->dbExec("TRUNCATE table_without_primary_key"); + // close connection + $db->dbClose(); + } + + // - get primary key + // dbGetReturning, dbGetInsertPK, dbGetInsertPKName + + /** + * Undocumented function + * + * @return array + */ + public function primaryKeyProvider(): array + { + // 0: insert query add (returning, etc) + // 1: pk_name, null for default + // 2: table name + // 3: primary key or empty if none + return [ + 'normal all auto' => [ + '', + null, + 'table_with_primary_key', + 'table_with_primary_key_id', + ], + 'table without primary key' => [ + '', + null, + 'table_without_primary_key', + '' + ], + // valid primary key name + 'normal, with pk_name' => [ + '', + 'table_with_primary_key_id', + 'table_with_primary_key', + 'table_with_primary_key_id', + ], + // returning name no pk name + 'normal, with returning' => [ + 'RETURNING table_with_primary_key_id', + null, + 'table_with_primary_key', + 'table_with_primary_key_id', + ], + // both pk name and returning + 'normal, with returning' => [ + 'RETURNING table_with_primary_key_id', + 'table_with_primary_key_id', + 'table_with_primary_key', + 'table_with_primary_key_id', + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::dbGetInsertPK + * @covers ::dbGetInsertPKName + * @dataProvider primaryKeyProvider + * @testdox Check returning pk $insert with $pk_name [$_dataName] + * + * @param string $insert + * @param string|null $pk_name + * @return void + */ + public function testGetPrimaryKey( + string $insert, + ?string $pk_name, + string $table, + string $primary_key + ): void { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + // basic query + $insert_query = "INSERT INTO " . $db->dbEscapeIdentifier($table) + . " (uid) " + . "VALUES ('A') " . $insert; + $pk_name === null ? + $db->dbExec($insert_query) : + $db->dbExec($insert_query, $pk_name); + // $db_get_returning = $db->dbGetReturning(); + $db_get_insert_pk_name = $db->dbGetInsertPKName(); + $db_get_insert_pk = $db->dbGetInsertPK(); + + $read_query = "SELECT " + . (!empty($primary_key) ? + $db->dbEscapeIdentifier($primary_key) . ", " : "" + ) + . "uid " + . "FROM " . $db->dbEscapeIdentifier($table) + . " WHERE uid = 'A'"; + $row = $db->dbReturnRow($read_query, true); + $this->assertEquals( + $row[$primary_key] ?? null, + $db_get_insert_pk + ); + $this->assertEquals( + $primary_key, + $db_get_insert_pk_name + ); + $this->assertTrue(true); + + + // reset all data + $db->dbExec("TRUNCATE table_with_primary_key"); + $db->dbExec("TRUNCATE table_without_primary_key"); + // close connection + $db->dbClose(); + } + + // - complex returning data checks + // dbGetReturningExt, dbGetReturningArray + + /** + * Undocumented function + * + * @return array + */ + public function returningPrvoider(): array + { + // NOTE that query can have multiple inserts + // NOTE if there are different INSERTS before the primary keys + // will not match anymore. Must be updated by hand + $table_with_primary_key_id = 55; + // 0: query + returning + // 1: pk name for db exec + // 2: key name/value or null (dbGetReturningExt) + // 3: pos or null (dbGetReturningExt) + // 4: matching return value (dbGetReturningExt) + // 5: full returning array value (dbGetReturningArray) + return [ + 'single insert (PK)' => [ + "INSERT INTO table_with_primary_key " + . "(row_varchar, row_varchar_literal, row_int, row_date) " + . "VALUES " + . "('Text', 'Other', 123, '2022-03-01') " + . "RETURNING row_varchar, row_varchar_literal, row_int, row_date", + null, + null, + null, + [ + 'row_varchar' => 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + // 'table_with_primary_key_id' => "/^\d+$/", + 'table_with_primary_key_id' => $table_with_primary_key_id + 1, + ], + [ + 0 => [ + 'row_varchar' => 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + // 'table_with_primary_key_id' => "/^\d+$/", + 'table_with_primary_key_id' => $table_with_primary_key_id + 1, + ] + ] + ], + // same but as EOM + 'single insert (PK), EOM string' => [ + << 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + // 'table_with_primary_key_id' => "/^\d+$/", + 'table_with_primary_key_id' => $table_with_primary_key_id + 2, + ], + [ + 0 => [ + 'row_varchar' => 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + // 'table_with_primary_key_id' => "/^\d+$/", + 'table_with_primary_key_id' => $table_with_primary_key_id + 2, + ] + ] + ], + // double insert (PK) + 'dobule insert (PK)' => [ + "INSERT INTO table_with_primary_key " + . "(row_varchar, row_varchar_literal, row_int, row_date) " + . "VALUES " + . "('Text', 'Other', 123, '2022-03-01'), " + . "('Foxtrott', 'Tango', 789, '1982-10-15') " + . "RETURNING row_varchar, row_varchar_literal, row_int, row_date", + null, + null, + null, + [ + 0 => [ + 'row_varchar' => 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + 'table_with_primary_key_id' => $table_with_primary_key_id + 3, + ], + 1 => [ + 'row_varchar' => 'Foxtrott', + 'row_varchar_literal' => 'Tango', + 'row_int' => 789, + 'row_date' => '1982-10-15', + 'table_with_primary_key_id' => $table_with_primary_key_id + 4, + ], + ], + [ + 0 => [ + 'row_varchar' => 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + 'table_with_primary_key_id' => $table_with_primary_key_id + 3, + ], + 1 => [ + 'row_varchar' => 'Foxtrott', + 'row_varchar_literal' => 'Tango', + 'row_int' => 789, + 'row_date' => '1982-10-15', + 'table_with_primary_key_id' => $table_with_primary_key_id + 4, + ], + ] + ], + // insert into table with no primary key + 'single insert (No PK)' => [ + "INSERT INTO table_without_primary_key " + . "(row_varchar, row_varchar_literal, row_int, row_date) " + . "VALUES " + . "('Text', 'Other', 123, '2022-03-01') " + . "RETURNING row_varchar, row_varchar_literal, row_int, row_date", + null, + null, + null, + [ + 'row_varchar' => 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + ], + [ + 0 => [ + 'row_varchar' => 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + ] + ] + ], + // same as above but as EOM string + 'single insert (No PK), EOM string' => [ + << 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + ], + [ + 0 => [ + 'row_varchar' => 'Text', + 'row_varchar_literal' => 'Other', + 'row_int' => 123, + 'row_date' => '2022-03-01', + ] + ] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::dbGetReturningExt + * @covers ::dbGetReturningArray + * @dataProvider returningPrvoider + * @testdox Check returning cursor using $pk_name with $key and $pos [$_dataName] + * + * @param string $query + * @param string|null $pk_name + * @param string|null $key + * @param integer|null $pos + * @param array|string|int|null $expected_ret_ext + * @param array $expected_ret_arr + * @return void + */ + public function testDbReturning( + string $query, + ?string $pk_name, + ?string $key, + ?int $pos, + $expected_ret_ext, + array $expected_ret_arr + ): void { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + // insert data + $pk_name === null ? + $db->dbExec($query) : + $db->dbExec($query, $pk_name); + + // get the last value for PK and match that somehow + + $returning_ext = $db->dbGetReturningExt($key, $pos); + $returning_arr = $db->dbGetReturningArray(); + + $this->assertEquals( + $expected_ret_ext, + $returning_ext, + 'Returning extended failed' + ); + $this->assertEquals( + $expected_ret_arr, + $returning_arr, + 'Returning Array failed' + ); + + // print "EXT: " . print_r($returning_ext, true) . "\n"; + // print "ARR: " . print_r($returning_arr, true) . "\n"; + + // reset all data + $db->dbExec("TRUNCATE table_with_primary_key"); + $db->dbExec("TRUNCATE table_without_primary_key"); + // close connection + $db->dbClose(); + } + + // - internal read data (post exec) + // dbGetNumRows, dbGetNumFields, dbGetFieldNames, + // dbGetQuery, dbGetQueryHash, dbGetDbh + + /** + * Undocumented function + * + * @return array + */ + public function getMethodsProvider(): array + { + // 0: run query + // 1: optional insert query (if select or needed) + // 2: optional compare query, if not set 0 is used + // 3: num rows + // 4: column count + // 5: column names + return [ + 'select data' => [ + "SELECT row_varchar, row_varchar_literal, row_int, row_date " + . "FROM table_with_primary_key", + "INSERT INTO table_with_primary_key " + . "(row_varchar, row_varchar_literal, row_int, row_date) " + . "VALUES " + . "('Text', 'Other', 123, '2022-03-01'), " + . "('Foxtrott', 'Tango', 789, '1982-10-15') ", + null, + // + 2, + 4, + ['row_varchar', 'row_varchar_literal', 'row_int', 'row_date'], + ], + // insert + 'insert data' => [ + "INSERT INTO table_with_primary_key " + . "(row_varchar, row_varchar_literal, row_int, row_numeric, row_date) " + . "VALUES " + . "('Text', 'Other', 123, 1.0, '2022-03-01'), " + . "('Foxtrott', 'Tango', 789, 2.2, '1982-10-15'), " + . "('Schlamm', 'Beizinger', 100, 3.14, '1990-1-1') ", + null, + "INSERT INTO table_with_primary_key " + . "(row_varchar, row_varchar_literal, row_int, row_numeric, row_date) " + . "VALUES " + . "('Text', 'Other', 123, 1.0, '2022-03-01'), " + . "('Foxtrott', 'Tango', 789, 2.2, '1982-10-15'), " + . "('Schlamm', 'Beizinger', 100, 3.14, '1990-1-1') " + . " RETURNING table_with_primary_key_id", + // + 3, + 0, + [], + ], + // update + 'update data' => [ + "UPDATE table_with_primary_key SET " + . "row_varchar = 'CHANGE A', row_int = 999 " + . "WHERE uid = 'A'", + "INSERT INTO table_with_primary_key " + . "(uid, row_varchar, row_varchar_literal, row_int, row_date) " + . "VALUES " + . "('A', 'Text', 'Other', 123, '2022-03-01'), " + . "('B', 'Foxtrott', 'Tango', 789, '1982-10-15') ", + null, + // + 1, + 0, + [], + ] + // something other (schema change?) + ]; + } + + /** + * Undocumented function + * + * @covers ::dbGetNumRows + * @covers ::dbGetNumFields + * @covers ::dbGetFieldNames + * @covers ::dbGetQuery + * @covers ::dbGetQueryHash + * @covers ::dbGetDbh + * @dataProvider getMethodsProvider + * @testdox Check check rows: $expected_rows and cols: $expected_cols [$_dataName] + * + * @param string $query + * @param string|null $insert_query + * @param string|null $compare_query + * @param integer $expected_rows + * @param integer $expected_cols + * @param array $expected_col_names + * @return void + */ + public function testDbGetMethods( + string $query, + ?string $insert_query, + ?string $compare_query, + int $expected_rows, + int $expected_cols, + array $expected_col_names + ): void { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + if (!empty($insert_query)) { + $db->dbExec($insert_query); + } + + $db->dbExec($query); + + $this->assertEquals( + $compare_query ?? $query, + $db->dbGetQuery() + ); + $this->assertEquals( + // perhaps move that somewhere else? + \CoreLibs\Create\Hash::__hashLong($query), + $db->dbGetQueryHash($query) + ); + $this->assertEquals( + $expected_rows, + $db->dbGetNumRows() + ); + $this->assertEquals( + $expected_cols, + $db->dbGetNumFields() + ); + $this->assertEquals( + $expected_col_names, + $db->dbGetFieldNames() + ); + $dbh = $db->dbGetDbh(); + if (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { + $this->assertIsObject( + $dbh + ); + // also check that this is correct instance type + $this->assertInstanceOf( + 'PgSql\Connection', + $dbh + ); + } else { + $this->assertIsResource( + $dbh + ); + } + + // if this is a select query, db dbReturn, dbReturnRow, dbReturnArray too + if (preg_match("/^(select|show|with) /i", $query)) { + // dbReturn + $db->dbReturn($query); + $this->assertEquals( + $expected_rows, + $db->dbGetNumRows() + ); + $this->assertEquals( + $expected_cols, + $db->dbGetNumFields() + ); + $this->assertEquals( + $expected_col_names, + $db->dbGetFieldNames() + ); + // dbReturnRow + // will return ALL rows there, but returns only the first + $db->dbReturnRow($query); + $this->assertEquals( + $expected_rows, + $db->dbGetNumRows() + ); + $this->assertEquals( + $expected_cols, + $db->dbGetNumFields() + ); + $this->assertEquals( + $expected_col_names, + $db->dbGetFieldNames() + ); + // dbReturnArray + $db->dbReturnArray($query); + $this->assertEquals( + $expected_rows, + $db->dbGetNumRows() + ); + $this->assertEquals( + $expected_cols, + $db->dbGetNumFields() + ); + $this->assertEquals( + $expected_col_names, + $db->dbGetFieldNames() + ); + } + + // reset all data + $db->dbExec("TRUNCATE table_with_primary_key"); + $db->dbExec("TRUNCATE table_without_primary_key"); + // close connection + $db->dbClose(); + } + + // TODO implement below checks + // - complex write sets + // dbWriteData, dbWriteDataExt + // - data debug + // dbDumpData + + // ASYNC at the end because it has 1s timeout + // - asynchronous executions + // dbExecAsync, dbCheckAsync + + /** + * Undocumented function + * + * @return array + */ + public function asyncProvider(): array + { + // 0: query + // 1: primary key + // 2: exepected exec return + // 3: warning + // 4: error + // 5: exepcted check return 1st + // 6: final result + // 7: warning + // 8: error + return [ + 'run simple async query' => [ + "SELECT pg_sleep(1)", + null, + // exec result + true, + '', + '', + // check first + true, + // check final + 'result', + '', + '' + ], + // send query failed (E40) + // result failed (E43) + // no query running (E42) + 'no async query running' => [ + '', + null, + // + false, + '', + '11', + // + false, + // + false, + '', + '42' + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::dbExecAsync + * @covers ::dbCheckAsync + * @dataProvider asyncProvider + * @testdox async query $query with $expected_exec (warning $warning_exec/error $error_exec) and $expected_check/$expected_final (warning $warning_final/error $error_final) [$_dataName] + * + * @param string $query + * @param string|null $pk_name + * @param boolean $expected_exec + * @param string $warning_exec + * @param string $error_exec + * @param bool $expected_check + * @param bool|object|resource $expected_final + * @param string $warning_final + * @param string $error_final + * @return void + */ + public function testDbExecAsync( + string $query, + ?string $pk_name, + bool $expected_exec, + string $warning_exec, + string $error_exec, + bool $expected_check, + $expected_final, + string $warning_final, + string $error_final + ): void { + // self::$log->setLogLevelAll('debug', true); + // self::$log->setLogLevelAll('print', true); + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + + // exec the query + $result_exec = $pk_name === null ? + $db->dbExecAsync($query) : + $db->dbExecAsync($query, $pk_name); + $this->assertEquals( + $expected_exec, + $result_exec + ); + // error/warning check + $this->subAssertErrorTest($db, $warning_exec, $error_exec); + + $run = 1; + // first loop check + while (($result_check = $db->dbCheckAsync()) === true) { + if ($run == 1) { + $this->assertEquals( + $expected_check, + $result_check + ); + } + $run++; + } + // check after final + if ($expected_final == 'result') { + // post end check + if (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { + $this->assertIsObject( + $result_check + ); + // also check that this is correct instance type + $this->assertInstanceOf( + 'PgSql\Result', + $result_check + ); + } else { + $this->assertIsResource( + $result_check + ); + } + } else { + // else compar check + $this->assertEquals( + $expected_final, + $result_check + ); + } + // error/warning check + $this->subAssertErrorTest($db, $warning_final, $error_final); + + // reset all data + $db->dbExec("TRUNCATE table_with_primary_key"); + $db->dbExec("TRUNCATE table_without_primary_key"); + // close connection + $db->dbClose(); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsDebugFileWriterTest.php b/test/phpunit/CoreLibsDebugFileWriterTest.php new file mode 100644 index 0000000..1248e2e --- /dev/null +++ b/test/phpunit/CoreLibsDebugFileWriterTest.php @@ -0,0 +1,176 @@ + [ + 0 => '/tmp/', + 1 => true, + ], + 'invalid log folder name' => [ + 0 => 'some name', + 1 => false, + ], + 'not writeable log folder name' => [ + 0 => '/opt', + 1 => false, + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function fsetFilenameProvider(): array + { + return [ + 'valid log file name' => [ + 0 => 'some_valid_name.log', + 1 => true, + ], + 'file name contains path' => [ + 0 => 'log/debug.log', + 1 => false, + ], + 'invalid log file name' => [ + 0 => 'invalid name', + 1 => false, + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function fdebugProvider(): array + { + return [ + 'debug with default enter' => [ + 0 => 'test string', + 1 => null, + 2 => true, + 3 => 'test string' . "\n" + ], + 'debug with no enter' => [ + 0 => 'test string', + 1 => false, + 2 => true, + 3 => 'test string' + ] + ]; + } + + /** + * Undocumented function + * + * @dataProvider fsetFolderProvider + * @testdox fsetFolder $input will match $expected [$_dataName] + * + * @param string $input + * @param boolean $expected + * @return void + */ + public function testFsetFolder(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Debug\FileWriter::fsetFolder($input) + ); + } + + /** + * Undocumented function + * + * @dataProvider fsetFilenameProvider + * @testdox fsetFilename $input will match $expected [$_dataName] + * + * @param string $input + * @param boolean $expected + * @return void + */ + public function testFsetFilename(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Debug\FileWriter::fsetFilename($input) + ); + } + + /** + * Undocumented function + * + * @dataProvider fdebugProvider + * @testdox fdebug write $input with enter $enter and will be $expected and written $expected_log [$_dataName] + * + * @param string $input + * @param boolean|null $enter + * @param boolean $expected + * @param string $expected_log + * @return void + */ + public function testFdebug(string $input, ?bool $enter, bool $expected, string $expected_log): void + { + // set debug log folder + $file = 'FileWriterTest.log'; + $folder = '/tmp'; + $debug_file = $folder . DIRECTORY_SEPARATOR . $file; + $valid_folder = \CoreLibs\Debug\FileWriter::fsetFolder($folder); + $this->assertTrue( + $valid_folder + ); + $valid_file = \CoreLibs\Debug\FileWriter::fsetFilename($file); + $this->assertTrue( + $valid_file + ); + // write the log line + if ($enter === null) { + $this->assertEquals( + $expected, + \CoreLibs\Debug\FileWriter::fdebug($input) + ); + } else { + $this->assertEquals( + $expected, + \CoreLibs\Debug\FileWriter::fdebug($input, $enter) + ); + } + if (is_file($debug_file)) { + // open file, load data, compre to expected_log + $log_data = file_get_contents($debug_file); + if ($log_data === false) { + $this->fail('fdebug file not readable or not data: ' . $debug_file); + } + $this->assertStringEndsWith( + $expected_log, + $log_data + ); + unlink($debug_file); + } else { + $this->fail('fdebug file not found: ' . $debug_file); + } + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsDebugLoggingTest.php b/test/phpunit/CoreLibsDebugLoggingTest.php new file mode 100644 index 0000000..21dc3df --- /dev/null +++ b/test/phpunit/CoreLibsDebugLoggingTest.php @@ -0,0 +1,990 @@ + [ + [ + 'log_folder' => '/tmp' + ], + [ + 'log_folder' => '/tmp/', + 'debug_all' => false, + 'print_all' => false, + ], + [] + ], + 'nothing set' => [ + null, + [ + 'log_folder' => getcwd() . DIRECTORY_SEPARATOR, + 'debug_all' => false, + 'print_all' => false, + ], + [] + ], + 'no options set, constant set' => [ + null, + [ + 'log_folder' => str_replace('/configs', '', __DIR__) + . DIRECTORY_SEPARATOR . 'log/', + 'debug_all' => false, + 'print_all' => false, + ], + [ + 'constant' => [ + 'BASE' => str_replace('/configs', '', __DIR__) + . DIRECTORY_SEPARATOR, + 'LOG' => 'log/' + ] + ] + ], + 'standard test set' => [ + [ + 'log_folder' => '/tmp', + 'debug_all' => true, + 'print_all' => true, + ], + [ + 'log_folder' => '/tmp/', + 'debug_all' => true, + 'print_all' => true, + ], + [] + ] + ]; + } + + /** + * init logging class + * + * @dataProvider optionsProvider + * @testdox init test [$_dataName] + * + * @param array|null $options + * @param array $expected + * @param array $override + * @return void + */ + public function testClassInit(?array $options, array $expected, array $override): void + { + if (!empty($override['constant'])) { + foreach ($override['constant'] as $var => $value) { + define($var, $value); + } + } + if ($options === null) { + $log = new \CoreLibs\Debug\Logging(); + } else { + $log = new \CoreLibs\Debug\Logging($options); + } + // check that settings match + $this->assertEquals( + $expected['log_folder'], + $log->getSetting('log_folder') + ); + $this->assertEquals( + $expected['debug_all'], + $log->getSetting('debug_output_all') + ); + $this->assertEquals( + $expected['print_all'], + $log->getSetting('print_output_all') + ); + // print "LOG: " . $log->getSetting('log_folder') . "\n"; + // print "DEBUG: " . $log->getSetting('debug_output_all') . "\n"; + // print "PRINT: " . $log->getSetting('print_output_all') . "\n"; + } + + /** + * adds log ID settings based on basic options + * + * @return array + */ + public function logIdOptionsProvider(): array + { + // 0: options + // 1: expected + // 2: override + return [ + 'no log id set' => [ + null, + [ + 'log_file_id' => '' + ], + [] + ], + // set log id manually afterwards + 'set log id manually' => [ + null, + [ + 'log_file_id' => '', + 'set_log_file_id' => 'abc123', + ], + [ + // set post launch + 'values' => [ + 'log_file_id' => 'abc123' + ] + ] + ], + // set log id from options + 'set log id via options' => [ + [ + 'file_id' => 'abc456', + ], + [ + 'log_file_id' => 'abc456' + ], + [] + ], + // set log id from GLOBALS [DEPRECATED] + 'set log id via globals' => [ + null, + [ + 'log_file_id' => 'def123' + ], + [ + 'globals' => [ + 'LOG_FILE_ID' => 'def123' + ] + ] + ], + // set log id from CONSTANT [DEPRECATED] + 'set log id via constant' => [ + null, + [ + 'log_file_id' => 'ghi123' + ], + [ + // reset global + 'globals' => [ + 'LOG_FILE_ID' => null + ], + 'constant' => [ + 'LOG_FILE_ID' => 'ghi123' + ] + ] + ], + // invalid, keep previous set + 'invalid log id' => [ + [ + 'file_id' => 'jkl456' + ], + [ + 'log_file_id' => 'jkl456', + 'set_log_file_id' => 'jkl456', + ], + [ + 'values' => [ + 'log_file_id' => './#' + ] + ] + ] + ]; + } + + /** + * test the setting and getting of LogId + * + * @covers ::setLogId + * @dataProvider logIdOptionsProvider + * @testdox log id set/get tests [$_dataName] + * + * @param array|null $options + * @param array $expected + * @param array $override + * @return void + */ + public function testLogId(?array $options, array $expected, array $override): void + { + // we need to set with file_id option, globals LOG_FILE_ID, constant LOG_FILE_ID + if (!empty($override['constant'])) { + foreach ($override['constant'] as $var => $value) { + define($var, $value); + } + } + if (!empty($override['globals'])) { + foreach ($override['globals'] as $var => $value) { + $GLOBALS[$var] = $value; + } + } + if ($options === null) { + $log = new \CoreLibs\Debug\Logging(); + } else { + $log = new \CoreLibs\Debug\Logging($options); + } + // check current + $this->assertEquals( + $log->getLogId(), + $expected['log_file_id'] + ); + // we need to override now too + if (!empty($override['values'])) { + // check if we have values, set them post and assert + $log->setLogId($override['values']['log_file_id']); + $this->assertEquals( + $log->getLogId(), + $expected['set_log_file_id'] + ); + } + } + + /** + * Undocumented function + * + * @return array + */ + public function logLevelAllProvider(): array + { + // 0: type + // 1: flag + // 2: expected set + // 3: expected get + return [ + 'debug all true' => [ + 'debug', + true, + true, + true, + ], + 'echo all true' => [ + 'echo', + true, + true, + true, + ], + 'print all true' => [ + 'print', + true, + true, + true, + ], + 'set invalid level' => [ + 'invalud', + true, + false, + false, + ], + ]; + } + + /** + * check set/get for log level all flag + * + * @dataProvider logLevelAllProvider + * @testdox set/get all log level $type with flag $flag [$_dataName] + * + * @param string $type + * @param bool $flag + * @param bool $expected_set + * @param bool $expected_get + * @return void + */ + public function testSetGetLogLevelAll( + string $type, + bool $flag, + bool $expected_set, + bool $expected_get + ): void { + // neutral start with default + $log = new \CoreLibs\Debug\Logging(); + // set and check + $this->assertEquals( + $log->setLogLevelAll($type, $flag), + $expected_set + ); + // get and check + $this->assertEquals( + $log->getLogLevelAll($type), + $expected_get + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function logLevelProvider(): array + { + // 0: type + // 1: flag + // 2: debug on (array) + // 3: expected set + // 4: level + // 5: expected get + return [ + 'set debug on for level A,B,C and check full set' => [ + 'debug', + 'on', + ['A', 'B', 'C'], + true, + null, + [ + 'A' => true, + 'B' => true, + 'C' => true, + ] + ], + 'set debug off for level A,B,C and check A' => [ + 'debug', + 'off', + ['A', 'B', 'C'], + true, + 'A', + true, + ], + // set one to false + 'set debug off for level A, B to false and check all' => [ + 'debug', + 'off', + ['A', 'B' => false], + true, + null, + [ + 'A' => true, + 'B' => false, + ], + ], + // set invalid type + 'set invalid level' => [ + 'invalid', + '', + [], + false, + null, + false + ], + // set invalid flag + 'set invalid on flag' => [ + 'print', + 'invalid', + [], + false, + null, + false + ], + // missing debug array set + 'missing debug level array' => [ + 'print', + 'off', + [], + false, + null, + [] + ], + // set but check no existing + 'set level but check no exisitng' => [ + 'print', + 'on', + ['A'], + true, + 'C', + false + ] + ]; + } + + /** + * checks setting for per log info level + * + * @covers ::setLogLevel + * @dataProvider logLevelProvider + * @testdox set/get log level $type to $flag check with $level [$_dataName] + * + * @param string $type + * @param string $flag + * @param array $debug_on + * @param bool $expected_set + * @param string|null $level + * @param bool|array $expected_get + * @return void + */ + public function testSetGetLogLevel( + string $type, + string $flag, + array $debug_on, + bool $expected_set, + ?string $level, + $expected_get + ): void { + // neutral start with default + $log = new \CoreLibs\Debug\Logging(); + // set + $this->assertEquals( + $log->setLogLevel($type, $flag, $debug_on), + $expected_set + ); + // get, if level is null compare to? + $this->assertEquals( + $log->getLogLevel($type, $flag, $level), + $expected_get + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function logPerProvider(): array + { + // 0: type + // 1: set + // 2: expected set + // 3: expected get + return [ + 'level set true' => [ + 'level', + true, + true, + true, + ], + 'class set true' => [ + 'class', + true, + true, + true, + ], + 'page set true' => [ + 'page', + true, + true, + true, + ], + 'run set true' => [ + 'run', + true, + true, + true, + ], + 'set invalid type' => [ + 'invalid', + true, + false, + false, + ] + ]; + } + + /** + * set and get per log + * for level/class/page/run flags + * + * @covers ::setLogPer + * @dataProvider logPerProvider + * @testdox set/get log per $type with $set [$_dataName] + * + * @param string $type + * @param boolean $set + * @param boolean $expected_set + * @param boolean $expected_get + * @return void + */ + public function testSetGetLogPer( + string $type, + bool $set, + bool $expected_set, + bool $expected_get + ): void { + // neutral start with default + $log = new \CoreLibs\Debug\Logging(); + // set and check + $this->assertEquals( + $log->setLogPer($type, $set), + $expected_set + ); + // get and check + $this->assertEquals( + $log->getLogPer($type), + $expected_get + ); + } + + /** + * set the print log file date part + * + * @covers ::setGetLogPrintFileDate + * @testWith [true, true, true] + * [false, false, false] + * @testdox set/get log file date to $input [$_dataName] + * + * @param boolean $input + * @param boolean $expected_set + * @param boolean $expected_get + * @return void + */ + public function testSetGetLogPrintFileDate(bool $input, bool $expected_set, bool $expected_get): void + { + // neutral start with default + $log = new \CoreLibs\Debug\Logging(); + // set and check + $this->assertEquals( + $log->setGetLogPrintFileDate($input), + $expected_set + ); + $this->assertEquals( + $log->setGetLogPrintFileDate(), + $expected_get + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function prArProvider(): array + { + return [ + 'simple array' => [ + [ + 'A' => 'foobar' + ], + "##HTMLPRE##Array\n(\n" + . " [A] => foobar\n" + . ")\n" + . "##/HTMLPRE##" + ], + 'empty array' => [ + [], + "##HTMLPRE##Array\n(\n" + . ")\n" + . "##/HTMLPRE##" + ], + 'nested array' => [ + [ + 'A' => [ + 'B' => 'bar' + ] + ], + "##HTMLPRE##Array\n(\n" + . " [A] => Array\n" + . " (\n" + . " [B] => bar\n" + . " )\n" + . "\n" + . ")\n" + . "##/HTMLPRE##" + ], + ]; + } + + /** + * convert array to string with ## pre replace space holders + * + * @covers ::prAr + * @dataProvider prArProvider + * @testdox check prAr array to string conversion [$_dataName] + * + * @param array $input + * @param string $expected + * @return void + */ + public function testPrAr(array $input, string $expected): void + { + $log = new \CoreLibs\Debug\Logging(); + $this->assertEquals( + $log->prAr($input), + $expected + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function prBlProvider(): array + { + // 0: input flag (bool) + // 1: is true + // 2: is flase + // 3: epxected + return [ + 'true bool default' => [ + true, + null, + null, + 'true' + ], + 'false bool default' => [ + false, + null, + null, + 'false' + ], + 'true bool override' => [ + true, + 'ok', + 'not ok', + 'ok' + ], + 'false bool override' => [ + false, + 'ok', + 'not ok', + 'not ok' + ], + ]; + } + + /** + * check bool to string converter + * + * @covers ::prBl + * @dataProvider prBlProvider + * @testdox check prBl $input ($true/$false) is expected $false [$_dataName] + * + * @param bool $input + * @param string|null $true + * @param string|null $false + * @param string $expected + * @return void + */ + public function testPrBl(bool $input, ?string $true, ?string $false, string $expected): void + { + $log = new \CoreLibs\Debug\Logging(); + $return = ''; + if ($true === null && $false === null) { + $return = $log->prBl($input); + } elseif ($true !== null || $false !== null) { + $return = $log->prBl($input, $true ?? '', $false ?? ''); + } + $this->assertEquals( + $expected, + $return + ); + } + + // from here are complex debug tests + + /** + * Undocumented function + * + * 0: array $options + * 1: array $debug_msg + * 2: boolean $expected_debug + * 3: string $expected_file + * 4: string $expected_string_start + * 5: string $expected_string_contains + * + * @return array + */ + public function debugProvider(): array + { + // error message to pass in + $error_msg['A'] = [ + 'level' => 'A', + 'string' => 'error msg', + 'strip' => false, + 'prefix' => '', + ]; + // file content to check + $file_msg['A'] = "{PHPUnit\TextUI\Command} - error msg\n"; + // string messages to check + $string_msg['A'] = [ + 's' => '
' + . '
{PHPUnit\TextUI\Command}' + . '
[A]
[', + 'c' => 'PHPUnit\TextUI\Command} - error msg
', + ]; + // array provider + return [ + 'A debug: on, print: on, echo: on' => [ + [ + 'debug_all' => true, + 'print_all' => true, + 'echo_all' => true, + ], + $error_msg['A'], + true, + $file_msg['A'], + $string_msg['A']['s'], + $string_msg['A']['c'], + ], + 'B debug: on, print: off, echo: on' => [ + [ + 'debug_all' => true, + 'print_all' => false, + 'echo_all' => true, + ], + $error_msg['A'], + true, + '', + $string_msg['A']['s'], + $string_msg['A']['c'], + ], + 'C debug: on, print: on, echo: off' => [ + [ + 'debug_all' => true, + 'print_all' => true, + 'echo_all' => false, + ], + $error_msg['A'], + true, + $file_msg['A'], + '', + '', + ], + 'D debug: on, print: off, echo: off' => [ + [ + 'debug_all' => true, + 'print_all' => false, + 'echo_all' => false, + ], + $error_msg['A'], + false, + '', + '', + '' + ], + 'E debug: off, print: off, echo: off' => [ + [ + 'debug_all' => false, + 'print_all' => false, + 'echo_all' => false, + ], + $error_msg['A'], + false, + '', + '', + '' + ] + // TODO more tests with different error messages + ]; + } + + /** + * Test debug flow + * + * @covers ::debug + * @dataProvider debugProvider + * @testdox check debug flow: $expected_debug [$_dataName] + * + * @param array $options + * @param array $debug_msg + * @param boolean $expected_debug + * @param string $expected_file + * @param string $expected_string_start + * @param string $expected_string_contains + * @return void + */ + public function testDebug( + array $options, + array $debug_msg, + bool $expected_debug, + string $expected_file, + string $expected_string_start, + string $expected_string_contains + ): void { + // must run with below matrix + // level | debug | print | echo | debug() | printErrorMsg() | file + // A 1/1/1 | on | on | on | true | 'string' | on + // B 1/0/1 | on | off | on | true | 'string' | off + // C 1/1/0 | on | on | off | true | '' | on + // D 1/0/0 | on | off | off | false | '' | off + // E 0/1/1 | off | on | on | false | '' | off + // F 0/0/1 | off | off | on | false | '' | off + // G 0/1/0 | off | on | off | false | '' | off + // H 0/0/0 | off | off | off | false | '' | off + + + // * debug off + // return false on debug(), + // return false on writeErrorMsg() + // empty string on printErrorMsg + // * print off + // return true on debug(), + // return false on writeErrorMsg() + // empty string on printErrorMsg + // * echo off + // return true on debug(), + // empty string on printErrorMsg + // fillxed error_msg array + + // overwrite any previous set from test + $options['file_id'] = 'TestDebug'; + // set log folder to temp + $options['log_folder'] = '/tmp/'; + // remove any files named /tmp/error_log_TestDebug*.log + array_map('unlink', glob($options['log_folder'] . 'error_msg_' . $options['file_id'] . '*.log')); + // init logger + $log = new \CoreLibs\Debug\Logging($options); + // * debug (A/B) + // NULL check for strip/prefix + $this->assertEquals( + $log->debug( + $debug_msg['level'], + $debug_msg['string'], + $debug_msg['strip'], + $debug_msg['prefix'], + ), + $expected_debug + ); + // * if print check data in log file + $log_file = $log->getLogFileName(); + if (!empty($options['debug_all']) && !empty($options['print_all'])) { + // file name matching + $this->assertStringStartsWith( + $options['log_folder'] . 'error_msg_' . $options['file_id'], + $log_file, + ); + // cotents check + if (!is_file($log_file)) { + $this->fail('error msg file not found: ' . $log_file); + } else { + $log_data = file_get_contents($log_file); + if ($log_data === null) { + $this->fail('error msg file not readable or not data: ' . $log_file); + } + // file content matching + $this->assertStringEndsWith( + $expected_file, + $log_data, + ); + } + } else { + // there should be no file there + $this->assertEquals( + $log_file, + '' + ); + } + // ** ECHO ON + $log_string = $log->printErrorMsg(); + // * print + if (!empty($options['debug_all']) && !empty($options['echo_all'])) { + // print $log->printErrorMsg() . "\n"; + // echo string must start with + $this->assertStringStartsWith( + $expected_string_start, + $log_string + ); + // echo string must containt + $this->assertStringContainsString( + $expected_string_contains, + $log_string + ); + // TODO: as printing directly is not really done anymore tests below are todo + // * get error msg (getErrorMsg) + // * merge error msg (mergeErrors) + // * print merged (printErrorMsg) + // * reset A (resetErrorMsg) + // * reset ALL (resetErrorMsg) + } else { + $this->assertEquals( + $log_string, + '' + ); + } + } + + // TODO: setLogUniqueId/getLogUniqueId + + /** + * Undocumented function + * + * @return array + */ + public function logUniqueIdProvider(): array + { + return [ + 'option set' => [ + 'option' => true, + 'override' => false, + ], + 'direct set' => [ + 'option' => false, + 'override' => false, + ], + 'override set' => [ + 'option' => false, + 'override' => true, + ], + 'option and override set' => [ + 'option' => false, + 'override' => true, + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::setLogUniqueId + * @covers ::getLogUniqueId + * @dataProvider logUniqueIdProvider + * @testdox per run log id set test: option: $option, override: $override [$_dataName] + * + * @param bool $option + * @param bool $override + * @return void + */ + public function testLogUniqueId(bool $option, bool $override): void + { + if ($option === true) { + $log = new \CoreLibs\Debug\Logging(['per_run' => $option]); + } else { + $log = new \CoreLibs\Debug\Logging(); + $log->setLogUniqueId(); + } + $per_run_id = $log->getLogUniqueId(); + $this->assertMatchesRegularExpression( + "/^\d{4}-\d{2}-\d{2}_\d{6}_U_[a-z0-9]{8}$/", + $per_run_id, + 'assert per log run id 1st' + ); + if ($override === true) { + $log->setLogUniqueId(true); + $per_run_id_2nd = $log->getLogUniqueId(); + $this->assertMatchesRegularExpression( + "/^\d{4}-\d{2}-\d{2}_\d{6}_U_[a-z0-9]{8}$/", + $per_run_id_2nd, + 'assert per log run id 2nd' + ); + $this->assertNotEquals( + $per_run_id, + $per_run_id_2nd, + '1st and 2nd don\'t match' + ); + } + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsDebugMemoryUsageTest.php b/test/phpunit/CoreLibsDebugMemoryUsageTest.php new file mode 100644 index 0000000..7b8e020 --- /dev/null +++ b/test/phpunit/CoreLibsDebugMemoryUsageTest.php @@ -0,0 +1,119 @@ + '/^[\w\s_-]+$/', + 'peak' => '/^\d+$/', + 'usage' => '/^\d+$/', + 'start' => '/^\d+$/', + 'last' => '/^\d+$/', + 'set' => '/^\d+$/', + ]; + // 0: prefix + // 1: raw flag + // 2: set flags array + // 3: array output expected (as regex) + // 4: string output expected (as regex) + return [ + 'test normal' => [ + 'test', + null, + [], + $regex_array, + $regex_raw_off, + ] + ]; + } + + /** + * Undocumented function + * + * @cover ::resetMemory + * @cover ::debugMemoryFlag + * @cover ::setStartMemory + * @cover ::setMemory + * @cover ::memoryUsage + * @cover ::printMemoryUsage + * @dataProvider memoryUsageProvider + * @testdox memoryUsage with $prefix, raw memory $raw [$_dataName] + * + * @param string $prefix + * @param bool|null $raw + * @param array $set_flags + * @param array $expected_array + * @param string $expected_string + * @return void + */ + public function testMemoryUsage( + string $prefix, + ?bool $raw, + array $settings, + array $expected_array, + string $expected_string + ): void { + // always reeset to null + MemoryUsage::resetMemory(); + MemoryUsage::debugMemoryFlag(true); + MemoryUsage::setStartMemory(); + MemoryUsage::setMemory(); + // run collector + $memory = MemoryUsage::memoryUsage($prefix); + if ($raw === null) { + $string = MemoryUsage::printMemoryUsage($memory); + } else { + $string = MemoryUsage::printMemoryUsage($memory, $raw); + } + + // expected_array for each + foreach ($expected_array as $name => $regex) { + $this->assertMatchesRegularExpression( + $regex, + (string)$memory[$name], + 'assert memory usage array ' . $name + ); + } + + // regex match string + $this->assertMatchesRegularExpression( + $expected_string, + $string, + 'assert memory usage string as regex' + ); + + // TODO additional tests with use more memory and check diff matching + // TODO reset memory usage test + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsDebugRunningTimeTest.php b/test/phpunit/CoreLibsDebugRunningTimeTest.php new file mode 100644 index 0000000..2037563 --- /dev/null +++ b/test/phpunit/CoreLibsDebugRunningTimeTest.php @@ -0,0 +1,183 @@ + [ + 0 => null, + 1 => '/^\d{4}\.\d{1,}$/' + ], + 'nanoseconds' => [ + 0 => 'ns', + 1 => '/^\d{10}$/' + ], + 'microseconds' => [ + 0 => 'ys', + 1 => '/^\d{7}\.\d{1,}$/' + ], + 'milliseconds' => [ + 0 => 'ms', + 1 => '/^\d{4}\.\d{1,}$/' + ], + 'seconds' => [ + 0 => 's', + 1 => '/^\d{1}\.\d{4,}$/' + ], + 'invalid fallback to ms' => [ + 0 => 'invalid', + 1 => '/^\d{4}\.\d{1,}$/' + ] + ]; + } + + public function runningTimeProvider(): array + { + return [ + 'run time test' => [ + 0 => '/^\d{1,}\.\d{1,}$/', + 1 => '/^Start: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} 0\.\d{8}, $/', + 2 => '/^Start: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} 0\.\d{8}, ' + . 'End: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} 0\.\d{8}, ' + . 'Run: \d{1,}\.\d{1,} s$/' + ] + ]; + } + + /** + * Undocumented function + * + * @cover ::hrRunningTime + * @dataProvider hrRunningTimeProvider + * @testdox hrRunningTime with $out_time matching $expected [$_dataName] + * + * @param string|null $out_time + * @param string $expected + * @return void + */ + public function testHrRunningTime(?string $out_time, string $expected): void + { + // reset for each run + \CoreLibs\Debug\RunningTime::hrRunningTimeReset(); + $start = \CoreLibs\Debug\RunningTime::hrRunningTime(); + $this->assertEquals( + 0, + $start, + 'assert first run 0' + ); + time_nanosleep(1, 500); + if ($out_time === null) { + $second = \CoreLibs\Debug\RunningTime::hrRunningTime(); + } else { + $second = \CoreLibs\Debug\RunningTime::hrRunningTime($out_time); + } + // print "E: " . $end . "\n"; + $this->assertMatchesRegularExpression( + $expected, + (string)$second, + 'assert second run regex' + ); + if ($out_time === null) { + $end_second = \CoreLibs\Debug\RunningTime::hrRunningTimeFromStart(); + } else { + $end_second = \CoreLibs\Debug\RunningTime::hrRunningTimeFromStart($out_time); + } + $this->assertEquals( + $end_second, + $second, + 'assert end is equal second' + ); + // sleep again, second messurement + time_nanosleep(1, 500); + if ($out_time === null) { + $third = \CoreLibs\Debug\RunningTime::hrRunningTime(); + } else { + $third = \CoreLibs\Debug\RunningTime::hrRunningTime($out_time); + } + // third call is not null + $this->assertNotEquals( + 0, + $third, + 'assert third call not null' + ); + // third call is bigger than end + $this->assertNotEquals( + $second, + $third, + 'assert third different second' + ); + // last messurement, must match start - end + last + if ($out_time === null) { + $end = \CoreLibs\Debug\RunningTime::hrRunningTimeFromStart(); + } else { + $end = \CoreLibs\Debug\RunningTime::hrRunningTimeFromStart($out_time); + } + $this->assertGreaterThan( + $third, + $end, + 'assert end greater third' + ); + // new start + \CoreLibs\Debug\RunningTime::hrRunningTimeReset(); + $new_start = \CoreLibs\Debug\RunningTime::hrRunningTime(); + $this->assertEquals( + 0, + $new_start, + 'assert new run 0' + ); + } + + /** + * Undocumented function + * + * @dataProvider runningTimeProvider + * @testdox runningTime matching return $expected_number and start $expected_start end $expected_end [$_dataName] + * + * @param string $expected_number + * @param string $expected_start + * @param string $expected_end + * @return void + */ + public function testRunningTime(string $expected_number, string $expected_start, string $expected_end): void + { + $start = \CoreLibs\Debug\RunningTime::runningTime(true); + // print "Start: " . $start . "\n"; + $this->assertEquals( + 0, + $start + ); + // print "STRING: " . \CoreLibs\Debug\RunningTime::runningTimeString() . "\n"; + $this->assertMatchesRegularExpression( + $expected_start, + \CoreLibs\Debug\RunningTime::runningTimeString() + ); + time_nanosleep(1, 500); + $end = \CoreLibs\Debug\RunningTime::runningTime(true); + // print "Start: " . $end . "\n"; + $this->assertMatchesRegularExpression( + $expected_number, + (string)$end + ); + // print "STRING: " . \CoreLibs\Debug\RunningTime::runningTimeString() . "\n"; + $this->assertMatchesRegularExpression( + $expected_end, + \CoreLibs\Debug\RunningTime::runningTimeString() + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsDebugSupportTest.php b/test/phpunit/CoreLibsDebugSupportTest.php new file mode 100644 index 0000000..9bf2fef --- /dev/null +++ b/test/phpunit/CoreLibsDebugSupportTest.php @@ -0,0 +1,475 @@ + [ + 0 => null, + 1 => "/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{8}$/", + ], + 'microtime -1' => [ + 0 => -1, + 1 => "/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{8}$/", + ], + 'microtime 0' => [ + 0 => 0, + 1 => "/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/", + ], + 'microtime 4' => [ + 0 => 4, + 1 => "/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{4}$/", + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function printArrayProvider(): array + { + return [ + 'empty array' => [ + 0 => [], + 1 => "
Array\n(\n)\n
" + ], + 'simple array' => [ + 0 => ['a', 'b'], + 1 => "
Array\n(\n"
+					. "    [0] => a\n"
+					. "    [1] => b\n"
+					. ")\n
" + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function printBoolProvider(): array + { + return [ + 'true input default' => [ + 0 => true, + 1 => [], + 2 => 'true' + ], + 'false input default' => [ + 0 => false, + 1 => [], + 2 => 'false' + ], + 'false input param name' => [ + 0 => false, + 1 => [ + 'name' => 'param test' + ], + 2 => 'param test: false' + ], + 'true input param name, true override' => [ + 0 => true, + 1 => [ + 'name' => 'param test', + 'true' => 'ok' + ], + 2 => 'param test: ok' + ], + 'false input param name, true override, false override' => [ + 0 => false, + 1 => [ + 'name' => 'param test', + 'true' => 'ok', + 'false' => 'not', + ], + 2 => 'param test: not' + ], + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function printToStringProvider(): array + { + // 0: unput + // 1: html flag (only for strings and arry) + // 2: expected + return [ + 'null' => [ + null, + null, + 'NULL', + ], + 'string' => [ + 'a string', + null, + 'a string', + ], + 'string with html chars, encode' => [ + 'a string with <> &', + true, + 'a string with <> &', + ], + 'string with html chars' => [ + 'a string with <> &', + null, + 'a string with <> &', + ], + 'a number' => [ + 1234, + null, + '1234', + ], + 'a float number' => [ + 1234.5678, + null, + '1234.5678', + ], + 'bool true' => [ + true, + null, + 'TRUE', + ], + 'bool false' => [ + false, + null, + 'FALSE', + ], + 'an array default' => [ + ['a', 'b'], + null, + "
Array\n(\n"
+					. "    [0] => a\n"
+					. "    [1] => b\n"
+					. ")\n
", + ], + 'an array, no html' => [ + ['a', 'b'], + true, + "##HTMLPRE##" + . "Array\n(\n" + . " [0] => a\n" + . " [1] => b\n" + . ")\n" + . "##/HTMLPRE##", + ], + // resource + 'a resource' => [ + tmpfile(), + null, + '/^Resource id #\d+$/', + ], + // object + 'an object' => [ + new \CoreLibs\Debug\Support(), + null, + 'CoreLibs\Debug\Support', + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function debugStringProvider(): array + { + // 0: input string + // 1: replace + // 2: html flag + // 3: expected + return [ + 'null string, default' => [ + null, + null, + null, + '-' + ], + 'empty string, ... replace' => [ + '', + '...', + null, + '...' + ], + 'filled string' => [ + 'some string', + null, + null, + 'some string' + ], + 'string with html chars, encode' => [ + 'a string with <> &', + '-', + true, + 'a string with <> &', + ], + 'string with html chars' => [ + 'a string with <> &', + '-', + null, + 'a string with <> &', + ], + ]; + } + + /** + * Undocumented function + * + * @cover ::printTime + * @dataProvider printTimeProvider + * @testdox printTime test with $microtime and match to regex [$_dataName] + * + * @param int|null $mircrotime + * @param string $expected + * @return void + */ + public function testPrintTime(?int $microtime, string $regex): void + { + if ($microtime === null) { + $this->assertMatchesRegularExpression( + $regex, + \CoreLibs\Debug\Support::printTime() + ); + } else { + $this->assertMatchesRegularExpression( + $regex, + \CoreLibs\Debug\Support::printTime($microtime) + ); + } + } + + /** + * Undocumented function + * + * @cover ::printAr + * @cover ::printArray + * @dataProvider printArrayProvider + * @testdox printAr/printArray $input will be $expected [$_dataName] + * + * @param array $input + * @param string $expected + * @return void + */ + public function testPrintAr(array $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::printAr($input), + 'assert printAr' + ); + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::printArray($input), + 'assert printArray' + ); + } + + /** + * Undocumented function + * + * @cover ::printBool + * @dataProvider printBoolProvider + * @testdox printBool $input will be $expected [$_dataName] + * + * @param bool $input + * @param array $params + * @param string $expected + * @return void + */ + public function testPrintBool(bool $input, array $params, string $expected): void + { + if ( + isset($params['name']) && + isset($params['true']) && + isset($params['false']) + ) { + $string = \CoreLibs\Debug\Support::printBool( + $input, + $params['name'], + $params['true'], + $params['false'] + ); + } elseif (isset($params['name']) && isset($params['true'])) { + $string = \CoreLibs\Debug\Support::printBool( + $input, + $params['name'], + $params['true'] + ); + } elseif (isset($params['name'])) { + $string = \CoreLibs\Debug\Support::printBool( + $input, + $params['name'] + ); + } else { + $string = \CoreLibs\Debug\Support::printBool($input); + } + $this->assertEquals( + $expected, + $string, + 'assert printBool' + ); + } + + /** + * Undocumented function + * + * @cover ::printToString + * @dataProvider printToStringProvider + * @testdox printToString $input with $flag will be $expected [$_dataName] + * + * @param mixed $input anything + * @param boolean|null $flag html flag, only for string and array + * @param string $expected always string + * @return void + */ + public function testPrintToString(mixed $input, ?bool $flag, string $expected): void + { + if ($flag === null) { + // if expected starts with / and ends with / then this is a regex compare + if ( + substr($expected, 0, 1) == '/' && + substr($expected, -1, 1) == '/' + ) { + $this->assertMatchesRegularExpression( + $expected, + \CoreLibs\Debug\Support::printToString($input) + ); + } else { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::printToString($input) + ); + } + } else { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::printToString($input, $flag) + ); + } + } + + /** + * Undocumented function + * + * @cover ::getCallerMethod + * @testWith ["testGetCallerMethod"] + * @testdox getCallerMethod check if it returns $expected [$_dataName] + * + * @return void + */ + public function testGetCallerMethod(string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::getCallerMethod() + ); + } + + /** + * Undocumented function + * + * @cover ::getCallerMethodList + * @testWith [["main", "run", "run", "run", "run", "run", "run", "runBare", "runTest", "testGetCallerMethodList"],["main", "run", "run", "run", "run", "run", "run", "run", "runBare", "runTest", "testGetCallerMethodList"]] + * @testdox getCallerMethodList check if it returns $expected [$_dataName] + * + * @param array $expected + * @return void + */ + public function testGetCallerMethodList(array $expected, array $expected_group): void + { + $compare = \CoreLibs\Debug\Support::getCallerMethodList(); + // if we direct call we have 10, if we call as folder we get 11 + if (count($compare) == 10) { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::getCallerMethodList(), + 'assert expected 10' + ); + } else { + $this->assertEquals( + $expected_group, + \CoreLibs\Debug\Support::getCallerMethodList(), + 'assert expected group' + ); + } + } + + /** + * Undocumented function + * + * @cover ::getCallerClass + * @testWith ["PHPUnit\\TextUI\\Command"] + * @testdox getCallerClass check if it returns $expected [$_dataName] + * + * @return void + */ + public function testGetCallerClass(string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::getCallerClass() + ); + } + + /** + * Undocumented function + * + * @cover ::debugString + * @dataProvider debugStringProvider + * @testdox debugString $input with replace $replace and html $flag will be $expected [$_dataName] + * + * @param string|null $input + * @param string|null $replace + * @param bool|null $flag + * @param string $expected + * @return void + */ + public function testDebugString(?string $input, ?string $replace, ?bool $flag, string $expected): void + { + if ($replace === null && $flag === null) { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::debugString($input), + 'assert all default' + ); + } elseif ($flag === null) { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::debugString($input, $replace), + 'assert flag default' + ); + } else { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::debugString($input, $replace, $flag), + 'assert all set' + ); + } + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsGetDotEnvTest.php b/test/phpunit/CoreLibsGetDotEnvTest.php new file mode 100644 index 0000000..285e19e --- /dev/null +++ b/test/phpunit/CoreLibsGetDotEnvTest.php @@ -0,0 +1,162 @@ + 'A', + 'OTHER' => 'B IS B', + 'Complex' => 'A B \"D is F', + 'HAS_SPACE' => 'ABC', + 'HAS_COMMENT_QUOTES_SPACE' => 'Comment at end with quotes and space', + 'HAS_COMMENT_QUOTES_NO_SPACE' => 'Comment at end with quotes no space', + 'HAS_COMMENT_NO_QUOTES_SPACE' => 'Comment at end no quotes and space', + 'HAS_COMMENT_NO_QUOTES_NO_SPACE' => 'Comment at end no quotes no space', + 'COMMENT_IN_TEXT_QUOTES' => 'Foo bar # comment in here', + 'FAILURE' => 'ABC', + 'SIMPLEBOX' => 'A B C', + 'TITLE' => '1', + 'FOO' => '1.2', + 'SOME.TEST' => 'Test Var', + 'SOME.LIVE' => 'Live Var', + 'A_TEST1' => 'foo', + 'A_TEST2' => '${TEST1:-bar}', + 'A_TEST3' => '${TEST4:-bar}', + 'A_TEST5' => 'null', + 'A_TEST6' => '${TEST5-bar}', + 'A_TEST7' => '${TEST6:-bar}', + 'B_TEST1' => 'foo', + 'B_TEST2' => '${TEST1:=bar}', + 'B_TEST3' => '${TEST4:=bar}', + 'B_TEST5' => 'null', + 'B_TEST6' => '${TEST5=bar}', + 'B_TEST7' => '${TEST6=bar}', + 'Test' => 'A', + 'TEST' => 'B', + 'LINE' => "ABC\nDEF", + 'OTHERLINE' => "ABC\nAF\"ASFASDF\nMORESHIT", + 'SUPERLINE' => '', + '__FOO_BAR_1' => 'b', + '__FOOFOO' => 'f ', + 123123 => 'number', + 'EMPTY' => '', + ]; + // 0: folder relative to test folder, if unset __DIR__ + // 1: file, if unset .env + // 2: status to be returned + // 3: _ENV file content to be set + // 4: override chmod as octect in string + return [ + 'default' => [ + 'folder' => null, + 'file' => null, + 'status' => 3, + 'content' => [], + 'chmod' => null, + ], + 'cannot open file' => [ + 'folder' => __DIR__ . DIRECTORY_SEPARATOR . 'dotenv', + 'file' => 'cannot_read.env', + 'status' => 2, + 'content' => [], + 'chmod' => '000', + ], + 'empty file' => [ + 'folder' => __DIR__ . DIRECTORY_SEPARATOR . 'dotenv', + 'file' => 'empty.env', + 'status' => 1, + 'content' => [], + 'chmod' => null, + ], + 'override all' => [ + 'folder' => __DIR__ . DIRECTORY_SEPARATOR . 'dotenv', + 'file' => 'test.env', + 'status' => 0, + 'content' => $dot_env_content, + 'chmod' => null, + ], + 'override directory' => [ + 'folder' => __DIR__ . DIRECTORY_SEPARATOR . 'dotenv', + 'file' => null, + 'status' => 0, + 'content' => $dot_env_content, + 'chmod' => null, + ], + ]; + } + + /** + * test read .env file + * + * @covers ::readEnvFile + * @dataProvider envFileProvider + * @testdox Read _ENV file from $folder / $file with expected status: $expected_status and chmod $chmod [$_dataName] + * + * @param string|null $folder + * @param string|null $file + * @param int $expected_status + * @param array $expected_env + * @param string|null $chmod + * @return void + */ + public function testReadEnvFile( + ?string $folder, + ?string $file, + int $expected_status, + array $expected_env, + ?string $chmod + ): void { + // if we have file + chmod set + $old_chmod = null; + if ( + is_file($folder . DIRECTORY_SEPARATOR . $file) && + !empty($chmod) + ) { + // get the old permissions + $old_chmod = fileperms($folder . DIRECTORY_SEPARATOR . $file); + chmod($folder . DIRECTORY_SEPARATOR . $file, octdec($chmod)); + } + if ($folder !== null && $file !== null) { + $status = DotEnv::readEnvFile($folder, $file); + } elseif ($folder !== null) { + $status = DotEnv::readEnvFile($folder); + } else { + $status = DotEnv::readEnvFile(); + } + $this->assertEquals( + $status, + $expected_status, + 'Assert returned status equal' + ); + // now assert read data + $this->assertEquals( + $_ENV, + $expected_env, + 'Assert _ENV correct' + ); + // if we have file and chmod unset + if ($old_chmod !== null) { + chmod($folder . DIRECTORY_SEPARATOR . $file, $old_chmod); + } + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsGetSystemTest.php b/test/phpunit/CoreLibsGetSystemTest.php new file mode 100644 index 0000000..26ea383 --- /dev/null +++ b/test/phpunit/CoreLibsGetSystemTest.php @@ -0,0 +1,221 @@ + [ + 0 => UPLOAD_ERR_INI_SIZE, + 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', + ], + 'upload err from size' => [ + 0 => UPLOAD_ERR_FORM_SIZE, + 1 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form' + ], + 'upload err partial' => [ + 0 => UPLOAD_ERR_PARTIAL, + 1 => 'The uploaded file was only partially uploaded' + ], + 'upload err no file' => [ + 0 => UPLOAD_ERR_NO_FILE, + 1 => 'No file was uploaded' + ], + 'upload err no tmp dir' => [ + 0 => UPLOAD_ERR_NO_TMP_DIR, + 1 => 'Missing a temporary folder' + ], + 'upload err cant write' => [ + 0 => UPLOAD_ERR_CANT_WRITE, + 1 => 'Failed to write file to disk' + ], + 'upload err extension' => [ + 0 => UPLOAD_ERR_EXTENSION, + 1 => 'File upload stopped by extension' + ], + 'unkown error' => [ + 0 => 99999, + 1 => 'Unknown upload error' + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function getHostNameProvider(): array + { + return [ + 'original set' => [ + 0 => null, + 1 => 'NOHOST', + 2 => 'NOPORT', + ], + 'override set no port' => [ + 0 => 'foo.org', + 1 => 'foo.org', + 2 => '80' + ], + 'override set with port' => [ + 0 => 'foo.org:443', + 1 => 'foo.org', + 2 => '443' + ] + ]; + } + + /** + * Undocumented function + * + * @return array + */ + public function getPageNameProvider(): array + { + return [ + // 0: input + // 1: expected default/WITH_EXTENSION + // 2: expected NO_EXTENSION + // 3: expected FULL_PATH, if first and last character are / use regex + 'original set' => [ + 0 => null, + 1 => 'phpunit', + 2 => 'phpunit', + // NOTE: this can change, so it is a regex check + 3 => "/^(\/?.*\/?)?www\/vendor\/bin\/phpunit$/", + ], + 'some path with extension' => [ + 0 => '/some/path/to/file.txt', + 1 => 'file.txt', + 2 => 'file', + 3 => '/some/path/to/file.txt', + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::fileUploadErrorMessage + * @dataProvider fileUploadErrorMessageProvider + * @testdox fileUploadErrorMessage $input error matches $expected [$_dataName] + * + * @param integer $input + * @param string $expected + * @return void + */ + public function testFileUploadErrorMessage(int $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Get\System::fileUploadErrorMessage($input) + ); + } + + /** + * Undocumented function + * + * @covers ::getHostName + * @dataProvider getHostNameProvider + * @testdox getHostName $input must match $expected_host:$expected_port [$_dataName] + * + * @param string|null $input + * @param string $expected_host + * @param string $expected_port + * @return void + */ + public function testGetHostNanme(?string $input, string $expected_host, string $expected_port): void + { + // print "HOSTNAME: " . $_SERVER['HTTP_HOST'] . "
"; + // print "SERVER: " . print_r($_SERVER, true) . "\n"; + // print "SELF: " . $_SERVER['PHP_SELF'] . "\n"; + if ($input !== null) { + $_SERVER['HTTP_HOST'] = $input; + } + list ($host, $port) = \CoreLibs\Get\System::getHostName(); + $this->assertEquals( + $expected_host, + $host, + 'failed expected host assert' + ); + $this->assertEquals( + $expected_port, + $port, + 'faile expected port assert' + ); + } + + /** + * Undocumented function + * + * @covers ::getPageName + * @dataProvider getPageNameProvider + * @testdox getPageName $input will match 0: $expected_0, 1: $expected_1, 2: $expected_2 [$_dataName] + * + * @param string|null $input + * @param string $expected_0 default with extension + * @param string $expected_1 no extension + * @param string $expected_2 full path + * @return void + */ + public function testGetPageName(?string $input, string $expected_0, string $expected_1, string $expected_2) + { + if ($input !== null) { + $_SERVER['PHP_SELF'] = $input; + } + // default 0, + $this->assertEquals( + $expected_0, + \CoreLibs\Get\System::getPageName(), + 'failed default assert' + ); + $this->assertEquals( + $expected_0, + \CoreLibs\Get\System::getPageName(\CoreLibs\Get\System::WITH_EXTENSION), + 'failed WITH_EXTESION assert' + ); + $this->assertEquals( + $expected_1, + \CoreLibs\Get\System::getPageName(\CoreLibs\Get\System::NO_EXTENSION), + 'failed NO_EXTENSION assert' + ); + // FULL PATH check can be equals or regex + $page_name_full_path = \CoreLibs\Get\System::getPageName(\CoreLibs\Get\System::FULL_PATH); + if ( + substr($expected_2, 0, 1) == '/' && + substr($expected_2, -1, 1) == '/' + ) { + // this is regex + $this->assertMatchesRegularExpression( + $expected_2, + $page_name_full_path, + 'failed FULL_PATH assert regex' + ); + } else { + $this->assertEquals( + $expected_2, + $page_name_full_path, + 'failed FULL_PATH assert equals' + ); + } + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsLanguageGetLocaleTest.php b/test/phpunit/CoreLibsLanguageGetLocaleTest.php new file mode 100644 index 0000000..981c3e2 --- /dev/null +++ b/test/phpunit/CoreLibsLanguageGetLocaleTest.php @@ -0,0 +1,310 @@ + [ + // lang, domain, encoding, path + null, null, null, null, + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + null, null, + // return array + [ + 'locale' => 'en_US.UTF-8', + 'lang' => 'en_US', + 'domain' => 'frontend', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?includes\/locale\/$/", + ], + ], + 'no params, session charset and lang' => [ + // lang, domain, encoding, path + null, null, null, null, + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + 'ja_JP', 'UTF-8', + // return array + [ + 'locale' => 'ja_JP', + 'lang' => 'ja_JP', + 'domain' => 'frontend', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?includes\/locale\/$/", + ], + ], + 'no params, session charset and lang short' => [ + // lang, domain, encoding, path + null, null, null, null, + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + 'ja', 'UTF-8', + // return array + [ + 'locale' => 'ja', + 'lang' => 'ja', + 'domain' => 'frontend', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?includes\/locale\/$/", + ], + ], + // param lang (no sessions) + 'locale param only, no sessions' => [ + // lang, domain, encoding, path + 'ja.UTF-8', null, null, null, + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + null, null, + // return array + [ + 'locale' => 'ja.UTF-8', + 'lang' => 'ja', + 'domain' => 'frontend', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?includes\/locale\/$/", + ], + ], + // different locale setting + 'locale complex param only, no sessions' => [ + // lang, domain, encoding, path + 'ja_JP.SJIS', null, null, null, + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + null, null, + // return array + [ + 'locale' => 'ja_JP.SJIS', + 'lang' => 'ja_JP', + 'domain' => 'frontend', + 'encoding' => 'SJIS', + 'path' => "/^\/(.*\/)?includes\/locale\/$/", + ], + ], + // param lang and domain (no override) + 'locale, domain params, no sessions' => [ + // lang, domain, encoding, path + 'ja.UTF-8', 'admin', null, null, + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + null, null, + // return array + [ + 'locale' => 'ja.UTF-8', + 'lang' => 'ja', + 'domain' => 'admin', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?includes\/locale\/$/", + ], + ], + // param lang and domain (no override) + 'locale, domain, encoding params, no sessions' => [ + // lang, domain, encoding, path + 'ja.UTF-8', 'admin', 'UTF-8', null, + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + null, null, + // return array + [ + 'locale' => 'ja.UTF-8', + 'lang' => 'ja', + 'domain' => 'admin', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?includes\/locale\/$/", + ], + ], + // lang, domain, path (no override) + 'locale, domain and path, no sessions' => [ + // lang, domain, encoding, path + 'ja.UTF-8', 'admin', '', __DIR__ . '/locale_other/', + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + null, null, + // return array + [ + 'locale' => 'ja.UTF-8', + 'lang' => 'ja', + 'domain' => 'admin', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?locale_other\/$/", + ], + ], + // all params set (no override) + 'all parameter, no sessions' => [ + // lang, domain, encoding, path + 'ja', 'admin', 'UTF-8', __DIR__ . '/locale_other/', + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + null, null, + // return array + [ + 'locale' => 'ja', + 'lang' => 'ja', + 'domain' => 'admin', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?locale_other\/$/", + ], + ], + // param lang and domain (no override) + 'long locale, domain, encoding params, no sessions' => [ + // lang, domain, encoding, path + 'de_CH.UTF-8@euro', 'admin', 'UTF-8', null, + // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET + null, null, + // return array + [ + 'locale' => 'de_CH.UTF-8@euro', + 'lang' => 'de_CH', + 'domain' => 'admin', + 'encoding' => 'UTF-8', + 'path' => "/^\/(.*\/)?includes\/locale\/$/", + ], + ], + // TODO invalid params (bad path) (no override) + // TODO param calls, but with override set + ]; + } + + /** + * Undocumented function + * + * @covers ::setLocale + * @dataProvider setLocaleProvider + * @testdox lang settings lang $language, domain $domain, encoding $encoding, path $path; session lang: $SESSION_DEFAULT_LOCALE, session char: $SESSION_DEFAULT_CHARSET [$_dataName] + * + * @return void + */ + public function testsetLocale( + ?string $language, + ?string $domain, + ?string $encoding, + ?string $path, + ?string $SESSION_DEFAULT_LOCALE, + ?string $SESSION_DEFAULT_CHARSET, + array $expected + ): void { + $return_lang_settings = []; + global $_SESSION; + // set override + if ($SESSION_DEFAULT_LOCALE !== null) { + $_SESSION['DEFAULT_LOCALE'] = $SESSION_DEFAULT_LOCALE; + } + if ($SESSION_DEFAULT_CHARSET !== null) { + $_SESSION['DEFAULT_CHARSET'] = $SESSION_DEFAULT_CHARSET; + } + // function call + if ($language === null && $domain === null && $encoding === null && $path === null) { + $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(); + } elseif ($language !== null && $domain === null && $encoding === null && $path === null) { + $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale( + $language + ); + } elseif ($language !== null && $domain !== null && $encoding === null && $path === null) { + $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale( + $language, + $domain + ); + } elseif ($language !== null && $domain !== null && $encoding !== null && $path === null) { + $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale( + $language, + $domain, + $encoding + ); + } else { + $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale( + $language, + $domain, + $encoding, + $path + ); + } + // print "RETURN: " . print_r($return_lang_settings, true) . "\n"; + + foreach ( + [ + 'locale', 'lang', 'domain', 'encoding', 'path' + ] as $key + ) { + $value = $expected[$key]; + if (strpos($value, "/") === 0) { + // this is regex + $this->assertMatchesRegularExpression( + $value, + $return_lang_settings[$key], + 'assert regex failed for ' . $key + ); + } else { + // assert equal + $this->assertEquals( + $value, + $return_lang_settings[$key], + 'assert equal failed for ' . $key + ); + } + } + // unset all vars + $_SESSION = []; + unset($GLOBALS['OVERRIDE_LANG']); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsLanguageL10nTest.php b/test/phpunit/CoreLibsLanguageL10nTest.php new file mode 100644 index 0000000..ee5bf0f --- /dev/null +++ b/test/phpunit/CoreLibsLanguageL10nTest.php @@ -0,0 +1,1046 @@ +assertIsObject( + $l10n_obj + ); + $this->assertInstanceOf( + '\CoreLibs\Language\L10n', + $l10n_obj + ); + } + + /** + * get current translator class type check + * + * @covers ::getTranslatorClass + * @testdox check that getTranslatorClass() returns valid instance + * + * @return void + */ + public function testGetTranslatorClass(): void + { + $l10n = new \CoreLibs\Language\L10n(); + $translator = $l10n->getTranslatorClass(); + $this->assertIsObject( + $translator + ); + $this->assertInstanceOf( + '\CoreLibs\Language\Core\GetTextReader', + $translator + ); + } + + /** + * provider for class load parameters + * + * @return array + */ + public function l10nObjectProvider(): array + { + return [ + // 0: locale + // 1: domain + // 2: encoding + // 3: path + // 4: locale expected + // 5: locale set expected + // 6: domain exepcted + // 7: context (null for none) + // 8: test string in + // 9: test translated + // new style load + 'gettext load en' => [ + 'en_US.UTF-8', + 'frontend', + __DIR__ . 'includes/locale/', + // + 'en_US.UTF-8', + 'en_US', + 'frontend', + null, + 'Original', + 'Translated frontend en_US', + ], + 'gettext load en' => [ + 'en_US.UTF-8', + 'frontend', + __DIR__ . 'includes/locale/', + // + 'en_US.UTF-8', + 'en_US', + 'frontend', + 'context', + 'Original', + 'Original context frontend en_US', + ], + 'gettext load ja' => [ + 'ja_JP.UTF-8', + 'admin', + __DIR__ . 'includes/locale/', + // + 'ja_JP.UTF-8', + 'ja_JP', + 'admin', + null, + 'Original', + 'Translated admin ja_JP', + ], + // mixed path and domain + 'mixed path and domain' => [ + 'en_US.UTF-8', + __DIR__ . 'includes/locale/', + 'frontend', + // + 'en_US.UTF-8', + 'en_US', + 'frontend', + 'context', + 'Original', + 'Original context frontend en_US', + ], + // null set + 'empty load new ' => [ + '', + '', + '', + // + '', + '', + '', + null, + 'Original', + 'Original', + ] + ]; + } + + /** + * new class load test (basic test) + * + * @covers ::__construct + * @dataProvider l10nObjectProvider + * @testdox check l10n init with Locale $locale, Path $path, Domain $domain, Legacy: $legacy with $context [$_dataName] + * + * @param string|null $locale + * @param string|null $domain + * @param string|null $path + * @param string $locale_expected + * @param string $locale_set_expected + * @param string $domain_expected + * @param ?string $context + * @param string $original + * @param string $translated + * @return void + */ + public function testL10nObject( + ?string $locale, + ?string $domain, + ?string $path, + string $locale_expected, + string $locale_set_expected, + string $domain_expected, + ?string $context, + string $original, + string $translated + ): void { + if ($locale === null) { + $l10n = new \CoreLibs\Language\L10n(); + } elseif ($domain === null) { + $l10n = new \CoreLibs\Language\L10n($locale); + } elseif ($path === null) { + $l10n = new \CoreLibs\Language\L10n($locale, $domain); + } else { + $l10n = new \CoreLibs\Language\L10n($locale, $domain, $path); + } + // print "LOC: " . $locale . ", " . $l10n->getLocale() . ", " . $locale_expected . "\n"; + // print "MO: " . $l10n->getMoFile() . "\n"; + $this->assertEquals( + $locale_expected, + $l10n->getLocale(), + 'Locale assert failed' + ); + $this->assertEquals( + $locale_set_expected, + $l10n->getLocaleSet(), + 'Locale set assert failed' + ); + $this->assertEquals( + $domain_expected, + $l10n->getDomain(), + 'Domain assert failed' + ); + if (empty($context)) { + $this->assertEquals( + $translated, + $l10n->__($original), + 'Translated string assert failed' + ); + } else { + $this->assertEquals( + $translated, + $l10n->__p($context, $original), + 'Translated string assert failed in context: ' . $context + ); + } + } + + // l10nReloadMOfile and getTranslator + // null init with loader + // loader with reload (change type) + + /** + * Undocumented function + * + * @return array + */ + public function getTranslatorProvider(): array + { + return [ + // 0: locale + // 1: domain + // 2: path + // 3: load error + // 4: input string to translated + // 5: expected locale + // 6: expected locale set + // 7: expected domain + // 8: expected translation + // 9: change locale + // 10: change domain + // 11: change path + // 12: change load error + // 13: expected locale + // 14: expected locale set + // 15: expected domain + // 16: expected translation + 'load and change (en->ja)' => [ + // set 0-2 + 'en_US.UTF-8', + 'frontend', + __DIR__ . 'includes/locale/', + // status 3 + false, + // to translate 4 + 'Original', + // check setter 5-7 + 'en_US.UTF-8', + 'en_US', + 'frontend', + 'Translated frontend en_US', + // set new 8-10 + 'ja_JP.UTF-8', + 'frontend', + __DIR__ . 'includes/locale/', + // status new 11 + false, + // check new setter 12-14 + 'ja_JP.UTF-8', + 'ja_JP', + 'frontend', + 'Translated frontend ja_JP', + ], + 'empty load and change to en' => [ + // set 0-2 + '', + '', + '', + // status 3 + false, + // to translate 4 + 'Original', + // check setter 5-7 + '', + '', + '', + 'Original', + // set new 8-10 + 'en_US.UTF-8', + 'frontend', + __DIR__ . 'includes/locale/', + // status new 11 + false, + // check new setter 12-14 + 'en_US.UTF-8', + 'en_US', + 'frontend', + 'Translated frontend en_US', + ] + ]; + } + + /** + * init check and connected change translation + * + * @covers ::getTranslator + * @covers ::l10nReloadMOfile + * @dataProvider getTranslatorProvider + * @testdox change locale from $locale and domain $domain to locale $locale_new and domain $domain_new [$_dataName] + * + * @param string|null $locale + * @param string|null $domain + * @param string|null $path + * @param bool $load_error + * @param string $original + * @param string $locale_expected_a + * @param string $locale_set_expected_a + * @param string $domain_expected_a + * @param string $translated_a + * @param string|null $locale_new + * @param string|null $domain_new + * @param string|null $path_new + * @param bool $load_error_new + * @param string $locale_set_expected_b + * @param string $locale_expected_b + * @param string $domain_expected_b + * @param string $translated_b + * @return void + */ + public function testGetTranslator( + // 0-2 + ?string $locale, + ?string $domain, + ?string $path, + // 3 + bool $load_error, + // 4 + string $original, + // 5-7 + string $locale_expected_a, + string $locale_set_expected_a, + string $domain_expected_a, + string $translated_a, + // 8-10 + ?string $locale_new, + ?string $domain_new, + ?string $path_new, + // 11 + bool $load_error_new, + // 12-14 + string $locale_expected_b, + string $locale_set_expected_b, + string $domain_expected_b, + string $translated_b + ): void { + if ($locale === null) { + $l10n = new \CoreLibs\Language\L10n(); + } elseif ($domain === null) { + $l10n = new \CoreLibs\Language\L10n($locale); + } elseif ($path === null) { + $l10n = new \CoreLibs\Language\L10n($locale, $domain); + } else { + $l10n = new \CoreLibs\Language\L10n($locale, $domain, $path); + } + // print "LOC: " . $locale . ", " . $l10n->getLocale() . ", " . $locale_expected . "\n"; + // status check + $this->assertEquals( + $load_error, + $l10n->getLoadError(), + 'Legacy method load error init check' + ); + $this->assertEquals( + $locale_expected_a, + $l10n->getLocale(), + 'Locale init assert failed' + );$this->assertEquals( + $locale_set_expected_a, + $l10n->getLocaleSet(), + 'Locale Set init assert failed' + ); + $this->assertEquals( + $domain_expected_a, + $l10n->getDomain(), + 'Domain init assert failed' + ); + $this->assertEquals( + $translated_a, + $l10n->__($original), + 'Translated string init assert failed' + ); + + // switch + if ($locale_new === null) { + $translator = $l10n->getTranslator(); + } elseif ($domain_new === null) { + $translator = $l10n->getTranslator($locale_new); + } elseif ($path_new === null) { + $translator = $l10n->getTranslator($locale_new, $domain_new); + } else { + $translator = $l10n->getTranslator($locale_new, $domain_new, $path_new); + } + // status check + $this->assertEquals( + $load_error_new, + $l10n->getLoadError(), + 'Translate method load error change check' + ); + // check that returned is class GetTextReader and object + $this->assertIsObject( + $translator, + 'translater class is object assert failed' + ); + $this->assertInstanceOf( + '\CoreLibs\Language\Core\GetTextReader', + $translator, + 'translator class is correct instance assert failed' + ); + + // translator class + $this->assertEquals( + $translated_b, + $translator->gettext($original), + 'Translated string change assert failed from returned class' + ); + // new set check + $this->assertEquals( + $locale_expected_b, + $l10n->getLocale(), + 'Locale change assert failed' + ); + $this->assertEquals( + $locale_set_expected_b, + $l10n->getLocaleSet(), + 'Locale Set change assert failed' + ); + $this->assertEquals( + $domain_expected_b, + $l10n->getDomain(), + 'Domain change assert failed' + ); + $this->assertEquals( + $translated_b, + $l10n->__($original), + 'Translated string change assert failed' + ); + } + + // TODO: domain based + // ->dgettext + // ->dngettext + // ->dpgettext + // ->dpngettext + + /** + * for plural and plural context + * + * @return array + */ + public function ngettextProvider(): array + { + return [ + // 0: locale + // 1: path + // 2: domain + // 3: context (null for none) + // 4: single string + // 5: plural string + // 6: array for each n value expected string + 'plural text en' => [ + 'en_US', + __DIR__ . 'includes/locale/', + 'admin', + // context + null, + // text single/multi in + 'single', + 'multi', + // confirm translation, pos in array equal n + [ + 0 => 'Multi admin en_US 1', + 1 => 'Multi admin en_US 0', + 2 => 'Multi admin en_US 1', + ] + ], + 'plural text context en' => [ + 'en_US', + __DIR__ . 'includes/locale/', + 'admin', + // context + 'context', + // text single/multi in + 'single', + 'multi', + // confirm translation, pos in array equal n + [ + 0 => 'Multi context admin en_US 1', + 1 => 'Multi context admin en_US 0', + 2 => 'Multi context admin en_US 1', + ] + ], + ]; + } + + /** + * plural and plural context + * + * @covers ::__n + * @covers ::__pn + * @dataProvider ngettextProvider + * @testdox plural string test for locale $locale and domain $domain with $context [$_dataName] + * + * @param string $locale + * @param string $path + * @param string $domain + * @param ?string $context + * @param string $original_single + * @param string $original_plural + * @param array $expected_strings + * @return void + */ + public function testNgettext( + // config 0-3 + string $locale, + string $path, + string $domain, + // context string + ?string $context, + // input strings + string $original_single, + string $original_plural, + // expected + array $expected_strings + ): void { + $l10n = new \CoreLibs\Language\L10n($locale, $path, $domain, false); + + foreach ($expected_strings as $n => $expected) { + if (empty($context)) { + $this->assertEquals( + $expected, + $l10n->__n($original_single, $original_plural, $n), + 'assert failed for plural: ' . $n + ); + } else { + $this->assertEquals( + $expected, + $l10n->__np($context, $original_single, $original_plural, $n), + 'assert failed for plural: ' . $n . ' in context: ' . $context + ); + } + } + } + + /** + * locales list for testing locale folder lookup + * + * @return array + */ + public function localesProvider(): array + { + return [ + // 0: locale + // 1: return array + 'en' => [ + 'en', + [ + 'en', + ], + [ + 'lang' => 'en', + 'country' => null, + 'charset' => null, + 'modifier' => null, + ], + ], + 'en.UTF-8' => [ + 'en.UTF-8', + [ + 'en.UTF-8', + 'en', + ], + [ + 'lang' => 'en', + 'country' => null, + 'charset' => 'UTF-8', + 'modifier' => null, + ], + ], + 'en_US' => [ + 'en_US', + [ + 'en_US', + 'en', + ], + [ + 'lang' => 'en', + 'country' => 'US', + 'charset' => null, + 'modifier' => null, + ], + ], + 'en_US.UTF-8' => [ + 'en_US.UTF-8', + [ + 'en_US.UTF-8', + 'en_US', + 'en', + ], + [ + 'lang' => 'en', + 'country' => 'US', + 'charset' => 'UTF-8', + 'modifier' => null, + ], + ], + 'en_US@subtext' => [ + 'en_US@subtext', + [ + 'en_US@subtext', + 'en@subtext', + 'en_US', + 'en', + ], + [ + 'lang' => 'en', + 'country' => 'US', + 'charset' => null, + 'modifier' => 'subtext', + ], + ], + 'en_US.UTF-8@subtext' => [ + 'en_US.UTF-8@subtext', + [ + 'en_US.UTF-8@subtext', + 'en_US@subtext', + 'en@subtext', + 'en_US.UTF-8', + 'en_US', + 'en', + ], + [ + 'lang' => 'en', + 'country' => 'US', + 'charset' => 'UTF-8', + 'modifier' => 'subtext', + ], + ] + ]; + } + + /** + * test locales array return + * + * @covers ::listLocales + * @dataProvider localesProvider + * @testdox check $locale [$_dataName] + * + * @param string $locale + * @param array $expected_list + * @param array $expected_detail + * @return void + */ + public function testListLocales(string $locale, array $expected_list, array $expected_detail): void + { + $locale_detail = \CoreLibs\Language\L10n::parseLocale($locale); + $this->assertEquals( + $expected_detail, + $locale_detail, + 'Parse local assert failed' + ); + $locale_list = \CoreLibs\Language\L10n::listLocales($locale); + // print "LOCALES: " . print_r($locale_list, true) . "\n"; + $this->assertEquals( + $expected_list, + $locale_list, + 'List locale assert failed' + ); + } + + // @covers ::detectLocale + + /** + * Undocumented function + * + * @return array + */ + public function detectLocaleProvider(): array + { + return [ + // 0: type: global | env + // 1: global variable name or enviroment var + // 2: value to set + // 3: value to expect back + 'global locale' => [ + 'global', + 'LOCALE', + 'ja_JP.UTF-8', + 'ja_JP.UTF-8', + ], + 'env LC_ALL' => [ + 'env', + 'LC_ALL', + 'ja_JP.UTF-8', + 'ja_JP.UTF-8', + ], + 'env LANG' => [ + 'env', + 'LANG', + 'ja_JP.UTF-8', + 'ja_JP.UTF-8', + ], + 'default return' => [ + 'env', + 'LC_ALL', + '', + 'en', + ] + ]; + } + + /** + * Undocumented function + * @covers ::detectLocale + * @dataProvider detectLocaleProvider + * @testdox check detectLocale for $type with $var and $value is $expected [$_dataName] + * + * @return void + */ + public function testDetectLocale( + string $type, + string $var, + string $value, + string $expected + ): void { + switch ($type) { + case 'global': + $GLOBALS[$var] = $value; + break; + case 'env': + $old_value = getenv("$var"); + putenv("$var=$value"); + // unset all other env vars + foreach (['LC_ALL', 'LC_MESSAGES', 'LANG'] as $env) { + if ($env != $var) { + putenv("$env="); + } + } + break; + } + $locale = \CoreLibs\Language\L10n::detectLocale(); + $this->assertEquals( + $expected, + $locale + ); + // reset post run + switch ($type) { + case 'global': + unset($GLOBALS[$var]); + break; + case 'env': + putenv("$var=$old_value"); + break; + } + } + + // set/get text domain, domain, locale + + /** + * Undocumented function + * + * @return array + */ + public function textDomainProvider(): array + { + return [ + // 0: set domain + // 1: set path + // 2: get domain + // 3: expected path + 'valid set and get' => [ + 'foo', + 'foo/bar', + 'foo', + 'foo/bar', + ], + 'invalid set and get' => [ + 'foo', + 'foo/bar', + 'iamnotset', + false + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::setTextDomain + * @covers ::getTextDomain + * @dataProvider textDomainProvider + * @testdox set $domain with $path and get $get_domain and expect $expected [$_dataName] + * + * @param string $domain + * @param string $path + * @param string $get_domain + * @param string|bool $expected + * @return void + */ + public function testSetGetTextDomain(string $domain, string $path, string $get_domain, $expected): void + { + $l10n = new \CoreLibs\Language\L10n(); + $l10n->setTextDomain($domain, $path); + $this->assertEquals( + $expected, + $l10n->getTextDomain($get_domain) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function domainProvider(): array + { + return [ + // 0: set domain + // 1: expected domain from get + 'valid domain' => [ + 'foo', + 'foo', + ], + 'empty domain' => [ + '', + '', + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::setDomain + * @covers ::getDomain + * @dataProvider domainProvider + * @testdox set $domain and expect $expected [$_dataName] + * + * @param string $domain + * @param string $expected + * @return void + */ + public function testSetGetDomain(string $domain, string $expected): void + { + $l10n = new \CoreLibs\Language\L10n(); + $l10n->setDomain($domain); + $this->assertEquals( + $expected, + $l10n->getDomain() + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function localeProvider(): array + { + return [ + // 0: set locale + // 1: pre set if not null or not empty + // 2: expected return from set + // 3: expected from get + 'valid locale' => [ + 'foo', + null, + 'foo', + 'foo', + ], + 'empty locale' => [ + '', + null, + '', + '', + ], + 'empty locale, pre set' => [ + '', + 'foo', + 'foo', + 'foo', + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::setLocale + * @covers ::getLocale + * @dataProvider localeProvider + * @testdox set $locale with $expected_return and expect $expected [$_dataName] + * + * @param string $locale + * @param string $pre_locale + * @param string $expected_return + * @param string $expected + * @return void + */ + public function testSetGetLocale( + string $locale, + ?string $pre_locale, + string $expected_return, + string $expected + ): void { + $l10n = new \CoreLibs\Language\L10n(); + if (!empty($pre_locale)) { + $l10n->setLocale($pre_locale); + } + $returned = $l10n->setLocale($locale); + $this->assertEquals( + $expected_return, + $returned, + 'Set locale return assert failed' + ); + $this->assertEquals( + $expected, + $l10n->getLocale(), + 'Get locale aszert failed' + ); + } + + // static load + + /** + * Undocumented function + * + * @return array + */ + public function functionsProvider(): array + { + return [ + // 0: lang/locale + // 1: domain + // 2: path + // 3: encoding + // 4: string + // 5: translated string + 'standard en' => [ + 'en_US.UTF-8', + 'frontend', + __DIR__ . 'includes/locale/', + 'UTF-8', + 'Original', + 'Translated frontend en_US', + ], + 'standard ja' => [ + 'ja_JP.UTF-8', + 'admin', + __DIR__ . 'includes/locale/', + 'UTF-8', + 'Original', + 'Translated admin ja_JP', + ] + ]; + } + + /** + * fuctions check + * TODO: others d/dn/dp/dnp gettext functions + * + * @covers __setlocale + * @covers __bindtextdomain + * @covers __bind_textdomain_codeset + * @covers __textdomain + * @covers __gettext + * @covers __ + * @dataProvider functionsProvider + * @testdox check functions with locale $locale and domain $domain [$_dataName] + * @param string $locale + * @param string $domain + * @param string $path + * @param string $encoding + * @param string $original + * @param string $translated + * @return void + */ + public function testFunctions( + string $locale, + string $domain, + string $path, + string $encoding, + string $original, + string $translated + ): void { + \CoreLibs\Language\L10n::loadFunctions(); + _setlocale(LC_MESSAGES, $locale); + _textdomain($domain); + _bindtextdomain($domain, $path); + _bind_textdomain_codeset($domain, $encoding); + $this->assertEquals( + $translated, + __($original), + 'function __ assert failed' + ); + $this->assertEquals( + $translated, + _gettext($original), + 'function gettext assert failed' + ); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsOutputFormElementsTest.php b/test/phpunit/CoreLibsOutputFormElementsTest.php new file mode 100644 index 0000000..f228bfa --- /dev/null +++ b/test/phpunit/CoreLibsOutputFormElementsTest.php @@ -0,0 +1,33 @@ +assertTrue(true, 'Output Form Elements Tests not implemented'); + $this->markTestIncomplete( + 'Output\Form\Elements Tests have not yet been implemented' + ); + // $this->markTestSkipped('No implementation for Output\Form\Elements at the moment'); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsOutputFormGenerateTest.php b/test/phpunit/CoreLibsOutputFormGenerateTest.php new file mode 100644 index 0000000..40ba194 --- /dev/null +++ b/test/phpunit/CoreLibsOutputFormGenerateTest.php @@ -0,0 +1,33 @@ +assertTrue(true, 'Output Form Generate Tests not implemented'); + $this->markTestIncomplete( + 'Output\Form\Generate Tests have not yet been implemented' + ); */ + $this->markTestSkipped('No implementation for Output\Form\Generate at the moment'); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsOutputFormTokenTest.php b/test/phpunit/CoreLibsOutputFormTokenTest.php new file mode 100644 index 0000000..1e3418b --- /dev/null +++ b/test/phpunit/CoreLibsOutputFormTokenTest.php @@ -0,0 +1,33 @@ +assertTrue(true, 'Output Form Token Tests not implemented'); + $this->markTestIncomplete( + 'Output\Form\Token Tests have not yet been implemented' + ); + // $this->markTestSkipped('No implementation for Output\Form\Token at the moment'); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsOutputImageTest.php b/test/phpunit/CoreLibsOutputImageTest.php new file mode 100644 index 0000000..5384093 --- /dev/null +++ b/test/phpunit/CoreLibsOutputImageTest.php @@ -0,0 +1,33 @@ +assertTrue(true, 'Output Image Tests not implemented'); + $this->markTestIncomplete( + 'Output\Image Tests have not yet been implemented' + ); + // $this->markTestSkipped('No implementation for Output\Image at the moment'); + } +} + +// __END__ diff --git a/test/phpunit/CoreLibsOutputProgressbarTest.php b/test/phpunit/CoreLibsOutputProgressbarTest.php new file mode 100644 index 0000000..a31e122 --- /dev/null +++ b/test/phpunit/CoreLibsOutputProgressbarTest.php @@ -0,0 +1,33 @@ +assertTrue(true, 'Output Progressbar Tests not implemented'); + // $this->markTestIncomplete( + // 'Output\Progressbar Tests have not yet been implemented' + // ); + $this->markTestSkipped('No implementation for Output\Progressbar at the moment'); + } +} + +// __END__ diff --git a/test/phpunit/database/CoreLibsACLLogin_database_create_data.sql b/test/phpunit/database/CoreLibsACLLogin_database_create_data.sql new file mode 100644 index 0000000..af0a5f6 --- /dev/null +++ b/test/phpunit/database/CoreLibsACLLogin_database_create_data.sql @@ -0,0 +1,1031 @@ +-- START: function/random_string.sql +-- create random string with length X + +CREATE FUNCTION random_string(randomLength int) +RETURNS text AS +$$ +SELECT array_to_string( + ARRAY( + SELECT substring( + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', + trunc(random() * 62)::int + 1, + 1 + ) + FROM generate_series(1, randomLength) AS gs(x) + ), + '' +) +$$ +LANGUAGE SQL +RETURNS NULL ON NULL INPUT +VOLATILE; -- LEAKPROOF; +-- END: function/random_string.sql +-- START: function/set_edit_generic.sql +-- adds the created or updated date tags + +CREATE OR REPLACE FUNCTION set_edit_generic() +RETURNS TRIGGER AS +$$ +DECLARE + random_length INT = 12; -- that should be long enough +BEGIN + IF TG_OP = 'INSERT' THEN + NEW.date_created := 'now'; + NEW.cuid := random_string(random_length); + ELSIF TG_OP = 'UPDATE' THEN + NEW.date_updated := 'now'; + END IF; + RETURN NEW; +END; +$$ +LANGUAGE 'plpgsql'; +-- END: function/set_edit_generic.sql +-- START: function/edit_access_set_uid.sql +-- add uid add for edit_access table + +CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS +$$ +DECLARE + myrec RECORD; + v_uid VARCHAR; +BEGIN + -- skip if NEW.name is not set + IF NEW.name IS NOT NULL AND NEW.name <> '' THEN + -- use NEW.name as base, remove all spaces + -- name data is already unique, so we do not need to worry about this here + v_uid := REPLACE(NEW.name, ' ', ''); + IF TG_OP = 'INSERT' THEN + -- always set + NEW.uid := v_uid; + ELSIF TG_OP = 'UPDATE' THEN + -- check if not set, then set + SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id; + IF FOUND THEN + NEW.uid := v_uid; + END IF; + END IF; + END IF; + RETURN NEW; +END; +$$ + LANGUAGE 'plpgsql'; +-- END: function/edit_access_set_uid.sql +-- START: function/edit_group_set_uid.sql +-- add uid add for edit_group table + +CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS +$$ +DECLARE + myrec RECORD; + v_uid VARCHAR; +BEGIN + -- skip if NEW.name is not set + IF NEW.name IS NOT NULL AND NEW.name <> '' THEN + -- use NEW.name as base, remove all spaces + -- name data is already unique, so we do not need to worry about this here + v_uid := REPLACE(NEW.name, ' ', ''); + IF TG_OP = 'INSERT' THEN + -- always set + NEW.uid := v_uid; + ELSIF TG_OP = 'UPDATE' THEN + -- check if not set, then set + SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id; + IF FOUND THEN + NEW.uid := v_uid; + END IF; + END IF; + END IF; + RETURN NEW; +END; +$$ + LANGUAGE 'plpgsql'; +-- END: function/edit_group_set_uid.sql +-- START: function/edit_log_partition_insert.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2018-07-17 +-- DESCRIPTION: +-- partition the edit_log table by year +-- auto creates table if missing, if failure writes to overflow table +-- HISTORY: + +CREATE OR REPLACE FUNCTION edit_log_insert_trigger () +RETURNS TRIGGER AS +$$ +DECLARE + start_date DATE := '2010-01-01'; + end_date DATE; + timeformat TEXT := 'YYYY'; + selector TEXT := 'year'; + base_table TEXT := 'edit_log'; + _interval INTERVAL := '1 ' || selector; + _interval_next INTERVAL := '2 ' || selector; + table_name TEXT; + -- compare date column + compare_date DATE := NEW.event_date; + compare_date_name TEXT := 'event_date'; + -- the create commands + command_create_table TEXT := 'CREATE TABLE IF NOT EXISTS {TABLE_NAME} (CHECK({COMPARE_DATE_NAME} >= {START_DATE} AND {COMPARE_DATE_NAME} < {END_DATE})) INHERITS ({BASE_NAME})'; + command_create_primary_key TEXT := 'ALTER TABLE {TABLE_NAME} ADD PRIMARY KEY ({BASE_TABLE}_id)'; + command_create_foreign_key_1 TEXT := 'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT {TABLE_NAME}_euid_fkey FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL'; + command_create_trigger_1 TEXT = 'CREATE TRIGGER trg_{TABLE_NAME} BEFORE INSERT OR UPDATE ON {TABLE_NAME} FOR EACH ROW EXECUTE PROCEDURE set_edit_generic()'; +BEGIN + -- we are in valid start time area + IF (NEW.event_date >= start_date) THEN + -- current table name + table_name := base_table || '_' || to_char(NEW.event_date, timeformat); + BEGIN + EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW; + -- if insert failed because of missing table, create new below + EXCEPTION + WHEN undefined_table THEN + -- another block, so in case the creation fails here too + BEGIN + -- create new table here + all indexes + start_date := date_trunc(selector, NEW.event_date); + end_date := date_trunc(selector, NEW.event_date + _interval); + -- creat table + EXECUTE format(REPLACE( -- end date + REPLACE( -- start date + REPLACE( -- compare date name + REPLACE( -- base name (inherit) + REPLACE( -- table name + command_create_table, + '{TABLE_NAME}', + table_name + ), + '{BASE_NAME}', + base_table + ), + '{COMPARE_DATE_NAME}', + compare_date_name + ), + '{START_DATE}', + quote_literal(start_date) + ), + '{END_DATE}', + quote_literal(end_date) + )); + -- create all indexes and triggers + EXECUTE format(REPLACE( + REPLACE( + command_create_primary_key, + '{TABLE_NAME}', + table_name + ), + '{BASE_TABLE}', + base_table + )); + -- FK constraints + EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name)); + -- generic trigger + EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name)); + + -- insert try again + EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW; + EXCEPTION + WHEN OTHERS THEN + -- if this faled, throw it into the overflow table (so we don't loose anything) + INSERT INTO edit_log_overflow VALUES (NEW.*); + END; + -- other errors, insert into overlow + WHEN OTHERS THEN + -- if this faled, throw it into the overflow table (so we don't loose anything) + INSERT INTO edit_log_overflow VALUES (NEW.*); + END; + -- main insert run done, check if we have to create next months table + BEGIN + -- check if next month table exists + table_name := base_table || '_' || to_char((SELECT NEW.event_date + _interval)::DATE, timeformat); + -- RAISE NOTICE 'SEARCH NEXT: %', table_name; + IF (SELECT to_regclass(table_name)) IS NULL THEN + -- move inner interval same + start_date := date_trunc(selector, NEW.event_date + _interval); + end_date := date_trunc(selector, NEW.event_date + _interval_next); + -- RAISE NOTICE 'CREATE NEXT: %', table_name; + -- create table + EXECUTE format(REPLACE( -- end date + REPLACE( -- start date + REPLACE( -- compare date name + REPLACE( -- base name (inherit) + REPLACE( -- table name + command_create_table, + '{TABLE_NAME}', + table_name + ), + '{BASE_NAME}', + base_table + ), + '{COMPARE_DATE_NAME}', + compare_date_name + ), + '{START_DATE}', + quote_literal(start_date) + ), + '{END_DATE}', + quote_literal(end_date) + )); + -- create all indexes and triggers + EXECUTE format(REPLACE( + REPLACE( + command_create_primary_key, + '{TABLE_NAME}', + table_name + ), + '{BASE_TABLE}', + base_table + )); + -- FK constraints + EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name)); + -- generic trigger + EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name)); + END IF; + EXCEPTION + WHEN OTHERS THEN + RAISE NOTICE 'Failed to create next table: %', table_name; + END; + ELSE + -- if outside valid date, insert into overflow + INSERT INTO edit_log_overflow VALUES (NEW.*); + END IF; + RETURN NULL; +END +$$ +LANGUAGE 'plpgsql'; +-- END: function/edit_log_partition_insert.sql +-- START: function/edit_user_set_login_user_id_set_date.sql +-- set edit user login_user_id_set_date if login_user_id is set +-- NOW() if not empty + +CREATE OR REPLACE FUNCTION set_login_user_id_set_date() +RETURNS TRIGGER AS +$$ +BEGIN + -- if new is not null/empty + -- and old one is null or old one different new one + -- set NOW() + -- if new one is NULL + -- set NULL + IF + NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND + (OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id) + THEN + NEW.login_user_id_set_date = NOW(); + NEW.login_user_id_last_revalidate = NOW(); + ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN + NEW.login_user_id_set_date = NULL; + NEW.login_user_id_last_revalidate = NULL; + END IF; + RETURN NEW; +END; +$$ +LANGUAGE 'plpgsql'; +-- END: function/edit_user_set_login_user_id_set_date.sql +-- START: table/edit_temp_files.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/08 +-- DESCRIPTION: +-- edit interface temporary files, list of all files in edit (admin) directory +-- TABLE: temp_files +-- HISTORY: + +-- DROP TABLE temp_files; +CREATE TABLE temp_files ( + filename VARCHAR, + folder VARCHAR +); +-- END: table/edit_temp_files.sql +-- START: table/edit_generic.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- edit tables, this is the generic table, inheriteded by most edit tables +-- TABLE: edit_generic +-- HISTORY: + +-- DROP TABLE edit_generic; +CREATE TABLE edit_generic ( + cuid VARCHAR, + date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(), + date_updated TIMESTAMP WITHOUT TIME ZONE +); +-- END: table/edit_generic.sql +-- START: table/edit_visible_group.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- edit tables, postgres SQL statements for the mysql definitions +-- TABLE: edit_visible_group +-- HISTORY + +-- DROP TABLE edit_visible_group; +CREATE TABLE edit_visible_group ( + edit_visible_group_id SERIAL PRIMARY KEY, + name VARCHAR, + flag VARCHAR +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_visible_group.sql +-- START: table/edit_menu_group.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- edit tables, groupings for menu +-- TABLE: edit_menu_group +-- HISTORY + +-- DROP TABLE edit_menu_group; +CREATE TABLE edit_menu_group ( + edit_menu_group_id SERIAL PRIMARY KEY, + name VARCHAR, + flag VARCHAR, + order_number INT NOT NULL +) INHERITS (edit_generic) WITHOUT OIDS; + + +-- END: table/edit_menu_group.sql +-- START: table/edit_page.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- edit tables, this table contains all pages in the edit interface and allocates rights + values to it +-- TABLE: edit_page +-- HISTORY: + +-- DROP TABLE edit_page; +CREATE TABLE edit_page ( + edit_page_id SERIAL PRIMARY KEY, + content_alias_edit_page_id INT, -- alias for page content, if the page content is defined on a different page, ege for ajax backend pages + FOREIGN KEY (content_alias_edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE, + filename VARCHAR, + name VARCHAR UNIQUE, + order_number INT NOT NULL, + online SMALLINT NOT NULL DEFAULT 0, + menu SMALLINT NOT NULL DEFAULT 0, + popup SMALLINT NOT NULL DEFAULT 0, + popup_x SMALLINT, + popup_y SMALLINT, + hostname VARCHAR +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_page.sql +-- START: table/edit_query_string.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- edit tables +-- TABLE: edit_query_string +-- HISTORY: + +-- DROP TABLE edit_query_string; +CREATE TABLE edit_query_string ( + edit_query_string_id SERIAL PRIMARY KEY, + edit_page_id INT NOT NULL, + FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + enabled SMALLINT NOT NULL DEFAULT 0, + name VARCHAR, + value VARCHAR, + dynamic SMALLINT NOT NULL DEFAULT 0 +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_query_string.sql +-- START: table/edit_page_visible_group.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- reference table between visible groups and pages +-- TABLE: edit_page_visible_group +-- HISTORY: + +-- DROP TABLE edit_page_visible_group; +CREATE TABLE edit_page_visible_group ( + edit_page_id INT NOT NULL, + FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_visible_group_id INT NOT NULL, + FOREIGN KEY (edit_visible_group_id) REFERENCES edit_visible_group (edit_visible_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE +); +-- END: table/edit_page_visible_group.sql +-- START: table/edit_page_menu_group.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- reference table between menu groups and pages +-- TABLE: edit_page_menu_group +-- HISTORY: + +-- DROP TABLE edit_page_menu_group; +CREATE TABLE edit_page_menu_group ( + edit_page_id INT NOT NULL, + FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_menu_group_id INT NOT NULL, + FOREIGN KEY (edit_menu_group_id) REFERENCES edit_menu_group (edit_menu_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE +); +-- END: table/edit_page_menu_group.sql +-- START: table/edit_access_right.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- holds all access right levels for the edit interface and other access areas +-- this table is fixed, prefilled and not changable +-- TABLE: edit_access_right +-- HISTORY: + +-- DROP TABLE edit_access_right; +CREATE TABLE edit_access_right ( + edit_access_right_id SERIAL PRIMARY KEY, + name VARCHAR, + level SMALLINT, + type VARCHAR, + UNIQUE (level,type) +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_access_right.sql +-- START: table/edit_scheme.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- holds backend template schemes +-- TABLE: edit_scheme +-- HISTORY: + +-- DROP TABLE edit_scheme; +CREATE TABLE edit_scheme ( + edit_scheme_id SERIAL PRIMARY KEY, + enabled SMALLINT NOT NULL DEFAULT 0, + name VARCHAR, + header_color VARCHAR, + css_file VARCHAR, + template VARCHAR +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_scheme.sql +-- START: table/edit_language.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- languages for the backend, this not used for the encoding, but only for having different language strings +-- the backend encoding is all UTF-8 (not changeable) +-- TABLE: edit_language +-- HISTORY: + +-- DROP TABLE edit_language; +CREATE TABLE edit_language ( + edit_language_id SERIAL PRIMARY KEY, + enabled SMALLINT NOT NULL DEFAULT 0, + lang_default SMALLINT NOT NULL DEFAULT 0, + long_name VARCHAR, + short_name VARCHAR, -- en_US, en or en_US@latin without encoding + iso_name VARCHAR, -- should actually be encoding + order_number INT +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_language.sql +-- START: table/edit_group.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- list of pages the user can access, with a generic access level, one group per user +-- TABLE: edit_group +-- HISTORY: + +-- DROP TABLE edit_group; +CREATE TABLE edit_group ( + edit_group_id SERIAL PRIMARY KEY, + edit_scheme_id INT, + FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_access_right_id INT NOT NULL, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + enabled SMALLINT NOT NULL DEFAULT 0, + deleted SMALLINT DEFAULT 0, + uid VARCHAR, + name VARCHAR, + additional_acl JSONB +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_group.sql +-- START: table/edit_page_access.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- groups pages together to one page group to which the user is then subscribed +-- TABLE: edit_page_access +-- HISTORY: + +-- DROP TABLE edit_page_access; +CREATE TABLE edit_page_access ( + edit_page_access_id SERIAL PRIMARY KEY, + edit_group_id INT NOT NULL, + FOREIGN KEY (edit_group_id) REFERENCES edit_group (edit_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_page_id INT NOT NULL, + FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_access_right_id INT NOT NULL, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + enabled SMALLINT NOT NULL DEFAULT 0 +) INHERITS (edit_generic) WITHOUT OIDS; + + +-- END: table/edit_page_access.sql +-- START: table/edit_page_content.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2019/9/9 +-- DESCRIPTION: +-- sub content to one page with additional edit access right set +-- can be eg JS content groups on one page +-- TABLE: edit_page_content +-- HISTORY: + +-- DROP TABLE edit_page_content; +CREATE TABLE edit_page_content ( + edit_page_content_id SERIAL PRIMARY KEY, + edit_page_id INT NOT NULL, + FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_access_right_id INT NOT NULL, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + uid VARCHAR UNIQUE, + name VARCHAR, + order_number INT NOT NULL, + online SMALLINT NOT NULL DEFAULT 0 +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_page_content.sql +-- START: table/edit_user.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/06 +-- DESCRIPTION: +-- holds the user that can login + group, scheme, lang and a default access right +-- TABLE: edit_user +-- HISTORY: + +-- DROP TABLE edit_user; +CREATE TABLE edit_user ( + edit_user_id SERIAL PRIMARY KEY, + connect_edit_user_id INT, -- possible reference to other user + FOREIGN KEY (connect_edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_language_id INT NOT NULL, + FOREIGN KEY (edit_language_id) REFERENCES edit_language (edit_language_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_group_id INT NOT NULL, + FOREIGN KEY (edit_group_id) REFERENCES edit_group (edit_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_scheme_id INT, + FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_access_right_id INT NOT NULL, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + -- username/password + username VARCHAR UNIQUE, + password VARCHAR, + -- name block + first_name VARCHAR, + last_name VARCHAR, + first_name_furigana VARCHAR, + last_name_furigana VARCHAR, + -- email + email VARCHAR, + -- eanbled/deleted flag + enabled SMALLINT NOT NULL DEFAULT 0, + deleted SMALLINT NOT NULL DEFAULT 0, + -- general flags + strict SMALLINT DEFAULT 0, + locked SMALLINT DEFAULT 0, + protected SMALLINT NOT NULL DEFAULT 0, + -- legacy, debug flags + debug SMALLINT NOT NULL DEFAULT 0, + db_debug SMALLINT NOT NULL DEFAULT 0, + -- is admin user + admin SMALLINT NOT NULL DEFAULT 0, + -- last login log + last_login TIMESTAMP WITHOUT TIME ZONE, + -- login error + login_error_count INT DEFAULT 0, + login_error_date_last TIMESTAMP WITHOUT TIME ZONE, + login_error_date_first TIMESTAMP WITHOUT TIME ZONE, + -- time locked + lock_until TIMESTAMP WITHOUT TIME ZONE, + lock_after TIMESTAMP WITHOUT TIME ZONE, + -- password change + password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed + password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval + password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested + password_reset_uid VARCHAR, -- the uid to access the password reset page + -- _GET login id for direct login + login_user_id VARCHAR UNIQUE, -- the loginUserId, at least 32 chars + login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE, -- when above uid was set + login_user_id_last_revalidate TIMESTAMP WITHOUT TIME ZONE, -- when the last login was done with user name and password + login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE, -- if set, from when the above uid is valid + login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE, -- if set, until when the above uid is valid + login_user_id_revalidate_after INTERVAL, -- user must login to revalidated loginUserId after set days, 0 for forever + login_user_id_locked SMALLINT DEFAULT 0, -- lock for loginUserId, but still allow normal login + -- additional ACL json block + additional_acl JSONB -- additional ACL as JSON string (can be set by other pages) +) INHERITS (edit_generic) WITHOUT OIDS; + +-- create unique index +-- CREATE UNIQUE INDEX edit_user_login_user_id_key ON edit_user (login_user_id) WHERE login_user_id IS NOT NULL; + +COMMENT ON COLUMN edit_user.username IS 'Login username, must set'; +COMMENT ON COLUMN edit_user.password IS 'Login password, must set'; +COMMENT ON COLUMN edit_user.enabled IS 'Login is enabled (master switch)'; +COMMENT ON COLUMN edit_user.deleted IS 'Login is deleted (master switch), overrides all other'; +COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be locked, default off'; +COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins'; +COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user'; +COMMENT ON COLUMN edit_user.debug IS 'Turn debug flag on (legacy)'; +COMMENT ON COLUMN edit_user.db_debug IS 'Turn DB debug flag on (legacy)'; +COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin'; +COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp'; +COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login'; +COMMENT ON COLUMN edit_user.login_error_date_last IS 'Last login error date'; +COMMENT ON COLUMN edit_user.login_error_date_first IS 'First login error date, reset on successfull login'; +COMMENT ON COLUMN edit_user.lock_until IS 'Account is locked until this date, <'; +COMMENT ON COLUMN edit_user.lock_after IS 'Account is locked after this date, >'; +COMMENT ON COLUMN edit_user.password_change_date IS 'Password was changed on'; +COMMENT ON COLUMN edit_user.password_change_interval IS 'After how many days the password has to be changed'; +COMMENT ON COLUMN edit_user.password_reset_time IS 'When the password reset was requested. For reset page uid valid check'; +COMMENT ON COLUMN edit_user.password_reset_uid IS 'Password reset page uid, one time, invalid after reset successful or time out'; +COMMENT ON COLUMN edit_user.login_user_id IS 'Min 32 character UID to be used to login without password. Via GET/POST parameter'; +COMMENT ON COLUMN edit_user.login_user_id_set_date IS 'loginUserId was set at what date'; +COMMENT ON COLUMN edit_user.login_user_id_last_revalidate IS 'set when username/password login is done and loginUserId is set'; +COMMENT ON COLUMN edit_user.login_user_id_valid_from IS 'loginUserId is valid from this date, >='; +COMMENT ON COLUMN edit_user.login_user_id_valid_until IS 'loginUserId is valid until this date, <='; +COMMENT ON COLUMN edit_user.login_user_id_revalidate_after IS 'If set to a number greater 0 then user must login after given amount of days to revalidate the loginUserId, set to 0 for valid forver'; +COMMENT ON COLUMN edit_user.login_user_id_locked IS 'A separte lock flag for loginUserId, user can still login normal'; +COMMENT ON COLUMN edit_user.additional_acl IS 'Additional Access Control List stored in JSON format'; +-- END: table/edit_user.sql +-- START: table/edit_log.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- log data for backend interface, logs all user activities +-- TABLE: edit_log +-- HISTORY: + +-- DROP TABLE edit_log; +CREATE TABLE edit_log ( + edit_log_id SERIAL PRIMARY KEY, + euid INT, -- this is a foreign key, but I don't nedd to reference to it + FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, + username VARCHAR, + password VARCHAR, + event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, + ip VARCHAR, + error TEXT, + event TEXT, + data_binary BYTEA, + data TEXT, + page VARCHAR, + action VARCHAR, + action_id VARCHAR, + action_yes VARCHAR, + action_flag VARCHAR, + action_menu VARCHAR, + action_loaded VARCHAR, + action_value VARCHAR, + action_type VARCHAR, + action_error VARCHAR, + user_agent VARCHAR, + referer VARCHAR, + script_name VARCHAR, + query_string VARCHAR, + server_name VARCHAR, + http_host VARCHAR, + http_accept VARCHAR, + http_accept_charset VARCHAR, + http_accept_encoding VARCHAR, + session_id VARCHAR +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_log.sql +-- START: table/edit_log_overflow.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2020/1/28 +-- DESCRIPTION: +-- edit log overflow table +-- this is the overflow table for partition +-- TABLE: edit_log_overflow +-- HISTORY: + +-- DROP TABLE edit_log_overflow; +CREATE TABLE IF NOT EXISTS edit_log_overflow () INHERITS (edit_log); +ALTER TABLE edit_log_overflow ADD PRIMARY KEY (edit_log_id); +ALTER TABLE edit_log_overflow ADD CONSTRAINT edit_log_overflow_euid_fkey FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL; +-- END: table/edit_log_overflow.sql +-- START: table/edit_access.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- is a "group" for the outside, a user can have serveral groups with different rights so he can access several parts from the outside +-- TABLE: edit_access +-- HISTORY: + +-- DROP TABLE edit_access; +CREATE TABLE edit_access ( + edit_access_id SERIAL PRIMARY KEY, + enabled SMALLINT NOT NULL DEFAULT 0, + protected SMALLINT DEFAULT 0, + deleted SMALLINT DEFAULT 0, + uid VARCHAR, + name VARCHAR UNIQUE, + description VARCHAR, + color VARCHAR, + additional_acl JSONB +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_access.sql +-- START: table/edit_access_user.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2005/07/05 +-- DESCRIPTION: +-- groupings which user has rights to which access groups (incl ACL) +-- TABLE: edit_access_user +-- HISTORY: + +-- DROP TABLE edit_access_user; +CREATE TABLE edit_access_user ( + edit_access_user_id SERIAL PRIMARY KEY, + edit_access_id INT NOT NULL, + FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_user_id INT NOT NULL, + FOREIGN KEY (edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_access_right_id INT NOT NULL, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_default SMALLINT DEFAULT 0, + enabled SMALLINT NOT NULL DEFAULT 0 +) INHERITS (edit_generic) WITHOUT OIDS; +-- END: table/edit_access_user.sql +-- START: table/edit_access_data.sql +-- AUTHOR: Clemens Schwaighofer +-- DATE: 2016/7/15 +-- DESCRIPTION: +-- sub table to edit access, holds additional data for access group +-- TABLE: edit_access_data +-- HISTORY: + +-- DROP TABLE edit_access_data; +CREATE TABLE edit_access_data ( + edit_access_data_id SERIAL PRIMARY KEY, + edit_access_id INT NOT NULL, + FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + enabled SMALLINT NOT NULL DEFAULT 0, + name VARCHAR, + value VARCHAR +) INHERITS (edit_generic) WITHOUT OIDS; + +-- create a unique index for each attached data block for each edit access can +-- only have ONE value; +CREATE UNIQUE INDEX edit_access_data_edit_access_id_name_ukey ON edit_access_data (edit_access_id, name); +-- END: table/edit_access_data.sql +-- START: trigger/trg_edit_access_right.sql +-- DROP TRIGGER IF EXISTS trg_edit_access_right ON edit_access_right; +CREATE TRIGGER trg_edit_access_right +BEFORE INSERT OR UPDATE ON edit_access_right +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_access_right.sql +-- START: trigger/trg_edit_access.sql +-- DROP TRIGGER IF EXISTS trg_edit_access ON edit_access; +CREATE TRIGGER trg_edit_access +BEFORE INSERT OR UPDATE ON edit_access +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); + +-- DROP TRIGGER IF EXISTS trg_set_edit_access_uid ON edit_access; +CREATE TRIGGER trg_set_edit_access_uid +BEFORE INSERT OR UPDATE ON edit_access +FOR EACH ROW EXECUTE PROCEDURE set_edit_access_uid(); +-- END: trigger/trg_edit_access.sql +-- START: trigger/trg_edit_access_data.sql +-- DROP TRIGGER IF EXISTS trg_edit_access_data ON edit_access_data; +CREATE TRIGGER trg_edit_access_data +BEFORE INSERT OR UPDATE ON edit_access_data +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_access_data.sql +-- START: trigger/trg_edit_access_user.sql +-- DROP TRIGGER IF EXISTS trg_edit_access_user ON edit_access_user; +CREATE TRIGGER trg_edit_access_user +BEFORE INSERT OR UPDATE ON edit_access_user +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_access_user.sql +-- START: trigger/trg_edit_group.sql +-- DROP TRIGGER IF EXISTS trg_edit_group ON edit_group; +CREATE TRIGGER trg_edit_group +BEFORE INSERT OR UPDATE ON edit_group +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); + +-- DROP TRIGGER IF EXISTS trg_set_edit_group_uid ON edit_group; +CREATE TRIGGER trg_set_edit_group_uid +BEFORE INSERT OR UPDATE ON edit_group +FOR EACH ROW EXECUTE PROCEDURE set_edit_group_uid(); +-- END: trigger/trg_edit_group.sql +-- START: trigger/trg_edit_language.sql +-- DROP TRIGGER IF EXISTS trg_edit_language ON edit_language; +CREATE TRIGGER trg_edit_language +BEFORE INSERT OR UPDATE ON edit_language +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_language.sql +-- START: trigger/trg_edit_log_overflow.sql +-- DROP TRIGGER IF EXISTS trg_edit_log_overflow ON edit_log_overflow; +CREATE TRIGGER trg_edit_log_overflow +BEFORE INSERT OR UPDATE ON edit_log_overflow +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_log_overflow.sql +-- START: trigger/trg_edit_log.sql +-- DROP TRIGGER IF EXISTS trg_edit_log ON edit_log; +CREATE TRIGGER trg_edit_log +BEFORE INSERT OR UPDATE ON edit_log +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); + +-- DROP TRIGGER IF EXISTS trg_edit_log_insert_partition ON edit_log; +CREATE TRIGGER trg_edit_log_insert_partition +BEFORE INSERT OR UPDATE ON edit_log +FOR EACH ROW EXECUTE PROCEDURE edit_log_insert_trigger(); +-- END: trigger/trg_edit_log.sql +-- START: trigger/trg_edit_page_access.sql +-- DROP TRIGGER IF EXISTS trg_edit_page_access ON edit_page_access; +CREATE TRIGGER trg_edit_page_access +BEFORE INSERT OR UPDATE ON edit_page_access +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_page_access.sql +-- START: trigger/trg_edit_page_content.sql +-- DROP TRIGGER IF EXISTS trg_edit_page_content ON edit_page_content; +CREATE TRIGGER trg_edit_page_content +BEFORE INSERT OR UPDATE ON edit_page_content +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_page_content.sql +-- START: trigger/trg_edit_page.sql +-- DROP TRIGGER IF EXISTS trg_edit_page ON edit_page; +CREATE TRIGGER trg_edit_page +BEFORE INSERT OR UPDATE ON edit_page +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_page.sql +-- START: trigger/trg_edit_query_string.sql +-- DROP TRIGGER IF EXISTS trg_edit_query_string ON edit_query_string; +CREATE TRIGGER trg_edit_query_string +BEFORE INSERT OR UPDATE ON edit_query_string +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_query_string.sql +-- START: trigger/trg_edit_scheme.sql +-- DROP TRIGGER IF EXISTS trg_edit_scheme ON edit_scheme; +CREATE TRIGGER trg_edit_scheme +BEFORE INSERT OR UPDATE ON edit_scheme +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_scheme.sql +-- START: trigger/trg_edit_user.sql +-- DROP TRIGGER IF EXISTS trg_edit_user ON edit_user; +CREATE TRIGGER trg_edit_user +BEFORE INSERT OR UPDATE ON edit_user +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); + +-- DROP TRIGGER IF EXISTS trg_edit_user_set_login_user_id_set_date ON edit_user; +CREATE TRIGGER trg_edit_user_set_login_user_id_set_date +BEFORE INSERT OR UPDATE ON edit_user +FOR EACH ROW EXECUTE PROCEDURE set_login_user_id_set_date(); +-- END: trigger/trg_edit_user.sql +-- START: trigger/trg_edit_visible_group.sql +-- DROP TRIGGER IF EXISTS trg_edit_visible_group ON edit_visible_group; +CREATE TRIGGER trg_edit_visible_group +BEFORE INSERT OR UPDATE ON edit_visible_group +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_visible_group.sql +-- START: trigger/trg_edit_menu_group.sql +-- DROP TRIGGER IF EXISTS trg_edit_menu_group ON edit_menu_group; +CREATE TRIGGER trg_edit_menu_group +BEFORE INSERT OR UPDATE ON edit_menu_group +FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); +-- END: trigger/trg_edit_menu_group.sql +-- START: data/edit_tables.sql +-- edit tables insert data in order + +-- edit visible group +DELETE FROM edit_visible_group; +INSERT INTO edit_visible_group (name, flag) VALUES ('Main Menu', 'main'); +INSERT INTO edit_visible_group (name, flag) VALUES ('Data popup Menu', 'datapopup'); + +-- edit menu group +DELETE FROM edit_menu_group; +INSERT INTO edit_menu_group (name, flag, order_number) VALUES ('Admin Menu', 'admin', 1); +INSERT INTO edit_menu_group (name, flag, order_number) VALUES ('Admin Data Popup Menu', 'AdminDataPopup', 2); + +-- edit page +DELETE FROM edit_page; +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_pages.php', 'Edit Pages', 1, 1, 1); +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_users.php', 'Edit Users', 2, 1, 1); +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_languages.php', 'Edit Languages', 3, 1, 1); +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_schemes.php', 'Edit Schemes', 4, 1, 1); +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_groups.php', 'Edit Groups', 5, 1, 1); +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_visible_group.php', 'Edit Visible Groups', 6, 1, 1); +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_menu_group.php', 'Edit Menu Groups', 7, 1, 1); +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_access.php', 'Edit Access', 8, 1, 1); +INSERT INTO edit_page (filename, name, order_number, online, menu) VALUES ('edit_order.php', 'Edit Order', 9, 1, 0); + +-- edit visible group +DELETE FROM edit_page_visible_group; +INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Pages'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); +INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Users'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); +INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Languages'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); +INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Schemes'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); +INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Groups'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); +INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Visible Groups'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); +INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Menu Groups'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); +INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Access'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); +-- INSERT INTO edit_page_visible_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Order'), (SELECT edit_visible_group_id FROM edit_visible_group WHERE flag = 'main')); + +-- edit page menu group +DELETE FROM edit_page_menu_group; +INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Pages'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); +INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Users'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); +INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Languages'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); +INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Schemes'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); +INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Groups'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); +INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Visible Groups'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); +INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Menu Groups'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); +INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Access'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); +-- INSERT INTO edit_page_menu_group VALUES ((SELECT edit_page_id FROM edit_page WHERE name = 'Edit Order'), (SELECT edit_menu_group_id FROM edit_menu_group WHERE flag = 'admin')); + + +-- edit access right +DELETE FROM edit_access_right; +INSERT INTO edit_access_right (name, level, type) VALUES ('Default', -1, 'default'); +INSERT INTO edit_access_right (name, level, type) VALUES ('No Access', 0, 'none'); +INSERT INTO edit_access_right (name, level, type) VALUES ('List', 10, 'list'); +INSERT INTO edit_access_right (name, level, type) VALUES ('Read', 20, 'read'); +INSERT INTO edit_access_right (name, level, type) VALUES ('Translator', 30, 'mod_trans'); +INSERT INTO edit_access_right (name, level, type) VALUES ('Modify', 40, 'mod'); +INSERT INTO edit_access_right (name, level, type) VALUES ('Create/Write', 60, 'write'); +INSERT INTO edit_access_right (name, level, type) VALUES ('Delete', 80, 'del'); +INSERT INTO edit_access_right (name, level, type) VALUES ('Site Admin', 90, 'siteadmin'); +INSERT INTO edit_access_right (name, level, type) VALUES ('Admin', 100, 'admin'); + +-- edit scheme +DELETE FROM edit_scheme; +INSERT INTO edit_scheme (name, header_color, enabled) VALUES ('Default Scheme', 'E0E2FF', 1); +INSERT INTO edit_scheme (name, header_color, enabled) VALUES ('Admin', 'CC7E7E', 1); +INSERT INTO edit_scheme (name, header_color, enabled) VALUES ('Visitor', 'B0C4B3', 1); +INSERT INTO edit_scheme (name, header_color, enabled) VALUES ('User', '1E789E', 1); + +-- edit language +-- short_name = locale without encoding +-- iso_name = encoding +DELETE FROM edit_language; +INSERT INTO edit_language (long_name, short_name, iso_name, order_number, enabled, lang_default) VALUES ('English', 'en_US', 'UTF-8', 1, 1, 1); +INSERT INTO edit_language (long_name, short_name, iso_name, order_number, enabled, lang_default) VALUES ('Japanese', 'ja_JP', 'UTF-8', 2, 1, 0); + +-- edit group +DELETE FROM edit_group; +INSERT INTO edit_group (name, enabled, edit_scheme_id, edit_access_right_id) VALUES ('Admin', 1, (SELECT edit_scheme_id FROM edit_scheme WHERE name = 'Admin'), (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin')); +INSERT INTO edit_group (name, enabled, edit_scheme_id, edit_access_right_id) VALUES ('User', 1, (SELECT edit_scheme_id FROM edit_scheme WHERE name = 'User'), (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'write')); + +-- edit page access +DELETE FROM edit_page_access; +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Pages'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Users'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Languages'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Schemes'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Groups'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Visible Groups'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Menu Groups'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Access'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_right_id) VALUES (1, + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_page_id FROM edit_page WHERE name = 'Edit Order'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); + +-- edit user +-- inserts admin user so basic users can be created +DELETE FROM edit_user; +INSERT INTO edit_user (username, password, enabled, debug, db_debug, email, protected, admin, edit_language_id, edit_group_id, edit_scheme_id, edit_access_right_id) VALUES ('admin', 'admin', 1, 1, 1, '', 1, 1, + (SELECT edit_language_id FROM edit_language WHERE short_name = 'en_US'), + (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), + (SELECT edit_scheme_id FROM edit_scheme WHERE name = 'Admin'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); + +-- edit access +DELETE FROM edit_access; +INSERT INTO edit_access (name, enabled, protected) VALUES ('Admin Access', 1, 1); + +-- edit access user +DELETE FROM edit_access_user; +INSERT INTO edit_access_user (edit_default, enabled, edit_access_id, edit_user_id, edit_access_right_id) VALUES (1, 1, + (SELECT edit_access_id FROM edit_access WHERE uid = 'AdminAccess'), + (SELECT edit_user_id FROM edit_user WHERE username = 'admin'), + (SELECT edit_access_right_id FROM edit_access_right WHERE type = 'admin') +); +-- END: data/edit_tables.sql diff --git a/test/phpunit/dotenv/cannot_read.env b/test/phpunit/dotenv/cannot_read.env new file mode 100644 index 0000000..e69de29 diff --git a/test/phpunit/dotenv/empty.env b/test/phpunit/dotenv/empty.env new file mode 100644 index 0000000..e69de29 diff --git a/test/phpunit/dotenv/test.env b/test/phpunit/dotenv/test.env new file mode 100644 index 0000000..46d3456 --- /dev/null +++ b/test/phpunit/dotenv/test.env @@ -0,0 +1,49 @@ +# enviroment file +SOMETHING=A +OTHER="B IS B" +Complex="A B \"D is F" +# COMMENT +HAS_SPACE= "ABC"; +# COMMENT AT END +HAS_COMMENT_QUOTES_SPACE="Comment at end with quotes and space" # Comment QE +HAS_COMMENT_QUOTES_NO_SPACE="Comment at end with quotes no space"# Comment QES +HAS_COMMENT_NO_QUOTES_SPACE=Comment at end no quotes and space # Comment NQE +HAS_COMMENT_NO_QUOTES_NO_SPACE=Comment at end no quotes no space# Comment NQES +COMMENT_IN_TEXT_QUOTES="Foo bar # comment in here" +FAILURE = ABC +SIMPLEBOX= A B C +TITLE=1 +FOO=1.2 +SOME.TEST=Test Var +SOME.LIVE=Live Var +# VAR TESTS - +A_TEST1 = foo +A_TEST2 = ${TEST1:-bar} # TEST1 is set so the value of TEST2 = foo +A_TEST3 = ${TEST4:-bar} # TEST4 is not set so the value of TEST3 = bar +A_TEST5 = null +A_TEST6 = ${TEST5-bar} # TEST5 is set but empty so the value of TEST6 = null +A_TEST7 = ${TEST6:-bar} # TEST5 is set and empty so the value of TEST7 = bar +# VAR TESTS = +B_TEST1 = foo +B_TEST2 = ${TEST1:=bar} # TEST1 is set so the value of TEST2 = foo +B_TEST3 = ${TEST4:=bar} # TEST4 is not set so the value of TEST3 = bar and TEST4 = bar +B_TEST5 = null +B_TEST6 = ${TEST5=bar} # TEST5 is set but emtpy so the value of TEST6 = null +B_TEST7 = ${TEST6=bar} # TEST5 is set and empty so the value of TEST7 = bar and TEST5 = bar +# VAR TEST END +Test="A" +TEST="B" +TEST="D" +LINE="ABC +DEF" +OTHERLINE="ABC +AF\"ASFASDF +MORESHIT" +SUPERLINE= +"asfasfasf" + __FOO_BAR_1 = b + __FOOFOO = f +123123=number +EMPTY= += flase +asfasdf diff --git a/test/phpunit/includes/create_po.sh b/test/phpunit/includes/create_po.sh new file mode 100755 index 0000000..0162152 --- /dev/null +++ b/test/phpunit/includes/create_po.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# if we don't have one base file we are in the wrong folder +if [ ! -f "locale/en_US/LC_MESSAGES/admin.mo" ]; then + echo "Locale file is missing, wrong base folder?" + echo "Should be: 4dev/tests/includes/" + exit; +fi; + +for file in $(ls -1 locale/*.po); do + echo $file; + file=$(basename $file .po); + locale=$(echo "${file}" | cut -d "-" -f 1); + domain=$(echo "${file}" | cut -d "-" -f 2); + msgfmt -o locale/${locale}/LC_MESSAGES/${domain}.mo locale/${locale}-${domain}.po; +done; diff --git a/test/phpunit/includes/locale/en_US-admin.po b/test/phpunit/includes/locale/en_US-admin.po new file mode 100644 index 0000000..c35caf2 --- /dev/null +++ b/test/phpunit/includes/locale/en_US-admin.po @@ -0,0 +1,34 @@ + +msgid "" +msgstr "" +"Project-Id-Version: en_US.UTF-8 LC_MESSAGES admin\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgid "Original" +msgstr "Translated admin en_US" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi admin en_US 0" +msgstr[1] "Multi admin en_US 1" +msgstr[2] "Multi admin en_US 2" + +msgctxt "context" +msgid "Original" +msgstr "Original context admin en_US" + +msgctxt "context" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi context admin en_US 0" +msgstr[1] "Multi context admin en_US 1" +msgstr[2] "Multi context admin en_US 2" diff --git a/test/phpunit/includes/locale/en_US-frontend.po b/test/phpunit/includes/locale/en_US-frontend.po new file mode 100644 index 0000000..a2f58ed --- /dev/null +++ b/test/phpunit/includes/locale/en_US-frontend.po @@ -0,0 +1,34 @@ + +msgid "" +msgstr "" +"Project-Id-Version: en_US.UTF-8 LC_MESSAGES frontend\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgid "Original" +msgstr "Translated frontend en_US" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi frontend en_US 0" +msgstr[1] "Multi frontend en_US 1" +msgstr[2] "Multi frontend en_US 2" + +msgctxt "context" +msgid "Original" +msgstr "Original context frontend en_US" + +msgctxt "context" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi context frontend en_US 0" +msgstr[1] "Multi context frontend en_US 1" +msgstr[2] "Multi context frontend en_US 2" diff --git a/test/phpunit/includes/locale/en_US/LC_MESSAGES/admin.mo b/test/phpunit/includes/locale/en_US/LC_MESSAGES/admin.mo new file mode 100644 index 0000000000000000000000000000000000000000..c681b0c4234124df6b37b71d66b5b1032368b096 GIT binary patch literal 807 zcmaix&5qMB5XTJ^fg%nFAx<33dF8O}X_Zn$HqGt^ZJMe{k>Ij2i8uAiu_N2r0#5)} zB*cjmFTo4&8oU56!KCSSx8iH0pT^^{$K(I}xYzrHz-|H$fiZ9k5I}41f!n|b;4bhH zxDR{8YeBgj z;(S51VRp3A?DAau3DALrGFJtRz{7j5xpk(Z(NvHFr7PoM2^v9YMuQ>lmLrKDjc|YH ypbHsT(TU%UW;1ZS9{5%=ZioD*4Bkb5&-MS28?1Ay`FAaBYF*vCsBv{~fPMk|*6&FG literal 0 HcmV?d00001 diff --git a/test/phpunit/includes/locale/en_US/LC_MESSAGES/frontend.mo b/test/phpunit/includes/locale/en_US/LC_MESSAGES/frontend.mo new file mode 100644 index 0000000000000000000000000000000000000000..b63ffe7415f1b1aab06c59653c5535ebe0f64fb2 GIT binary patch literal 834 zcmaiy-;2~R5XYmgD8&a6Uj)J2v!vec)6sRs+U?y{uG?kX6%^r!ZMK^uO-hp9>YI=H zD)=t=Pxue`Z}^`GPWRVwE1rQ5lbJ~;)9>Whz0Owz>n89J7y`Ed0krEAa2xmx+y%Y> z_kr)g9pDG>?HWQq!9D#>;R{(dv}7b>wDNFBjwb&2I0{l6 zByl_)MS-71$tcAyX-y@5%H>SCXZ5A_o^7kFC2-;>4liWsy8BMBQ~;X$P($~v*iULf zx$NV7PPJinu$1lMT>BHC0|{lW3Ydb258iR>j76iVAcsm<#={ac0@nI@(*ztPoyZN}C_CJz%A6=R5UY+i()0-Eu>S$YZC%h@P6YiluPb~YF literal 0 HcmV?d00001 diff --git a/test/phpunit/includes/locale/ja_JP-admin.po b/test/phpunit/includes/locale/ja_JP-admin.po new file mode 100644 index 0000000..e2a8795 --- /dev/null +++ b/test/phpunit/includes/locale/ja_JP-admin.po @@ -0,0 +1,35 @@ + +msgid "" +msgstr "" +"Project-Id-Version: ja_JP.UTF-8 LC_MESSAGES admin\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n" +# 0, 1, 2 plural + +msgid "Original" +msgstr "Translated admin ja_JP" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi admin ja_JP 0" +msgstr[1] "Multi admin ja_JP 1" +msgstr[2] "Multi admin ja_JP 2" + +msgctxt "context" +msgid "Original" +msgstr "Original context admin ja_JP" + +msgctxt "context" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi context admin ja_JP 0" +msgstr[1] "Multi context admin ja_JP 1" +msgstr[2] "Multi context admin ja_JP 2" diff --git a/test/phpunit/includes/locale/ja_JP-frontend.po b/test/phpunit/includes/locale/ja_JP-frontend.po new file mode 100644 index 0000000..ccdba7a --- /dev/null +++ b/test/phpunit/includes/locale/ja_JP-frontend.po @@ -0,0 +1,35 @@ + +msgid "" +msgstr "" +"Project-Id-Version: ja_JP.UTF-8 LC_MESSAGES frontend\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n" +# 0, 1, 2 plural + +msgid "Original" +msgstr "Translated frontend ja_JP" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi frontend ja_JP 0" +msgstr[1] "Multi frontend ja_JP 1" +msgstr[2] "Multi frontend ja_JP 2" + +msgctxt "context" +msgid "Original" +msgstr "Original context frontend ja_JP" + +msgctxt "context" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi context frontend ja_JP 0" +msgstr[1] "Multi context frontend ja_JP 1" +msgstr[2] "Multi context frontend ja_JP 2" diff --git a/test/phpunit/includes/locale/ja_JP/LC_MESSAGES/admin.mo b/test/phpunit/includes/locale/ja_JP/LC_MESSAGES/admin.mo new file mode 100644 index 0000000000000000000000000000000000000000..df8d579c47826434472d5bcc25e26560b28d0014 GIT binary patch literal 839 zcmaiy&2G~`5P%m50vQenAx<2o2YNvp>~N_ZOzS3Y>#B~UI7Na>wb)x{BYW5KIt0#K z;EKeBD-Qt|j=ToX!q{%pM*NNRd3I*jv$M1F^KR!8LAj1RK!(T-BtvR>kK9B)Aa{_D z$UWo>avS-Ee7{1-57hUOcURjypHW9>e?{F#ed`(_xUCU%HRqwLkxu;$OOXo6G7>9U z@i$fHH-{E7%{a*yStZDtF)r1d#})Mx`ka?qC~3o-og9w4$J0H!1xJIEz>6Yx-;01H zxsb+$7pkmipi`01-9@VDRM`+`Jm*q(bv#=#ku z&5w(W3E78u#!AgAr)k*5zWN5(1nS9HCD?x(wqA+K7-x%;Wpq!Kxwb)KgV5B@lRmUd zM?SPn$FX2@6Y8b8;aC=Ig9*4BHXK}RgM~(~Z;B1;!wq;tHs_l?KNRvt3)KzLnc1qz7 zAb3UM9f?1|BQN|7ehcSz?X)7sNng&sbI#|x`+cYOm7rWl?jvpF22vnaJ|Q=e&&X}$ z3vw6vj@&|iAV04V@(cAn==RRr||egv<+0rn90F?bVrJ%ZH&aumo6;VC zC+sb>%_{E0tEknZNlM!-I86N{e*8M^M9wiY6&+|MO~ERq{~sFvkok<|oDMjf`Vi5B zUaNZ;hkXdsBsuBCVQUblojx3~l1bPVa-`fzc_F=L>nOGaP7){4c}+aG?u3iyV`d*p zOx;%cNm(!m ehZE}LQnz=xyK&}TP0TW*b