From bdbf2cf6165e3e9d2abea9147546be2d8eb5b4bc Mon Sep 17 00:00:00 2001 From: Rustam Mamadaminov Date: Thu, 24 Mar 2022 19:27:55 +0500 Subject: [PATCH] Implement (#16) --- .github/workflows/build.yml | 3 +- .github/workflows/mutation.yml | 4 + .github/workflows/static.yml | 1 - composer.json | 6 +- phpunit.xml.dist | 29 ++-- src/.gitkeep | 0 src/AssignmentsStorage.php | 147 ++++++++++++++++++ src/Command/RbacCycleInit.php | 155 +++++++++++++++++++ src/ItemsStorage.php | 258 +++++++++++++++++++++++++++++++ tests/.gitkeep | 0 tests/AssignmentsStorageTest.php | 159 +++++++++++++++++++ tests/ItemsStorageTest.php | 240 ++++++++++++++++++++++++++++ tests/RbacCycleInitTest.php | 26 ++++ tests/TestCase.php | 79 ++++++++++ tests/runtime/.gitignore | 2 + 15 files changed, 1090 insertions(+), 19 deletions(-) delete mode 100644 src/.gitkeep create mode 100644 src/AssignmentsStorage.php create mode 100644 src/Command/RbacCycleInit.php create mode 100644 src/ItemsStorage.php delete mode 100644 tests/.gitkeep create mode 100644 tests/AssignmentsStorageTest.php create mode 100644 tests/ItemsStorageTest.php create mode 100644 tests/RbacCycleInitTest.php create mode 100644 tests/TestCase.php create mode 100644 tests/runtime/.gitignore diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d147d39..70f79d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,7 @@ jobs: name: PHP ${{ matrix.php }}-${{ matrix.os }} env: + extensions: pdo, pdo_sqlite key: cache-v1 runs-on: ${{ matrix.os }} @@ -37,7 +38,6 @@ jobs: - windows-latest php: - - 7.4 - 8.0 - 8.1 @@ -52,6 +52,7 @@ jobs: ini-values: date.timezone='UTC' coverage: pcov tools: composer:v2 + extensions: ${{ env.extensions }} - name: Determine composer cache directory on Linux if: matrix.os == 'ubuntu-latest' diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 36553fd..b1cc6ac 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -23,6 +23,9 @@ jobs: mutation: name: PHP ${{ matrix.php }}-${{ matrix.os }} + env: + extensions: pdo, pdo_sqlite + runs-on: ${{ matrix.os }} strategy: @@ -44,6 +47,7 @@ jobs: ini-values: memory_limit=-1 coverage: pcov tools: composer:v2 + extensions: ${{ env.extensions }} - name: Determine composer cache directory run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 86fab9d..c50088f 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -33,7 +33,6 @@ jobs: - ubuntu-latest php: - - 7.4 - 8.0 - 8.1 diff --git a/composer.json b/composer.json index 8328225..b102ae8 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,10 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "php": "^7.4|^8.0", - "cycle/database": "^2.1" + "php": "^8.0", + "cycle/database": "^2.1", + "symfony/console": "^5.3|^6.0", + "yiisoft/rbac": "dev-master" }, "require-dev": { "phpunit/phpunit": "^9.5", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8541c1c..fbc56bf 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,28 +1,27 @@ - - + failOnWarning="true" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> + + + ./ + + + ./tests + ./vendor + + - - + ./tests - - - - ./ - - ./tests - ./vendor - - - diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/AssignmentsStorage.php b/src/AssignmentsStorage.php new file mode 100644 index 0000000..a7b143c --- /dev/null +++ b/src/AssignmentsStorage.php @@ -0,0 +1,147 @@ +database = $dbal->database(); + $this->tableName = $tableName; + } + + /** + * @inheritDoc + */ + public function getAll(): array + { + $assignments = []; + foreach ($this->database->select()->from($this->tableName)->fetchAll() as $item) { + $assignments[$item['userId']][$item['itemName']] = new Assignment( + $item['userId'], + $item['itemName'], + (int)$item['createdAt'] + ); + } + + return $assignments; + } + + /** + * @inheritDoc + */ + public function getByUserId(string $userId): array + { + $assignments = $this->database->select()->from($this->tableName)->where(['userId' => $userId])->fetchAll(); + + return array_map( + static fn (array $item) => new Assignment($userId, $item['itemName'], (int)$item['createdAt']), + $assignments + ); + } + + /** + * @inheritDoc + */ + public function get(string $itemName, string $userId): ?Assignment + { + $assignment = $this->database + ->select() + ->from($this->tableName) + ->where(['itemName' => $itemName, 'userId' => $userId]) + ->run() + ->fetch(); + if (!empty($assignment)) { + return new Assignment($userId, $itemName, (int)$assignment['createdAt']); + } + return null; + } + + /** + * @inheritDoc + */ + public function add(string $itemName, string $userId): void + { + $this->database + ->insert($this->tableName) + ->values( + [ + 'itemName' => $itemName, + 'userId' => $userId, + 'createdAt' => time(), + ], + ) + ->run(); + } + + /** + * @inheritDoc + */ + public function hasItem(string $name): bool + { + return $this->database->select('itemName')->from($this->tableName)->where(['itemName' => $name])->count() > 0; + } + + /** + * @inheritDoc + */ + public function renameItem(string $oldName, string $newName): void + { + if ($oldName === $newName) { + return; + } + $this->database->update($this->tableName, ['itemName' => $newName], ['itemName' => $oldName])->run(); + } + + /** + * @inheritDoc + */ + public function remove(string $itemName, string $userId): void + { + $this->database->delete($this->tableName, ['itemName' => $itemName, 'userId' => $userId])->run(); + } + + /** + * @inheritDoc + */ + public function removeByUserId(string $userId): void + { + $this->database->delete($this->tableName, ['userId' => $userId])->run(); + } + + /** + * @inheritDoc + */ + public function removeByItemName(string $itemName): void + { + $this->database->delete($this->tableName, ['itemName' => $itemName])->run(); + } + + /** + * @inheritDoc + */ + public function clear(): void + { + /** @var Table $table */ + $table = $this->database->table($this->tableName); + $table->eraseData(); + } +} diff --git a/src/Command/RbacCycleInit.php b/src/Command/RbacCycleInit.php new file mode 100644 index 0000000..1c54072 --- /dev/null +++ b/src/Command/RbacCycleInit.php @@ -0,0 +1,155 @@ +dbal = $dbal; + $this->config = $config; + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDescription('Create RBAC schemas') + ->setHelp('This command creates schemas for RBAC using Cycle DBAL') + ->addOption('force', 'f', InputOption::VALUE_OPTIONAL, 'Force re-create schemas if exists', false); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + /** @var non-empty-string $itemsChildrenTable */ + $itemsChildrenTable = $this->config['itemsChildrenTable'] ?? $this->config['itemsTable'] . '_child'; + $force = $input->getOption('force'); + if ($force === false) { + $reCreate = false; + } else { + $reCreate = true; + } + /** @var Table $table */ + if ($reCreate && $this->dbal->database()->hasTable($itemsChildrenTable) === true) { + $this->dropTable($itemsChildrenTable); + } + if ($reCreate && $this->dbal->database()->hasTable($this->config['assignmentsTable']) === true) { + $this->dropTable($this->config['assignmentsTable']); + } + if ($reCreate && $this->dbal->database()->hasTable($this->config['itemsTable']) === true) { + $this->dropTable($this->config['itemsTable']); + } + if ($this->dbal->database()->hasTable($this->config['itemsTable']) === false) { + $output->writeln('Creating `' . $this->config['itemsTable'] . '` table...'); + $this->createItemsTable(); + $output->writeln('Table `' . $this->config['itemsTable'] . '` created successfully'); + } + + if ($this->dbal->database()->hasTable($itemsChildrenTable) === false) { + $output->writeln('Creating `' . $itemsChildrenTable . '` table...'); + $this->createItemsChildrenTable($itemsChildrenTable); + $output->writeln('Table `' . $itemsChildrenTable . '` created successfully'); + } + + if ($this->dbal->database()->hasTable($this->config['assignmentsTable']) === false) { + $output->writeln('Creating `' . $this->config['assignmentsTable'] . '` table...'); + $this->createAssignmentsTable(); + $output->writeln('Table `' . $this->config['assignmentsTable'] . '` created successfully'); + } + $output->writeln('DONE'); + return 0; + } + + private function createItemsTable(): void + { + /** @var Table $table */ + $table = $this->dbal->database()->table($this->config['itemsTable']); + $schema = $table->getSchema(); + + $schema->string('name', 128); + $schema->enum('type', [Item::TYPE_ROLE, Item::TYPE_PERMISSION])->nullable(false); + $schema->string('description', 191)->nullable(); + $schema->string('ruleName', 64)->nullable(); + $schema->integer('createdAt')->nullable(false); + $schema->integer('updatedAt')->nullable(false); + $schema->index(['type']); + $schema->setPrimaryKeys(['name']); + + $schema->save(); + } + + /** + * @param non-empty-string $itemsChildrenTable + */ + private function createItemsChildrenTable(string $itemsChildrenTable): void + { + /** @var Table $table */ + $table = $this->dbal->database()->table($itemsChildrenTable); + $schema = $table->getSchema(); + + $schema->string('parent', 128)->nullable(false); + $schema->string('child', 128)->nullable(false); + $schema->setPrimaryKeys(['parent', 'child']); + + $schema->foreignKey(['parent']) + ->references($this->config['itemsTable'], ['name']) + ->onDelete(ForeignKeyInterface::CASCADE) + ->onUpdate(ForeignKeyInterface::CASCADE); + + $schema->foreignKey(['child']) + ->references($this->config['itemsTable'], ['name']) + ->onDelete(ForeignKeyInterface::CASCADE) + ->onUpdate(ForeignKeyInterface::CASCADE); + + $schema->save(); + } + + private function createAssignmentsTable(): void + { + /** @var Table $table */ + $table = $this->dbal->database()->table($this->config['assignmentsTable']); + $schema = $table->getSchema(); + + $schema->primary('itemName')->string(128); + $schema->primary('userId')->string(128); + $schema->integer('createdAt')->nullable(false); + $schema->index(['itemName', 'userId']); + + $schema->foreignKey(['itemName']) + ->references($this->config['itemsTable'], ['name']) + ->onUpdate(ForeignKeyInterface::CASCADE) + ->onDelete(ForeignKeyInterface::CASCADE); + + $schema->save(); + } + + /** + * @param non-empty-string $tableName + */ + private function dropTable(string $tableName): void + { + /** @var Table $table */ + $table = $this->dbal->database()->table($tableName); + $schema = $table->getSchema(); + $schema->declareDropped(); + $schema->save(); + } +} diff --git a/src/ItemsStorage.php b/src/ItemsStorage.php new file mode 100644 index 0000000..63299a9 --- /dev/null +++ b/src/ItemsStorage.php @@ -0,0 +1,258 @@ +database = $dbal->database(); + $this->tableName = $tableName; + $this->childrenTableName = $childrenTableName ?? $tableName . '_child'; + } + + /** + * @inheritDoc + */ + public function clear(): void + { + if ($this->database->hasTable($this->tableName)) { + /** @var Table $table */ + $table = $this->database->table($this->tableName); + $table->eraseData(); + } + } + + /** + * @inheritDoc + */ + public function getAll(): array + { + return array_map( + fn (array $item): Item => $this->populateItem($item), + $this->database->select()->from($this->tableName)->fetchAll() + ); + } + + /** + * @inheritDoc + */ + public function get(string $name): ?Item + { + $item = $this->database->select()->from($this->tableName)->where(['name' => $name])->run()->fetch(); + if (!empty($item)) { + return $this->populateItem($item); + } + return null; + } + + /** + * @inheritDoc + */ + public function add(Item $item): void + { + $time = time(); + if (!$item->hasCreatedAt()) { + $item = $item->withCreatedAt($time); + } + if (!$item->hasUpdatedAt()) { + $item = $item->withUpdatedAt($time); + } + $this->database->insert($this->tableName)->values($item->getAttributes())->run(); + } + + /** + * @inheritDoc + */ + public function update(string $name, Item $item): void + { + $this->database->update($this->tableName, $item->getAttributes(), ['name' => $name])->run(); + } + + /** + * @inheritDoc + */ + public function remove(string $name): void + { + $this->database->delete($this->tableName, ['name' => $name])->run(); + } + + /** + * @inheritDoc + */ + public function getRoles(): array + { + return $this->getItemsByType(Item::TYPE_ROLE); + } + + /** + * @inheritDoc + */ + public function getRole(string $name): ?Role + { + return $this->getItemByTypeAndName(Item::TYPE_ROLE, $name); + } + + /** + * @inheritDoc + */ + public function clearRoles(): void + { + $this->database->delete($this->tableName, ['type' => Item::TYPE_ROLE])->run(); + } + + /** + * @inheritDoc + */ + public function getPermissions(): array + { + return $this->getItemsByType(Item::TYPE_PERMISSION); + } + + /** + * @inheritDoc + */ + public function getPermission(string $name): ?Permission + { + return $this->getItemByTypeAndName(Item::TYPE_PERMISSION, $name); + } + + /** + * @inheritDoc + */ + public function clearPermissions(): void + { + $this->database->delete($this->tableName, ['type' => Item::TYPE_PERMISSION])->run(); + } + + /** + * @inheritDoc + */ + public function getParents(string $name): array + { + $parents = $this->database + ->select() + ->from([$this->tableName, $this->childrenTableName]) + ->where(['child' => $name, 'name' => new Expression('parent')]) + ->fetchAll(); + return array_map(fn (array $item): Item => $this->populateItem($item), $parents); + } + + /** + * @inheritDoc + */ + public function getChildren(string $name): array + { + $children = $this->database + ->select() + ->from([$this->tableName, $this->childrenTableName]) + ->where(['parent' => $name, 'name' => new Expression('child')]) + ->fetchAll(); + + return array_map(fn (array $item): Item => $this->populateItem($item), $children); + } + + /** + * @inheritDoc + */ + public function hasChildren(string $name): bool + { + return $this->database->select('parent')->from($this->childrenTableName)->where(['parent' => $name])->count() > 0; + } + + /** + * @inheritDoc + */ + public function addChild(string $parentName, string $childName): void + { + $this->database->insert($this->childrenTableName)->values(['parent' => $parentName, 'child' => $childName])->run(); + } + + /** + * @inheritDoc + */ + public function removeChild(string $parentName, string $childName): void + { + $this->database + ->delete($this->childrenTableName, ['parent' => $parentName, 'child' => $childName]) + ->run(); + } + + /** + * @inheritDoc + */ + public function removeChildren(string $parentName): void + { + $this->database->delete($this->childrenTableName, ['parent' => $parentName])->run(); + } + + /** + * @psalm-return ($type is Item::TYPE_PERMISSION ? Permission[] : ($type is Item::TYPE_ROLE ? Role[] : Item[])) + */ + private function getItemsByType(string $type): array + { + $items = $this->database->select()->from($this->tableName)->where(['type' => $type])->fetchAll(); + + return array_map(fn (array $item): Item => $this->populateItem($item), $items); + } + + /** + * @psalm-return ($type is Item::TYPE_PERMISSION ? Permission : ($type is Item::TYPE_ROLE ? Role : Item))|null + */ + private function getItemByTypeAndName(string $type, string $name): ?Item + { + $item = $this->database->select()->from($this->tableName)->where(['type' => $type, 'name' => $name])->run()->fetch(); + + if (empty($item)) { + return null; + } + + return $this->populateItem($item); + } + + /** + * @psalm-param array{type: string, name: string, description?: string, ruleName?: string, createdAt: int|string, updatedAt: int|string} $attributes + */ + private function populateItem(array $attributes): Item + { + return $this->createItemByTypeAndName($attributes['type'], $attributes['name']) + ->withDescription($attributes['description'] ?? '') + ->withRuleName($attributes['ruleName'] ?? '') + ->withCreatedAt((int)$attributes['createdAt']) + ->withUpdatedAt((int)$attributes['updatedAt']); + } + + /** + * @psalm-return ($type is Item::TYPE_PERMISSION ? Permission : ($type is Item::TYPE_ROLE ? Role : Item)) + */ + private function createItemByTypeAndName(string $type, string $name): Item + { + return $type === Item::TYPE_PERMISSION ? new Permission($name) : new Role($name); + } +} diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/AssignmentsStorageTest.php b/tests/AssignmentsStorageTest.php new file mode 100644 index 0000000..85f8bdb --- /dev/null +++ b/tests/AssignmentsStorageTest.php @@ -0,0 +1,159 @@ +getStorage(); + + $this->assertTrue($storage->hasItem('Admin')); + } + + public function testRenameItem(): void + { + $storage = $this->getStorage(); + $storage->renameItem('Admin', 'Tech Admin'); + + $this->assertTrue($storage->hasItem('Tech Admin')); + } + + public function testGetAll(): void + { + $storage = $this->getStorage(); + $all = $storage->getAll(); + + $this->assertCount(2, $all); + } + + public function testRemoveByItemName(): void + { + $storage = $this->getStorage(); + $storage->removeByItemName('Manager'); + + $this->assertFalse($storage->hasItem('Manager')); + } + + public function testGetByUserId(): void + { + $storage = $this->getStorage(); + $assignments = $storage->getByUserId('admin'); + + $this->assertCount(1, $assignments); + $this->assertSame('Admin', $assignments[0]->getItemName()); + } + + public function testRemoveByUserId(): void + { + $storage = $this->getStorage(); + $storage->removeByUserId('manager'); + + $this->assertFalse($storage->hasItem('Manager')); + $this->assertEmpty($storage->getByUserId('manager')); + } + + public function testRemove(): void + { + $storage = $this->getStorage(); + $storage->remove('Admin', 'admin'); + + $this->assertFalse($storage->hasItem('Admin')); + $this->assertEmpty($storage->get('Admin', 'admin')); + } + + public function testClear(): void + { + $storage = $this->getStorage(); + $storage->clear(); + + $this->assertEmpty($storage->getAll()); + } + + public function testGet(): void + { + $storage = $this->getStorage(); + $assignment = $storage->get('Manager', 'manager'); + + $this->assertSame('Manager', $assignment->getItemName()); + $this->assertSame('manager', $assignment->getUserId()); + } + + public function testAdd(): void + { + $storage = $this->getStorage(); + $storage->add('Tech Admin', 'admin'); + + $this->assertInstanceOf(Assignment::class, $storage->get('Tech Admin', 'admin')); + } + + protected function populateDb(): void + { + $items = [ + [ + 'name' => 'Admin', + 'type' => Item::TYPE_ROLE, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + [ + 'name' => 'Tech Admin', + 'type' => Item::TYPE_ROLE, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + [ + 'name' => 'Manager', + 'type' => Item::TYPE_ROLE, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + [ + 'name' => 'Delete user', + 'type' => Item::TYPE_PERMISSION, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + ]; + + $assignments = [ + [ + 'itemName' => 'Admin', + 'userId' => 'admin', + 'createdAt' => time(), + ], + [ + 'itemName' => 'Manager', + 'userId' => 'manager', + 'createdAt' => time(), + ], + ]; + + foreach ($items as $item) { + $this->getDbal() + ->database() + ->insert('auth_item') + ->values($item) + ->run(); + } + + foreach ($assignments as $item) { + $this->getDbal() + ->database() + ->insert('auth_assignment') + ->values($item) + ->run(); + } + } + + private function getStorage(): AssignmentsStorage + { + return new AssignmentsStorage('auth_assignment', $this->getDbal()); + } +} diff --git a/tests/ItemsStorageTest.php b/tests/ItemsStorageTest.php new file mode 100644 index 0000000..1c1a1e7 --- /dev/null +++ b/tests/ItemsStorageTest.php @@ -0,0 +1,240 @@ +getStorage(); + $item = $storage->get('Parent 1'); + $this->assertEmpty($item->getRuleName()); + + $item = $item + ->withName('Super Admin') + ->withRuleName('super admin'); + $storage->update('Parent 1', $item); + $this->assertSame('Super Admin', $storage->get('Super Admin')->getName()); + $this->assertSame('super admin', $storage->get('Super Admin')->getRuleName()); + } + + public function testGet(): void + { + $storage = $this->getStorage(); + $item = $storage->get('Parent 3'); + + $this->assertInstanceOf(Permission::class, $item); + $this->assertSame(Item::TYPE_PERMISSION, $item->getType()); + } + + public function testGetPermission(): void + { + $storage = $this->getStorage(); + $permission = $storage->getPermission('Child 1'); + + $this->assertInstanceOf(Permission::class, $permission); + } + + public function testAddChild(): void + { + $storage = $this->getStorage(); + + $storage->addChild('Parent 2', 'Child 1'); + + $this->assertCount(2, $storage->getChildren('Parent 2')); + } + + public function testClear(): void + { + $storage = $this->getStorage(); + $storage->clear(); + + $this->assertEmpty($storage->getAll()); + $this->assertEmpty($storage->getChildren('Parent 2')); + } + + public function testGetChildren(): void + { + $storage = $this->getStorage(); + + $children = $storage->getChildren('Parent 1'); + + $this->assertCount(1, $children); + $this->assertContainsOnlyInstancesOf(Item::class, $children); + } + + public function testGetRoles(): void + { + $storage = $this->getStorage(); + $roles = $storage->getRoles(); + + $this->assertNotEmpty($roles); + $this->assertContainsOnlyInstancesOf(Role::class, $roles); + } + + public function testGetPermissions(): void + { + $storage = $this->getStorage(); + $permissions = $storage->getPermissions(); + + $this->assertCount(2, $permissions); + $this->assertContainsOnlyInstancesOf(Permission::class, $permissions); + } + + public function testRemove(): void + { + $storage = $this->getStorage(); + $storage->remove('Parent 3'); + + $this->assertEmpty($storage->get('Parent 3')); + } + + public function testGetParents(): void + { + $storage = $this->getStorage(); + $parents = $storage->getParents('Child 1'); + + $this->assertCount(1, $parents); + $this->assertSame('Parent 1', $parents[0]->getName()); + } + + public function testRemoveChildren(): void + { + $storage = $this->getStorage(); + $storage->removeChildren('Parent 2'); + + $this->assertFalse($storage->hasChildren('Parent 2')); + } + + public function testGetRole(): void + { + $storage = $this->getStorage(); + $role = $storage->getRole('Parent 1'); + + $this->assertNotEmpty($role); + $this->assertInstanceOf(Role::class, $role); + } + + public function testAdd(): void + { + $storage = $this->getStorage(); + $newItem = new Permission('Delete post'); + $storage->add($newItem); + + $this->assertInstanceOf(Permission::class, $storage->get('Delete post')); + } + + public function testRemoveChild(): void + { + $storage = $this->getStorage(); + $storage->removeChild('Parent 2', 'Child 2'); + + $this->assertFalse($storage->hasChildren('Parent 2')); + } + + public function testGetAll(): void + { + $storage = $this->getStorage(); + $all = $storage->getAll(); + + $this->assertCount(5, $all); + } + + public function testHasChildren(): void + { + $storage = $this->getStorage(); + + $this->assertTrue($storage->hasChildren('Parent 1')); + $this->assertFalse($storage->hasChildren('Parent 3')); + } + + public function testClearPermissions(): void + { + $storage = $this->getStorage(); + $storage->clearPermissions(); + + $this->assertContainsOnlyInstancesOf(Role::class, $storage->getAll()); + } + + public function testClearRoles(): void + { + $storage = $this->getStorage(); + $storage->clearRoles(); + + $this->assertContainsOnlyInstancesOf(Permission::class, $storage->getAll()); + } + + protected function populateDb(): void + { + $items = [ + [ + 'name' => 'Parent 1', + 'type' => Item::TYPE_ROLE, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + [ + 'name' => 'Parent 2', + 'type' => Item::TYPE_ROLE, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + [ + 'name' => 'Parent 3', + 'type' => Item::TYPE_PERMISSION, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + [ + 'name' => 'Child 1', + 'type' => Item::TYPE_PERMISSION, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + [ + 'name' => 'Child 2', + 'type' => Item::TYPE_ROLE, + 'createdAt' => time(), + 'updatedAt' => time(), + ], + ]; + $items_child = [ + [ + 'parent' => 'Parent 1', + 'child' => 'Child 1', + ], + [ + 'parent' => 'Parent 2', + 'child' => 'Child 2', + ], + ]; + + foreach ($items as $item) { + $this->getDbal() + ->database() + ->insert('auth_item') + ->values($item) + ->run(); + } + + foreach ($items_child as $item) { + $this->getDbal() + ->database() + ->insert('auth_item_child') + ->values($item) + ->run(); + } + } + + private function getStorage() + { + return new ItemsStorage('auth_item', $this->getDbal()); + } +} diff --git a/tests/RbacCycleInitTest.php b/tests/RbacCycleInitTest.php new file mode 100644 index 0000000..02e3eef --- /dev/null +++ b/tests/RbacCycleInitTest.php @@ -0,0 +1,26 @@ +createDbTables(); + + $this->assertTrue($this->getDbal()->database()->hasTable('auth_item')); + $this->assertTrue($this->getDbal()->database()->hasTable('auth_item_child')); + $this->assertTrue($this->getDbal()->database()->hasTable('auth_assignment')); + } + + protected function populateDb(): void + { + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..8699de6 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,79 @@ +databaseManager === null) { + $this->createConnection(); + } + return $this->databaseManager; + } + + protected function setUp(): void + { + parent::setUp(); + $this->createDbTables(); + $this->populateDb(); + } + + protected function tearDown(): void + { + foreach ($this->getDbal()->database()->getTables() as $table) { + /** @var AbstractTable $schema */ + $schema = $table->getSchema(); + $schema->declareDropped(); + $schema->save(); + } + } + + protected function createDbTables(): void + { + $app = new Application(); + $app->add( + new RbacCycleInit( + [ + 'itemsTable' => 'auth_item', + 'itemsChildTable' => 'auth_item_child', + 'assignmentsTable' => 'auth_assignment', + ], + $this->getDbal() + ) + ); + $app->find('rbac/cycle/init')->run(new ArrayInput([]), new NullOutput()); + } + + private function createConnection(): void + { + $dbConfig = new DatabaseConfig( + [ + 'default' => 'default', + 'databases' => [ + 'default' => ['connection' => 'sqlite'], + ], + 'connections' => [ + 'sqlite' => new SQLiteDriverConfig(new FileConnectionConfig(__DIR__ . '/runtime/test.db')), + ], + ] + ); + $this->databaseManager = new DatabaseManager($dbConfig); + } + + abstract protected function populateDb(): void; +} diff --git a/tests/runtime/.gitignore b/tests/runtime/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore