diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index a0e7173..602e7ee 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -61,6 +61,7 @@ jobs: POSTGRES_PASSWORD: 'bedita' POSTGRES_DB: 'bedita' ports: + - '3306:3306' - '5432:5432' options: '${{ fromJson(matrix.db).options }}' diff --git a/config/app.php b/config/app.php index b04a7c5..2871840 100644 --- a/config/app.php +++ b/config/app.php @@ -213,7 +213,10 @@ 'skipLog' => ['Cake\Network\Exception\NotFoundException', 'BEdita\API\Exception\ExpiredTokenException'], 'log' => true, 'trace' => true, - 'ignoredDeprecationPaths' => ['vendor/cakephp/cakephp/src/Log/Engine/FileLog.php'], + 'ignoredDeprecationPaths' => [ + 'vendor/cakephp/cakephp/src/Log/Engine/FileLog.php', + 'vendor/cakephp/migrations/src/Db/Table/ForeignKey.php', + ], ], /* diff --git a/phpstan.neon.dist b/phpstan.neon.dist index cb0d17d..b1bc3e0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,3 +7,4 @@ parameters: - tests excludePaths: - src/Console/Installer.php + - tests/Fixture diff --git a/phpunit.xml.dist b/phpunit.xml.dist index affe678..9b6c640 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,25 +7,26 @@ bootstrap="./tests/bootstrap.php" cacheDirectory=".phpunit.cache" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.1/phpunit.xsd" + displayDetailsOnTestsThatTriggerDeprecations="false" + displayDetailsOnTestsThatTriggerErrors="false" + displayDetailsOnTestsThatTriggerNotices="false" + displayDetailsOnTestsThatTriggerWarnings="false" + displayDetailsOnPhpunitDeprecations="false" > + - + ./tests/TestCase/ - - - - - diff --git a/tests/Fixture/ApplicationsFixture.php b/tests/Fixture/ApplicationsFixture.php new file mode 100644 index 0000000..4e499f3 --- /dev/null +++ b/tests/Fixture/ApplicationsFixture.php @@ -0,0 +1,26 @@ + API_KEY, + 'client_secret' => null, + 'name' => 'my_webapp', + 'created' => '2026-01-29 07:10:57', + 'modified' => '2026-01-29 07:10:57', + 'enabled' => 1, + ], + ]; +} diff --git a/tests/Fixture/ObjectTypesFixture.php b/tests/Fixture/ObjectTypesFixture.php new file mode 100644 index 0000000..825a9d1 --- /dev/null +++ b/tests/Fixture/ObjectTypesFixture.php @@ -0,0 +1,73 @@ + 'object', + 'name' => 'objects', + 'is_abstract' => true, + 'parent_id' => null, + 'tree_left' => 1, + 'tree_right' => 24, + 'description' => null, + 'plugin' => 'BEdita/Core', + 'model' => 'Objects', + 'created' => '2025-11-10 09:27:23', + 'modified' => '2025-11-10 09:27:23', + 'enabled' => true, + 'core_type' => true, + 'translation_rules' => null, + 'is_translatable' => false, + ], + // 2 + [ + 'singular' => 'user', + 'name' => 'users', + 'is_abstract' => false, + 'parent_id' => 1, + 'tree_left' => 6, + 'tree_right' => 7, + 'description' => null, + 'plugin' => 'BEdita/Core', + 'model' => 'Users', + 'created' => '2025-11-10 09:27:23', + 'modified' => '2025-11-10 09:27:23', + 'enabled' => true, + 'core_type' => true, + 'translation_rules' => null, + 'is_translatable' => false, + ], + // 3 + [ + 'singular' => 'profile', + 'name' => 'profiles', + 'is_abstract' => false, + 'parent_id' => 1, + 'tree_left' => 4, + 'tree_right' => 5, + 'description' => null, + 'associations' => ['Tags'], + 'plugin' => 'BEdita/Core', + 'model' => 'Profiles', + 'created' => '2025-11-10 09:27:23', + 'modified' => '2025-11-10 09:27:23', + 'enabled' => true, + 'core_type' => true, + 'translation_rules' => null, + 'is_translatable' => false, + ], + ]; +} diff --git a/tests/Fixture/ObjectsFixture.php b/tests/Fixture/ObjectsFixture.php new file mode 100644 index 0000000..4e35492 --- /dev/null +++ b/tests/Fixture/ObjectsFixture.php @@ -0,0 +1,76 @@ + 2, + 'status' => 'on', + 'uname' => 'gustavo-admin', + 'locked' => 1, + 'deleted' => 0, + 'created' => '2026-01-29 07:09:23', + 'modified' => '2026-01-29 07:09:23', + 'title' => 'Gustavo Admin', + 'lang' => 'it', + 'created_by' => 1, + 'modified_by' => 1, + ], + ]; + + /** + * @inheritDoc + */ + public function init(): void + { + parent::init(); + + // remove `objects_createdby_fk` and `objects_modifiedby_fk` constraints + // to avoid PostgreSQL error inserting first user that references itself. + // CakePHP inserting fixture disables constraints but + // when the constraints are enabled again PostgreSQL give an SQL error. + $connection = ConnectionManager::get($this->connection()); + if (!$connection->getDriver() instanceof Postgres) { + return; + } + + $constraints = $this->_schema->constraints(); + $removeConstraints = ['objects_createdby_fk', 'objects_modifiedby_fk']; + if (empty(array_intersect($constraints, $removeConstraints))) { + return; + } + + $restoreConstraints = []; + foreach ($this->_schema->constraints() as $name) { + if (in_array($name, $removeConstraints)) { + continue; + } + + $restoreConstraints[$name] = $this->_schema->getConstraint($name); + $this->_schema->dropConstraint($name); + } + + $dropConstraintSql = $this->_schema->dropConstraintSql($connection); + foreach ($dropConstraintSql as $sql) { + $connection->execute($sql); + } + + foreach ($restoreConstraints as $name => $attrs) { + $this->_schema->addConstraint($name, $attrs); + } + } +} diff --git a/tests/Fixture/ProfilesFixture.php b/tests/Fixture/ProfilesFixture.php new file mode 100644 index 0000000..c896158 --- /dev/null +++ b/tests/Fixture/ProfilesFixture.php @@ -0,0 +1,24 @@ + 1, + 'name' => 'Gustavo', + 'surname' => 'Admin', + 'email' => 'gustavo-admin@example.com', + ], + ]; +} diff --git a/tests/Fixture/PropertiesFixture.php b/tests/Fixture/PropertiesFixture.php new file mode 100644 index 0000000..ff7a47b --- /dev/null +++ b/tests/Fixture/PropertiesFixture.php @@ -0,0 +1,18 @@ +records = [ + // 1 + [ + 'name' => 'string', + 'params' => ['type' => 'string'], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 2 + [ + 'name' => 'text', + 'params' => [ + 'type' => 'string', + 'contentMediaType' => 'text/html', + ], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 3 + [ + 'name' => 'status', + 'params' => [ + 'type' => 'string', + 'enum' => ['on', 'off', 'draft'], + ], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 4 + [ + 'name' => 'email', + 'params' => [ + 'type' => 'string', + 'format' => 'email', + ], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 5 + [ + 'name' => 'url', + 'params' => [ + 'type' => 'string', + 'format' => 'uri', + ], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 6 + [ + 'name' => 'date', + 'params' => [ + 'type' => 'string', + 'format' => 'date', + ], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 7 + [ + 'name' => 'datetime', + 'params' => [ + 'type' => 'string', + 'format' => 'date-time', + ], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 8 + [ + 'name' => 'number', + 'params' => ['type' => 'number'], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 9 + [ + 'name' => 'integer', + 'params' => ['type' => 'integer'], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 10 + [ + 'name' => 'boolean', + 'params' => ['type' => 'boolean'], + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 11 + [ + 'name' => 'json', + 'params' => new stdClass(), + 'created' => '2025-11-01 09:23:43', + 'modified' => '2025-11-01 09:23:43', + 'core_type' => true, + ], + // 12 + [ + 'name' => 'unused property type', + 'params' => [ + 'type' => 'object', + 'properties' => [ + 'gustavo' => ['const' => 'supporto'], + ], + 'required' => ['gustavo'], + ], + 'created' => '2025-11-02 09:23:43', + 'modified' => '2025-11-02 09:23:43', + 'core_type' => false, + ], + // 13 + [ + 'name' => 'children_order', + 'params' => [ + 'type' => 'string', + 'enum' => [ + 'position', + '-position', + 'title', + '-title', + 'created', + '-created', + 'modified', + '-modified', + 'publish_start', + '-publish_start', + ], + ], + 'created' => '2022-12-01 15:35:21', + 'modified' => '2022-12-01 15:35:21', + 'core_type' => true, + ], + ]; + + parent::init(); + } +} diff --git a/tests/Fixture/RolesFixture.php b/tests/Fixture/RolesFixture.php new file mode 100644 index 0000000..999964a --- /dev/null +++ b/tests/Fixture/RolesFixture.php @@ -0,0 +1,26 @@ + 'admin', + 'unchangeable' => 1, + 'created' => '2025-12-29 11:36:00', + 'modified' => '2025-12-29 11:36:00', + 'priority' => 0, + ], + ]; +} diff --git a/tests/Fixture/RolesUsersFixture.php b/tests/Fixture/RolesUsersFixture.php new file mode 100644 index 0000000..773ba41 --- /dev/null +++ b/tests/Fixture/RolesUsersFixture.php @@ -0,0 +1,22 @@ + 1, + 'user_id' => 1, + ], + ]; +} diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php new file mode 100644 index 0000000..cac6cb3 --- /dev/null +++ b/tests/Fixture/UsersFixture.php @@ -0,0 +1,35 @@ +records = [ + [ + 'id' => 1, + 'username' => 'gustavo-admin', + 'password_hash' => (new LegacyPasswordHasher(['hashType' => 'md5']))->hash('supporto'), + 'blocked' => 0, + 'last_login' => null, + 'last_login_err' => null, + 'num_login_err' => 0, + 'verified' => '2026-01-29 11:36:00', + 'password_modified' => '2026-01-29 11:36:00', + ], + ]; + + parent::init(); + } +} diff --git a/tests/IntegrationTestCase.php b/tests/IntegrationTestCase.php new file mode 100644 index 0000000..ddd4014 --- /dev/null +++ b/tests/IntegrationTestCase.php @@ -0,0 +1,47 @@ + + */ + protected array $fixtures = []; + + /** + * The required fixtures for authentication. + * They are added to fixtures present in test case class + * + * @var array + */ + protected array $authFixtures = [ + 'app.Applications', + 'app.ObjectTypes', + 'app.Objects', + 'app.Profiles', + 'app.Users', + 'app.Roles', + 'app.RolesUsers', + 'app.PropertyTypes', + 'app.Properties', + ]; + + /** + * Default user used for authentication + * + * @var array + */ + protected array $defaultUser = [ + 'username' => 'gustavo-admin', + 'password' => 'supporto', + ]; +} diff --git a/tests/TestCase/UsersTest.php b/tests/TestCase/UsersTest.php new file mode 100644 index 0000000..5d40e0d --- /dev/null +++ b/tests/TestCase/UsersTest.php @@ -0,0 +1,38 @@ +configRequestHeaders('GET', $this->getUserAuthHeader(username: 'gustavo-admin', password: 'supporto')); + $this->get('/users/1'); + $this->assertResponseOk(); + } +}