From e3d30763dabdf12f961ed3d1d7cf4eaee44c65a9 Mon Sep 17 00:00:00 2001 From: Harald Eilertsen Date: Sat, 6 Jan 2024 16:34:38 +0000 Subject: tests: Integrate the DB in "unit" tests. --- .gitlab-ci.yml | 165 +++++++++++---------------- Zotlabs/Web/HTTPSig.php | 5 +- tests/create_test_db_pgsql.sh | 63 ++++++++++ tests/phpunit.xml | 49 ++++---- tests/unit/GetTagsTest.php | 5 +- tests/unit/Lib/PermissionDescriptionTest.php | 47 +++++++- tests/unit/UnitTestCase.php | 95 ++++++++++++++- tests/unit/Web/HttpSigTest.php | 7 +- tests/unit/includes/AccountTest.php | 12 ++ tests/unit/includes/PhotodriverTest.php | 28 +---- 10 files changed, 317 insertions(+), 159 deletions(-) create mode 100755 tests/create_test_db_pgsql.sh create mode 100644 tests/unit/includes/AccountTest.php diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1354a5de7..9825c1e8a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,10 +1,3 @@ -# Select image from https://hub.docker.com/_/php/ -#image: php:7.3 -# Use a prepared Hubzilla image to optimise pipeline duration -# image: registry.gitlab.com/dawnbreak/hubzilla/core:php7.3 - -image: php:8.1 - stages: - test - deploy @@ -33,58 +26,64 @@ variables: before_script: -# pecl and composer do not work with PHP production restrictions (from Hubzilla Docker image) -- if [ -f /usr/local/etc/php/conf.d/z_prod.ini ]; then mv /usr/local/etc/php/conf.d/z_prod.ini /usr/local/etc/php/conf.d/z_prod.ini.off; fi -# Install & enable Xdebug for code coverage reports -- pecl install xdebug -- apt-get update -- apt-get install zip unzip libjpeg-dev libpng-dev -yqq -- docker-php-ext-enable xdebug -- docker-php-ext-install gd - -# Install composer -- curl -sS https://getcomposer.org/installer | php -# Install dev libraries from composer -- php ./composer.phar install --no-progress -# php.ini settings -- echo 'xdebug.mode=coverage' >> /usr/local/etc/php/php.ini - -# hidden job definition with template for PHP -.job_template_php: &job_definition_php + # Install & enable Xdebug for code coverage reports + - pecl install xdebug + - apt-get update + - apt-get install -yqq libjpeg-dev libpng-dev libpq-dev libzip-dev mariadb-client postgresql-client unzip zip + - docker-php-ext-enable xdebug + - docker-php-ext-install gd pdo_mysql pdo_pgsql zip + + # Install composer + - curl -sS https://getcomposer.org/installer | php + # Install dev libraries from composer + - php ./composer.phar install --no-progress + # php.ini settings + - echo 'xdebug.mode=coverage' >> /usr/local/etc/php/php.ini + +# hidden job definition with template for MySQL/MariaDB +.job_template_mysql: &job_definition_mysql stage: test + variables: + HZ_TEST_DB_HOST: mysql + HZ_TEST_DB_TYPE: mysql + HZ_TEST_DB_USER: root + HZ_TEST_DB_PASS: $MYSQL_ROOT_PASSWORD + HZ_TEST_DB_DATABASE: $MYSQL_DATABASE script: - - vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text - + - echo "USE $MYSQL_DATABASE; $(cat ./install/schema_mysql.sql)" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql "$MYSQL_DATABASE" + - echo "SHOW DATABASES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql "$MYSQL_DATABASE" + - echo "USE $MYSQL_DATABASE; SHOW TABLES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql "$MYSQL_DATABASE" + - touch dbfail.out + - vendor/bin/phpunit --configuration tests/phpunit.xml --verbose --stop-on-error --coverage-text --colors=never --log-junit tests/results/junit.xml + coverage: '/^\s*Lines:\s*\d+.\d+\%/' -# hidden job definition with template for MySQL/MariaDB -#.job_template_mysql: &job_definition_mysql -# stage: test -# script: -# - echo "USE $MYSQL_DATABASE; $(cat ./install/schema_mysql.sql)" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql "$MYSQL_DATABASE" -# - echo "SHOW DATABASES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql "$MYSQL_DATABASE" -# - echo "USE $MYSQL_DATABASE; SHOW TABLES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql "$MYSQL_DATABASE" -# - vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text # hidden job definition with template for PostgreSQL -#.job_template_postgres: &job_definition_postgres -# stage: test -# services: -# - postgres:latest -# script: -# - export PGPASSWORD=$POSTGRES_PASSWORD -# - psql --version -# - psql -h "postgres" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT VERSION();" - # Import hubzilla's DB schema -# - psql -h "postgres" -U "$POSTGRES_USER" -v ON_ERROR_STOP=1 --quiet "$POSTGRES_DB" < ./install/schema_postgres.sql - # Show databases and relations/tables of hubzilla's database - #- psql -h "postgres" -U "$POSTGRES_USER" -l - #- psql -h "postgres" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\dt;" - # Run the actual tests -# - vendor/bin/phpunit --configuration tests/phpunit-pgsql.xml --testdox +.job_template_postgres: &job_definition_postgres + stage: test + variables: + HZ_TEST_DB_HOST: postgres + HZ_TEST_DB_TYPE: postgres + HZ_TEST_DB_USER: $POSTGRES_USER + HZ_TEST_DB_PASS: $POSTGRES_PASSWORD + HZ_TEST_DB_DATABASE: $POSTGRES_DB + script: + - export PGPASSWORD=$POSTGRES_PASSWORD + - psql --version + - psql -h "postgres" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT VERSION();" + # Import hubzilla's DB schema + - psql -h "postgres" -U "$POSTGRES_USER" -v ON_ERROR_STOP=1 --quiet "$POSTGRES_DB" < ./install/schema_postgres.sql + # Show databases and relations/tables of hubzilla's database + #- psql -h "postgres" -U "$POSTGRES_USER" -l + #- psql -h "postgres" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\dt;" + # Run the actual tests + - touch dbfail.out + - vendor/bin/phpunit --configuration tests/phpunit.xml --verbose --stop-on-error --coverage-text --colors=never --log-junit tests/results/junit.xml + coverage: '/^\s*Lines:\s*\d+.\d+\%/' # hidden job definition with artifacts config template -.artifacts_template: - artifacts: &artifacts_template +.artifacts_template: &artifacts_template + artifacts: expire_in: 1 week # Gitlab should show the results, but has problems parsing PHPUnit's junit file. reports: @@ -95,54 +94,22 @@ before_script: - tests/results/ -# PHP8.1 -php8.1: - <<: *job_definition_php - # PHP8.0 with MySQL 5.7 -#php8.0_mysql5.7: -# <<: *job_definition_mysql -# services: -# - mysql:5.7 - - -# PHP8.0 with MySQL 8 (latest) -#php8.0_mysql8: -# <<: *job_definition_mysql -# services: -# - name: mysql:8 -# command: ["--default-authentication-plugin=mysql_native_password"] - - -# PHP8.0 with MariaDB 10.2 -#php8.0_mariadb10.2: -# <<: *job_definition_mysql -# services: -# - name: mariadb:10.2 -# alias: mysql - - -# PHP8.0 with MariaDB 10.3 (latest) -#php8.0_mariadb10.3: -# <<: *job_definition_mysql -# image: php:8.0 - #image: registry.gitlab.com/dawnbreak/hubzilla/core:php7.3 -# services: -# - name: mariadb:10.3 -# alias: mysql - - -# PHP7.3 with PostgreSQL latest (11) -#php7.3_postgres11: -# <<: *job_definition_postgres -# artifacts: *artifacts_template - - -# PHP7.3 with PostgreSQL latest (11) -#php7.3_postgres11: -# <<: *job_definition_postgres -# image: registry.gitlab.com/dawnbreak/hubzilla/core:php7.3 -# artifacts: *artifacts_template +php8.1_mysql5.7: + <<: *job_definition_mysql + <<: *artifacts_template + image: php:8.1 + services: + - mysql:5.7 + + +# PHP8.1 with PostgreSQL 12 +php8.1_postgres12: + <<: *job_definition_postgres + <<: *artifacts_template + image: php:8.1 + services: + - postgres:12-alpine # Generate Doxygen API Documentation and deploy it as GitLab pages diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index 36a00528e..439ca472b 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -27,9 +27,12 @@ class HTTPSig { * @param string $alg hash algorithm (one of 'sha256','sha512') * @return string The generated digest header string for $body */ - static function generate_digest_header($body, $alg = 'sha256') { + if ($body === null) { + $body = ''; + } + $digest = base64_encode(hash($alg, $body, true)); switch ($alg) { case 'sha512': diff --git a/tests/create_test_db_pgsql.sh b/tests/create_test_db_pgsql.sh new file mode 100755 index 000000000..8ec2bc371 --- /dev/null +++ b/tests/create_test_db_pgsql.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# +# Copyright (c) 2016 Hubzilla +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +# Exit if anything fails +set -e + +# +# Initialize some defaults if they're not set by the environment +# +: ${DB_ROOT_USER:=postgres} +: ${DB_TEST_USER:=test_user} +: ${DB_TEST_DB:=hubzilla_test_db} + +echo "Creating test db for PostgreSQL..." + +if [[ "$POSTGRESQL_VERSION" == "10" ]]; then + echo "Using PostgreSQL in Docker container, need to use TCP" + export PROTO="-h localhost" +fi + +# Print out some PostgreSQL information +psql --version +# Why does this hang further execution of the job? +psql $PROTO -U $DB_ROOT_USER -c "SELECT VERSION();" + +# Create Hubzilla database +psql $PROTO -U $DB_ROOT_USER -v ON_ERROR_STOP=1 <<-EOSQL + DROP DATABASE IF EXISTS $DB_TEST_DB; + DROP USER IF EXISTS $DB_TEST_USER; + CREATE USER $DB_TEST_USER WITH PASSWORD 'hubzilla'; + CREATE DATABASE $DB_TEST_DB WITH OWNER $DB_TEST_USER; +EOSQL + +export PGPASSWORD=hubzilla + +# Import table structure +echo "Importing schema..." +psql $PROTO -U $DB_TEST_USER -v ON_ERROR_STOP=1 $DB_TEST_DB < ./install/schema_postgres.sql + +# Show databases and tables +psql $PROTO -U $DB_TEST_USER -l +psql $PROTO -U $DB_TEST_USER -d $DB_TEST_DB -c "\dt;" diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 6b1b33534..fe197b84a 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -1,21 +1,20 @@ - - - - ../Zotlabs/ - ../include/ - - + + - - - - - - - - - + .. + + + + + + + @@ -24,14 +23,14 @@ ./unit/ - - - - - - postgresql - - - + + + ../Zotlabs/ + ../include/ + + diff --git a/tests/unit/GetTagsTest.php b/tests/unit/GetTagsTest.php index 418d32c47..ccb88080e 100644 --- a/tests/unit/GetTagsTest.php +++ b/tests/unit/GetTagsTest.php @@ -26,6 +26,7 @@ class MockApp { * * @param string $sql */ +/* function q($sql) { $result=array(array('id'=>15, 'attag'=>'', 'network'=>'dfrn', @@ -55,7 +56,7 @@ function q($sql) { return $result; } } - +*/ /** * Replacement for dbesc. * I don't want to test dbesc here, so @@ -68,9 +69,11 @@ function q($sql) { * * @return input */ +/* function dbesc($str) { return $str; } +*/ /** * TestCase for tag handling. diff --git a/tests/unit/Lib/PermissionDescriptionTest.php b/tests/unit/Lib/PermissionDescriptionTest.php index 96c381d0c..fdd676f61 100644 --- a/tests/unit/Lib/PermissionDescriptionTest.php +++ b/tests/unit/Lib/PermissionDescriptionTest.php @@ -63,11 +63,52 @@ class PermissionDescriptionTest extends UnitTestCase { $this->assertNotNull($permDescSelf); } + /** + * Test fetching permission descriptions for the current channel. + */ public function testFromGlobalPermission() { - //$permDesc = PermissionDescription::fromGlobalPermission('view_profile'); + // Initiate the global App with a channel_id + \App::$channel = array( + 'channel_id' => 42, + ); + + // Make sure the requested permission is set for this channel. + \Zotlabs\Access\PermissionLimits::Set( + \App::$channel['channel_id'], + 'view_profile', + PERMS_NETWORK + ); + + \Zotlabs\Access\PermissionLimits::Set( + \App::$channel['channel_id'], + 'write_storage', + PERMS_SPECIFIC + ); + + // Set an invalid(?) permission + \Zotlabs\Access\PermissionLimits::Set( + \App::$channel['channel_id'], + 'view_wiki', + 1337 + ); + + $permDesc = PermissionDescription::fromGlobalPermission('view_profile'); + $this->assertEquals( + 'Anybody in the Hubzilla network', + $permDesc->get_permission_description() + ); + + $permDesc = PermissionDescription::fromGlobalPermission('write_storage'); + $this->assertEquals( + 'Only connections I specifically allow', + $permDesc->get_permission_description() + ); - $this->markTestIncomplete( - 'The method fromGlobalPermission() is not yet testable ...' + // Permissions we don't know about will get the fallback description. + $permDesc = PermissionDescription::fromGlobalPermission('view_wiki'); + $this->assertEquals( + 'Visible to your default audience', + $permDesc->get_permission_description() ); } diff --git a/tests/unit/UnitTestCase.php b/tests/unit/UnitTestCase.php index f6fb28555..2ef414cb5 100644 --- a/tests/unit/UnitTestCase.php +++ b/tests/unit/UnitTestCase.php @@ -23,12 +23,14 @@ namespace Zotlabs\Tests\Unit; use PHPUnit\Framework\TestCase; +use Symfony\Component\Yaml\Yaml; /* * Make sure global constants and the global App object is available to the * tests. */ require_once __DIR__ . '/../../boot.php'; +require_once 'include/dba/dba_driver.php' ; /** * @brief Base class for our Unit Tests. @@ -39,6 +41,95 @@ require_once __DIR__ . '/../../boot.php'; * * @author Klaus Weidenbach */ -abstract class UnitTestCase extends TestCase { - // when needed we can define functionality here which is used in UnitTests. +class UnitTestCase extends TestCase { + private bool $in_transaction = false; + protected array $fixtures = array(); + + public static function setUpBeforeClass() : void { + if ( !\DBA::$dba ) { + \DBA::dba_factory( + getenv('HZ_TEST_DB_HOST') ?: 'db', + + // Use default port for db type if none specified + getenv('HZ_TEST_DB_PORT'), + getenv('HZ_TEST_DB_USER') ?: 'test_user', + getenv('HZ_TEST_DB_PASS') ?: 'hubzilla', + getenv('HZ_TEST_DB_DATABASE') ?: 'hubzilla_test_db', + Self::dbtype(getenv('HZ_TEST_DB_TYPE')), + getenv('HZ_TEST_DB_CHARSET') ?: 'UTF8', + false); + + if ( !\DBA::$dba->connected ) { + $msg = "Unable to connect to db! "; + if(file_exists('dbfail.out')) { + $msg .= file_get_contents('dbfail.out'); + } + + throw new \Exception($msg); + } + + \DBA::$dba->dbg(true); + } + } + + protected function setUp() : void { + if ( \DBA::$dba->connected ) { + // Create a transaction, so that any actions taken by the + // tests does not change the actual contents of the database. + $this->in_transaction = \DBA::$dba->db->beginTransaction(); + + $this->loadFixtures(); + + } + } + + protected function tearDown() : void { + if ( \DBA::$dba->connected && $this->in_transaction ) { + // Roll back the transaction, restoring the db to the + // state it was before the test was run. + if ( \DBA::$dba->db->rollBack() ) { + $this->in_transaction = false; + } else { + throw new \Exception( + "Transaction rollback failed! Error is: " + . \DBA::$dba->db->errorInfo()); + } + } + } + + private static function dbtype(string $type): int { + if (trim(strtolower($type)) === 'postgres') { + return DBTYPE_POSTGRES; + } else { + return DBTYPE_MYSQL; + } + } + + private function loadFixtures() : void { + $files = glob(__DIR__ . '/includes/dba/_files/*.yml'); + if ($files === false || empty($files)) { + error_log('[-] ' . __METHOD__ . ': No fixtures found! :('); + } + array_walk($files, fn($file) => $this->loadFixture($file)); + } + + private function loadFixture($file) : void { + $table_name = basename($file, '.yml'); + $this->fixtures[$table_name] = Yaml::parseFile($file)[$table_name]; + + //echo "\n[*] Loaded fixture '{$table_name}':\n"; + // . print_r($this->fixtures[$table_name], true) + // . PHP_EOL; + + foreach ($this->fixtures[$table_name] as $entry) { + $query = 'INSERT INTO ' . dbesc($table_name) . '(' + . implode(',', array_keys($entry)) + . ') VALUES(' + . implode(',', array_map(fn($val) => "'{$val}'", array_values($entry))) + . ')'; + + //print_r($query); + q($query); + } + } } diff --git a/tests/unit/Web/HttpSigTest.php b/tests/unit/Web/HttpSigTest.php index 5524e0510..0a22b543a 100644 --- a/tests/unit/Web/HttpSigTest.php +++ b/tests/unit/Web/HttpSigTest.php @@ -70,9 +70,6 @@ class HttpSigTest extends UnitTestCase { ); } - /** - * @uses ::Crypto::unencapsulate - */ function testDecrypt_sigheader() { $header = 'Header: iv="value_iv" key="value_key" alg="value_alg" data="value_data"'; $result = [ @@ -85,9 +82,7 @@ class HttpSigTest extends UnitTestCase { $this->assertSame($result, HTTPSig::decrypt_sigheader($header, 'site private key')); } - /** - * @uses ::Crypto::unencapsulate - */ + function testDecrypt_sigheaderUseSitePrivateKey() { // Create a stub for global function get_config() with expectation $t = $this->getFunctionMock('Zotlabs\Web', 'get_config'); diff --git a/tests/unit/includes/AccountTest.php b/tests/unit/includes/AccountTest.php new file mode 100644 index 000000000..d123a0c70 --- /dev/null +++ b/tests/unit/includes/AccountTest.php @@ -0,0 +1,12 @@ +assertNotFalse($account); + $this->assertEquals($this->fixtures['account'][0]['account_email'], $account['account_email']); + } +} diff --git a/tests/unit/includes/PhotodriverTest.php b/tests/unit/includes/PhotodriverTest.php index 6f6ad0ffe..34dc058b7 100644 --- a/tests/unit/includes/PhotodriverTest.php +++ b/tests/unit/includes/PhotodriverTest.php @@ -2,38 +2,22 @@ namespace Zotlabs\Tests\Unit\includes; -//use Zotlabs\Photo\PhotoGd; use Zotlabs\Tests\Unit\UnitTestCase; -//use phpmock\phpunit\PHPMock; /** * @brief Unit Test cases for include/photo/photo_driver.php file. */ class PhotodriverTest extends UnitTestCase { - //use PHPMock; public function testPhotofactoryReturnsNullForUnsupportedType() { - // php-mock can not mock global functions which is called by a global function. - // If the calling function is in a namespace it would work. - //$logger = $this->getFunctionMock(__NAMESPACE__, 'logger'); - //$logger->expects($this->once()); - - //$ph = \photo_factory('', 'image/bmp'); - //$this->assertNull($ph); - - $this->markTestIncomplete('Need to mock logger(), otherwise not unit testable.'); + $photo = \photo_factory('', 'image/bmp'); + $this->assertNull($photo); } public function testPhotofactoryReturnsPhotogdIfConfigIgnore_imagickIsSet() { - // php-mock can not mock global functions which is called by a global function. - // If the calling function is in a namespace it would work. - //$gc = $this->getFunctionMock(__NAMESPACE__, 'get_config'); - // simulate get_config('system', 'ignore_imagick') configured - //$gc->expects($this->once())->willReturn(1) - - //$ph = \photo_factory(file_get_contents('images/hz-16.png'), 'image/png'); - //$this->assertInstanceOf(PhotoGd::class, $ph); + \Zotlabs\Lib\Config::Set('system', 'ignore_imagick', true); - $this->markTestIncomplete('Need to mock get_config(), otherwise not unit testable.'); + $photo = \photo_factory(file_get_contents('images/hz-16.png'), 'image/png'); + $this->assertInstanceOf('Zotlabs\Photo\PhotoGd', $photo); } -} \ No newline at end of file +} -- cgit v1.2.3