From 6f903448cd6422a1239fc06232f984687fd5c0e7 Mon Sep 17 00:00:00 2001 From: zerai Date: Thu, 12 Sep 2024 11:01:50 +0200 Subject: [PATCH 01/18] Changed namespace for Metadata tests. Moved from Metadata\Tests to MetadataTests 'Metadata' namespace onflict with ecotone namespace conf in ecotone.yaml --- .../Bdd/Context/ForSynchronizingMetadataScenarioContext.php | 2 +- _metadata/tests/Unit/Framework/FrameworkExtensionTest.php | 2 +- _metadata/tests/Unit/MetadataUpdaterTest.php | 2 +- behat.yml | 2 +- composer.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/_metadata/tests/Acceptance/Bdd/Context/ForSynchronizingMetadataScenarioContext.php b/_metadata/tests/Acceptance/Bdd/Context/ForSynchronizingMetadataScenarioContext.php index a397c7e..0571cfe 100644 --- a/_metadata/tests/Acceptance/Bdd/Context/ForSynchronizingMetadataScenarioContext.php +++ b/_metadata/tests/Acceptance/Bdd/Context/ForSynchronizingMetadataScenarioContext.php @@ -13,7 +13,7 @@ * @license https://github.com/MedicalMundi/marketplace-engine/blob/main/LICENSE MIT */ -namespace Metadata\Tests\Acceptance\Bdd\Context; +namespace MetadataTests\Acceptance\Bdd\Context; use Behat\Behat\Context\Context; use Behat\Gherkin\Node\TableNode; diff --git a/_metadata/tests/Unit/Framework/FrameworkExtensionTest.php b/_metadata/tests/Unit/Framework/FrameworkExtensionTest.php index 92926e4..df8c9a6 100644 --- a/_metadata/tests/Unit/Framework/FrameworkExtensionTest.php +++ b/_metadata/tests/Unit/Framework/FrameworkExtensionTest.php @@ -13,7 +13,7 @@ * @license https://github.com/MedicalMundi/marketplace-engine/blob/main/LICENSE MIT */ -namespace Metadata\Tests\Unit\Framework; +namespace MetadataTests\Unit\Framework; use Metadata\Infrastructure\Framework\Extension\MetadataModuleExtension; use PHPUnit\Framework\Attributes\CoversClass; diff --git a/_metadata/tests/Unit/MetadataUpdaterTest.php b/_metadata/tests/Unit/MetadataUpdaterTest.php index 3b61943..36a5680 100644 --- a/_metadata/tests/Unit/MetadataUpdaterTest.php +++ b/_metadata/tests/Unit/MetadataUpdaterTest.php @@ -13,7 +13,7 @@ * @license https://github.com/MedicalMundi/marketplace-engine/blob/main/LICENSE MIT */ -namespace Metadata\Tests\Unit; +namespace MetadataTests\Unit; use Metadata\AdapterForReadingExternalMetadataSourceStub\StubAdapterForReadingExternalMetadataSource; use Metadata\AdapterForStoringMetadataFake\FakeForStoringMetadata; diff --git a/behat.yml b/behat.yml index 2ebad49..132612f 100644 --- a/behat.yml +++ b/behat.yml @@ -11,4 +11,4 @@ default: paths: - '%paths.base%/_metadata/tests/Acceptance/Bdd/Features/driver_for_synchronizing_metadata' contexts: - - Metadata\Tests\Acceptance\Bdd\Context\ForSynchronizingMetadataScenarioContext + - MetadataTests\Acceptance\Bdd\Context\ForSynchronizingMetadataScenarioContext diff --git a/composer.json b/composer.json index 882ca3c..7acbf29 100644 --- a/composer.json +++ b/composer.json @@ -123,7 +123,7 @@ "Catalog\\Tests\\": "_catalog/tests/", "BffWeb\\Tests\\": "_bffWeb/tests/", "BffApi\\Tests\\": "_bffApi/tests/", - "Metadata\\Tests\\": "_metadata/tests/" + "MetadataTests\\": "_metadata/tests/" } }, "minimum-stability": "stable", From 3ed92681b107d45b54fa32d9eeddc4648de48966 Mon Sep 17 00:00:00 2001 From: zerai Date: Fri, 13 Sep 2024 08:47:12 +0200 Subject: [PATCH 02/18] Composer added knplabs/github-api. --- .env | 6 + composer.json | 1 + composer.lock | 201 +++++++++++++++++++++++++++++++- config/packages/github_api.yaml | 13 +++ symfony.lock | 12 ++ 5 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 config/packages/github_api.yaml diff --git a/.env b/.env index fdd3989..a52c6cb 100644 --- a/.env +++ b/.env @@ -49,3 +49,9 @@ SENTRY_DSN= # postgresql+advisory://db_user:db_password@localhost/db_name LOCK_DSN=flock ###< symfony/lock ### + +###> knplabs/github-api ### +GITHUB_AUTH_METHOD=http_password +GITHUB_USERNAME=username +GITHUB_SECRET=password_or_token +###< knplabs/github-api ### diff --git a/composer.json b/composer.json index 7acbf29..65b19e4 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "ecotone/jms-converter": "^1.2", "ecotone/pdo-event-sourcing": "^1.2", "ecotone/symfony-bundle": "^1.2", + "knplabs/github-api": "*", "knplabs/knp-menu-bundle": "^3.3", "knplabs/packagist-api": "^2.0", "knpuniversity/oauth2-client-bundle": "^2.18", diff --git a/composer.lock b/composer.lock index fcb62d0..b031a00 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6cfdc360de49df6e69227b9fc03320e0", + "content-hash": "4e2a8e6617d23fa18059acfe2c3c8d5e", "packages": [ { "name": "babdev/pagerfanta-bundle", @@ -3212,6 +3212,94 @@ ], "time": "2023-08-03T14:43:08+00:00" }, + { + "name": "knplabs/github-api", + "version": "v3.14.1", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/php-github-api.git", + "reference": "71fec50e228737ec23c0b69801b85bf596fbdaca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/71fec50e228737ec23c0b69801b85bf596fbdaca", + "reference": "71fec50e228737ec23c0b69801b85bf596fbdaca", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2.5 || ^8.0", + "php-http/cache-plugin": "^1.7.1|^2.0", + "php-http/client-common": "^2.3", + "php-http/discovery": "^1.12", + "php-http/httplug": "^2.2", + "php-http/multipart-stream-builder": "^1.1.2", + "psr/cache": "^1.0|^2.0|^3.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.0|^2.0", + "symfony/deprecation-contracts": "^2.2|^3.0", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.2", + "guzzlehttp/psr7": "^1.7", + "http-interop/http-factory-guzzle": "^1.0", + "php-http/mock-client": "^1.4.1", + "phpstan/extension-installer": "^1.0.5", + "phpstan/phpstan": "^0.12.57", + "phpstan/phpstan-deprecation-rules": "^0.12.5", + "phpunit/phpunit": "^8.5 || ^9.4", + "symfony/cache": "^5.1.8", + "symfony/phpunit-bridge": "^5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.20.x-dev", + "dev-master": "3.14-dev" + } + }, + "autoload": { + "psr-4": { + "Github\\": "lib/Github/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + }, + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + } + ], + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": [ + "api", + "gh", + "gist", + "github" + ], + "support": { + "issues": "https://github.com/KnpLabs/php-github-api/issues", + "source": "https://github.com/KnpLabs/php-github-api/tree/v3.14.1" + }, + "funding": [ + { + "url": "https://github.com/acrobat", + "type": "github" + } + ], + "time": "2024-03-24T18:21:15+00:00" + }, { "name": "knplabs/knp-menu", "version": "v3.5.0", @@ -4221,6 +4309,61 @@ }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "php-http/cache-plugin", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/cache-plugin.git", + "reference": "539b2d1ea0dc1c2f141c8155f888197d4ac5635b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/cache-plugin/zipball/539b2d1ea0dc1c2f141c8155f888197d4ac5635b", + "reference": "539b2d1ea0dc1c2f141c8155f888197d4ac5635b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/client-common": "^1.9 || ^2.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/http-factory-implementation": "^1.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "require-dev": { + "nyholm/psr7": "^1.6.1", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Client\\Common\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "PSR-6 Cache plugin for HTTPlug", + "homepage": "http://httplug.io", + "keywords": [ + "cache", + "http", + "httplug", + "plugin" + ], + "support": { + "issues": "https://github.com/php-http/cache-plugin/issues", + "source": "https://github.com/php-http/cache-plugin/tree/2.0.0" + }, + "time": "2024-02-19T17:02:14+00:00" + }, { "name": "php-http/client-common", "version": "2.7.1", @@ -4550,6 +4693,62 @@ "abandoned": "psr/http-factory", "time": "2023-04-14T14:16:17+00:00" }, + { + "name": "php-http/multipart-stream-builder", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/multipart-stream-builder.git", + "reference": "10086e6de6f53489cca5ecc45b6f468604d3460e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/10086e6de6f53489cca5ecc45b6f468604d3460e", + "reference": "10086e6de6f53489cca5ecc45b6f468604d3460e", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/discovery": "^1.15", + "psr/http-factory-implementation": "^1.0" + }, + "require-dev": { + "nyholm/psr7": "^1.0", + "php-http/message": "^1.5", + "php-http/message-factory": "^1.0.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Message\\MultipartStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + } + ], + "description": "A builder class that help you create a multipart stream", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "multipart stream", + "stream" + ], + "support": { + "issues": "https://github.com/php-http/multipart-stream-builder/issues", + "source": "https://github.com/php-http/multipart-stream-builder/tree/1.4.2" + }, + "time": "2024-09-04T13:22:54+00:00" + }, { "name": "php-http/promise", "version": "1.3.1", diff --git a/config/packages/github_api.yaml b/config/packages/github_api.yaml new file mode 100644 index 0000000..3510294 --- /dev/null +++ b/config/packages/github_api.yaml @@ -0,0 +1,13 @@ +services: + Github\Client: + arguments: + - '@Github\HttpClient\Builder' + # Uncomment to enable authentication + #calls: + # - ['authenticate', ['%env(GITHUB_USERNAME)%', '%env(GITHUB_SECRET)%', '%env(GITHUB_AUTH_METHOD)%']] + + Github\HttpClient\Builder: + arguments: + - '@?Http\Client\HttpClient' + - '@?Http\Message\RequestFactory' + - '@?Http\Message\StreamFactory' diff --git a/symfony.lock b/symfony.lock index bfb641a..a6548b2 100644 --- a/symfony.lock +++ b/symfony.lock @@ -65,6 +65,18 @@ "ref": "4dd47013ecec8a158d186f63ec4b51f1defff897" } }, + "knplabs/github-api": { + "version": "3.14", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "2.6", + "ref": "a7d5781e4a8e84f1c238c43e4c9bb806f01d8a3f" + }, + "files": [ + "config/packages/github_api.yaml" + ] + }, "knplabs/knp-menu-bundle": { "version": "v3.3.0" }, From 3402e4fa74d795ed725bf7b2c7ddd34be45d37b1 Mon Sep 17 00:00:00 2001 From: zerai Date: Fri, 13 Sep 2024 10:15:43 +0200 Subject: [PATCH 03/18] Update ecotone conf (load metadata namespace). --- config/packages/ecotone.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/packages/ecotone.yaml b/config/packages/ecotone.yaml index 346a4bc..eb1fd66 100644 --- a/config/packages/ecotone.yaml +++ b/config/packages/ecotone.yaml @@ -4,6 +4,7 @@ ecotone: namespaces: # string[] (default: []) - 'Catalog\' - 'BffWeb\' + - 'Metadata\' defaultSerializationMediaType: application/x-php-serialized # string (default: application/x-php-serialized) [application/json, application/xml] defaultErrorChannel: null # string (default: null) defaultMemoryLimit: 1024 # string (default: 1024) From 00134216e391512bbcd0ba5b43a72449a9f74332 Mon Sep 17 00:00:00 2001 From: zerai Date: Fri, 13 Sep 2024 10:16:37 +0200 Subject: [PATCH 04/18] try & discovry. --- .../src/AdapterForWeb/GithubController.php | 49 ++++++++++++++++++ .../src/AdapterForWeb/PackagistController.php | 50 +++++++++++++++++++ .../AdapterCli/ForSynchronizingMetadata/X.php | 17 +++++++ ...dapterForReadingExternalMetadataSource.php | 30 +++++++++++ 4 files changed, 146 insertions(+) create mode 100644 _bffWeb/src/AdapterForWeb/GithubController.php create mode 100644 _bffWeb/src/AdapterForWeb/PackagistController.php create mode 100644 _metadata/src/AdapterCli/ForSynchronizingMetadata/X.php create mode 100644 _metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php diff --git a/_bffWeb/src/AdapterForWeb/GithubController.php b/_bffWeb/src/AdapterForWeb/GithubController.php new file mode 100644 index 0000000..a56e274 --- /dev/null +++ b/_bffWeb/src/AdapterForWeb/GithubController.php @@ -0,0 +1,49 @@ + 'en|es|it', + ], + defaults: [ + '_locale' => 'en', + ], + methods: 'GET', + )] + public function index(): Response + { + $repo =$this->githubClient->repo()->show('medicalmundi', 'oe-module-npi-registry'); + + dd($repo); + return $this->render('@web/home/index.html.twig'); + } +} diff --git a/_bffWeb/src/AdapterForWeb/PackagistController.php b/_bffWeb/src/AdapterForWeb/PackagistController.php new file mode 100644 index 0000000..75a1eb9 --- /dev/null +++ b/_bffWeb/src/AdapterForWeb/PackagistController.php @@ -0,0 +1,50 @@ + 'en|es|it', + ], + defaults: [ + '_locale' => 'en', + ], + methods: 'GET', + )] + public function index(): Response + { + $repo =$this->packagistClient->getComposer('medicalmundi/oe-module-npi-registry'); + + dd($repo); + return $this->render('@web/home/index.html.twig'); + } +} diff --git a/_metadata/src/AdapterCli/ForSynchronizingMetadata/X.php b/_metadata/src/AdapterCli/ForSynchronizingMetadata/X.php new file mode 100644 index 0000000..b210e72 --- /dev/null +++ b/_metadata/src/AdapterCli/ForSynchronizingMetadata/X.php @@ -0,0 +1,17 @@ +synchronizeMetadataFor($moduleId); + } +} \ No newline at end of file diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php b/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php new file mode 100644 index 0000000..2ab203c --- /dev/null +++ b/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php @@ -0,0 +1,30 @@ +getMessage()); + } + } +} \ No newline at end of file From 093edea8f5a345918d5cb1dfa922b877e39dee58 Mon Sep 17 00:00:00 2001 From: zerai Date: Wed, 18 Sep 2024 12:57:28 +0200 Subject: [PATCH 05/18] Added Repository VO. * removed unused controller --- .../src/AdapterForWeb/GithubController.php | 49 -------- .../src/AdapterForWeb/PackagistController.php | 50 -------- _metadata/src/Core/ValueObject/Repository.php | 83 ++++++++++++ .../Unit/Core/ValueObject/RepositoryTest.php | 119 ++++++++++++++++++ 4 files changed, 202 insertions(+), 99 deletions(-) delete mode 100644 _bffWeb/src/AdapterForWeb/GithubController.php delete mode 100644 _bffWeb/src/AdapterForWeb/PackagistController.php create mode 100644 _metadata/src/Core/ValueObject/Repository.php create mode 100644 _metadata/tests/Unit/Core/ValueObject/RepositoryTest.php diff --git a/_bffWeb/src/AdapterForWeb/GithubController.php b/_bffWeb/src/AdapterForWeb/GithubController.php deleted file mode 100644 index a56e274..0000000 --- a/_bffWeb/src/AdapterForWeb/GithubController.php +++ /dev/null @@ -1,49 +0,0 @@ - 'en|es|it', - ], - defaults: [ - '_locale' => 'en', - ], - methods: 'GET', - )] - public function index(): Response - { - $repo =$this->githubClient->repo()->show('medicalmundi', 'oe-module-npi-registry'); - - dd($repo); - return $this->render('@web/home/index.html.twig'); - } -} diff --git a/_bffWeb/src/AdapterForWeb/PackagistController.php b/_bffWeb/src/AdapterForWeb/PackagistController.php deleted file mode 100644 index 75a1eb9..0000000 --- a/_bffWeb/src/AdapterForWeb/PackagistController.php +++ /dev/null @@ -1,50 +0,0 @@ - 'en|es|it', - ], - defaults: [ - '_locale' => 'en', - ], - methods: 'GET', - )] - public function index(): Response - { - $repo =$this->packagistClient->getComposer('medicalmundi/oe-module-npi-registry'); - - dd($repo); - return $this->render('@web/home/index.html.twig'); - } -} diff --git a/_metadata/src/Core/ValueObject/Repository.php b/_metadata/src/Core/ValueObject/Repository.php new file mode 100644 index 0000000..3d16604 --- /dev/null +++ b/_metadata/src/Core/ValueObject/Repository.php @@ -0,0 +1,83 @@ +source; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getName(): string + { + return $this->name; + } + + public function isGitHub(): bool + { + return self::GITHUB_SOURCE === $this->getSource(); + } + + public function isBitbucket(): bool + { + return self::BITBUCKET_SOURCE === $this->getSource(); + } + + public function isGitLab(): bool + { + return self::GITLAB_SOURCE === $this->getSource(); + } + + public function isSupported(): bool + { + return $this->isGitHub() || $this->isBitbucket() || $this->isGitLab(); + } +} diff --git a/_metadata/tests/Unit/Core/ValueObject/RepositoryTest.php b/_metadata/tests/Unit/Core/ValueObject/RepositoryTest.php new file mode 100644 index 0000000..74e5f10 --- /dev/null +++ b/_metadata/tests/Unit/Core/ValueObject/RepositoryTest.php @@ -0,0 +1,119 @@ +getSource()); + self::assertEquals('username', $repository->getUsername()); + self::assertEquals('repository', $repository->getName()); + } + + public function testItThrowExceptionIfUrlNotValid(): void + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Impossible to fetch package by "https://google.it" repository.'); + + Repository::createFromRepositoryUrl('https://google.it'); + } + + public function testItShouldCreateRepository(): void + { + $repository = Repository::create('github.com', 'username', 'repository'); + + self::assertEquals('github.com', $repository->getSource()); + self::assertEquals('username', $repository->getUsername()); + self::assertEquals('repository', $repository->getName()); + } + + public function testItDetectGitHubAsSourceProvider(): void + { + $repository = Repository::createFromRepositoryUrl('https://github.com/username/repository'); + + self::assertTrue($repository->isGitHub()); + } + + public function testGitHubShouldNotdetectedAsSourceProvider(): void + { + $repository = Repository::createFromRepositoryUrl('https://fake-provider.com/username/repository'); + + self::assertFalse($repository->isGitHub()); + } + + public function testItSupportGitHubAsSourceProvider(): void + { + $repository = Repository::createFromRepositoryUrl('https://github.com/username/repository'); + + self::assertTrue($repository->isSupported()); + } + + public function testItDetectBitbucketAsSourceProvider(): void + { + $repository = Repository::createFromRepositoryUrl('https://bitbucket.org/username/repository'); + + self::assertTrue($repository->isBitbucket()); + } + + public function testBitbucketShouldNotdetectedAsSourceProvider(): void + { + $repository = Repository::createFromRepositoryUrl('https://fake-provider.com/username/repository'); + + self::assertFalse($repository->isBitbucket()); + } + + public function testItSupportBitbucketAsSourceProvider(): void + { + $repository = Repository::createFromRepositoryUrl('https://bitbucket.org/username/repository'); + + self::assertTrue($repository->isSupported()); + } + + /** + * @dataProvider unsupportedRepositorySourceProvider + */ + public function testItDetectUnsupportedSourceProvider(string $sourceProviderUrl): void + { + $repository = Repository::createFromRepositoryUrl($sourceProviderUrl); + + self::assertFalse($repository->isSupported()); + } + + public function testItDetectGitLabAsSourceProvider(): void + { + $repository = Repository::createFromRepositoryUrl('https://gitlab.com/username/repository'); + + self::assertTrue($repository->isGitLab()); + } + + /** + * @return \Generator> + */ + public static function unsupportedRepositorySourceProvider(): \Generator + { + yield ['https://www.gitlab.com/username/repository']; + yield ['https://www.my-self-hosted-git.com/acme/foo']; + yield ['https://www.fake-provider.com/foo/bar']; + } +} From f5eebe9b7ebc18be19d5271d0c176bd69065df05 Mon Sep 17 00:00:00 2001 From: zerai Date: Thu, 19 Sep 2024 14:45:28 +0200 Subject: [PATCH 06/18] init update metadata as process. --- .../MetadataUpdateCommand.php | 42 ++++ .../AdapterCli/ForSynchronizingMetadata/X.php | 17 -- ...dapterForReadingExternalMetadataSource.php | 29 ++- .../StatelessWorkflowProcess.php | 212 ++++++++++++++++++ .../UpdateModuleMetadata.php | 25 +++ 5 files changed, 299 insertions(+), 26 deletions(-) create mode 100644 _metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php delete mode 100644 _metadata/src/AdapterCli/ForSynchronizingMetadata/X.php create mode 100644 _metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php create mode 100644 _metadata/src/AdapterForReadingExternalMetadataSource/UpdateModuleMetadata.php diff --git a/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php b/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php new file mode 100644 index 0000000..9203163 --- /dev/null +++ b/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php @@ -0,0 +1,42 @@ +send($command); + } +} diff --git a/_metadata/src/AdapterCli/ForSynchronizingMetadata/X.php b/_metadata/src/AdapterCli/ForSynchronizingMetadata/X.php deleted file mode 100644 index b210e72..0000000 --- a/_metadata/src/AdapterCli/ForSynchronizingMetadata/X.php +++ /dev/null @@ -1,17 +0,0 @@ -synchronizeMetadataFor($moduleId); - } -} \ No newline at end of file diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php b/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php index 2ab203c..ad158b4 100644 --- a/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php +++ b/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php @@ -1,4 +1,17 @@ -getMessage()); } } -} \ No newline at end of file +} diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php b/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php new file mode 100644 index 0000000..47cec6d --- /dev/null +++ b/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php @@ -0,0 +1,212 @@ +logger->info('MetadataUpdater - init update for module id: ' . $command->moduleId); + + return $command; + } + + #[InternalHandler( + inputChannelName: 'module.enrich', + outputChannelName: 'module.getDefaultBranch', + changingHeaders: true + )] + public function enrichCommand(UpdateModuleMetadata $command, GithubClient $githubClient): array + { + /** + * TODO: handle failure + */ + $repository = Repository::createFromRepositoryUrl($command->repositoryUrl); + + + return [ + 'repository' => $repository, + ]; + } + + #[InternalHandler( + inputChannelName: 'module.getDefaultBranch', + outputChannelName: 'module.getComposerJson', + changingHeaders: true, + )] + public function getDefaultBranch( + UpdateModuleMetadata $command, + #[Header('repository')] + Repository $repository, + GithubClient $githubClient + ): ?array { + /** + * TODO: handle failure + */ + try { + $data = $githubClient->repo()->show($repository->getUsername(), $repository->getName()); + if (\array_key_exists('default_branch', $data)) { + return [ + 'default_branch' => (string) $data['default_branch'], + ]; + } + + } catch (\Exception $exception) { + $this->logger->error('Metadata updater error on module id: ' . $command->moduleId . ' ' . $exception->getMessage()); + } + + return null; + } + + #[InternalHandler( + inputChannelName: 'module.getComposerJson', + outputChannelName: 'module.extractMetadata', + changingHeaders: true, + )] + public function getComposerJson( + UpdateModuleMetadata $command, + #[Header('repository')] + Repository $repository, + #[Header('default_branch')] + string $defaultBranchName, + ClientInterface $httpClient, + GithubClient $githubClient, + ): ?array { + $path = 'composer.json'; + + $reference = $defaultBranchName; + + /** + * TODO: handle failure + */ + try { + /** @var Repo $repoApi */ + $repoApi = $githubClient->api('repo'); + $fileInfo = (array) $repoApi + ->contents() + ->show($repository->getUsername(), $repository->getName(), $path, $reference); + $content = $this->getComposerJsonFileContent((string) $fileInfo['download_url'], $httpClient); + + return [ + 'composer_json_content' => $content, + ]; + } catch (\Exception $exception) { + $this->logger->error('Metadata updater error on module id: ' . $command->moduleId . ' ' . $exception->getMessage()); + } + + return null; + } + + #[InternalHandler( + inputChannelName: 'module.extractMetadata', + outputChannelName: 'image.upload' + )] + public function extractMetadata( + UpdateModuleMetadata $command, + #[Header('composer_json_content')] + string $composerJsonContent, + ): UpdateModuleMetadata { + $ar = (array) json_decode($composerJsonContent, true); + + if ($this->hasMetadata($ar)) { + + print_r($ar['extra']['openemr-module']['metadata']['oe-modules.com']); + } + + print_r($nodata = 'No metadata found'); + + return $command; + } + + #[InternalHandler(inputChannelName: 'image.resize', outputChannelName: 'image.upload')] + public function resizeImage(UpdateModuleMetadata $command): UpdateModuleMetadata + { + //echo 'xxxx'; + + return $command; + } + + #[InternalHandler(inputChannelName: 'image.upload')] + public function uploadImage(UpdateModuleMetadata $command): void + { + //$imageUploader->uploadImage($command->path); + echo 'yyyy'; + } + + private function getComposerJsonFileContent(string $url, ClientInterface $httpclient): string + { + $request = new Request('GET', $url); + + $response = $httpclient->sendRequest($request); + + $x = $response->getBody()->getContents(); + + return $x; + } + + /** + * TODO: refactor + */ + private function hasMetadata(array $data): bool + { + $result = false; + + if (\array_key_exists('extra', $data)) { + $extraSection = (array) $data['extra']; + + + if (\array_key_exists('openemr-module', $extraSection)) { + $openEmrModuleSection = (array) $extraSection['openemr-module']; + + if (\array_key_exists('metadata', $openEmrModuleSection)) { + $metadataSection = (array) $openEmrModuleSection['metadata']; + + if (\array_key_exists('oe-modules.com', $metadataSection)) { + $result = true; + } + } + } + } + + return $result; + } +} diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/UpdateModuleMetadata.php b/_metadata/src/AdapterForReadingExternalMetadataSource/UpdateModuleMetadata.php new file mode 100644 index 0000000..4da3340 --- /dev/null +++ b/_metadata/src/AdapterForReadingExternalMetadataSource/UpdateModuleMetadata.php @@ -0,0 +1,25 @@ + Date: Thu, 19 Sep 2024 14:54:37 +0200 Subject: [PATCH 07/18] Fix composer validation issue. --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 65b19e4..6ce0dd9 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "ecotone/jms-converter": "^1.2", "ecotone/pdo-event-sourcing": "^1.2", "ecotone/symfony-bundle": "^1.2", - "knplabs/github-api": "*", + "knplabs/github-api": "^3.14", "knplabs/knp-menu-bundle": "^3.3", "knplabs/packagist-api": "^2.0", "knpuniversity/oauth2-client-bundle": "^2.18", diff --git a/composer.lock b/composer.lock index b031a00..b4b40ad 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4e2a8e6617d23fa18059acfe2c3c8d5e", + "content-hash": "58e028aa07298161ceb2007e95b58f44", "packages": [ { "name": "babdev/pagerfanta-bundle", From f498fc6edd14f4ab47d2b40e4dd9018bbad6c8f9 Mon Sep 17 00:00:00 2001 From: zerai Date: Thu, 19 Sep 2024 14:55:58 +0200 Subject: [PATCH 08/18] Fix CS. --- .../StatelessWorkflowProcess.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php b/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php index 47cec6d..1b48f53 100644 --- a/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php +++ b/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php @@ -89,7 +89,6 @@ public function getDefaultBranch( 'default_branch' => (string) $data['default_branch'], ]; } - } catch (\Exception $exception) { $this->logger->error('Metadata updater error on module id: ' . $command->moduleId . ' ' . $exception->getMessage()); } @@ -148,7 +147,6 @@ public function extractMetadata( $ar = (array) json_decode($composerJsonContent, true); if ($this->hasMetadata($ar)) { - print_r($ar['extra']['openemr-module']['metadata']['oe-modules.com']); } From 563cf683ac2e8324710c5ad36b9b32c5484fd6cb Mon Sep 17 00:00:00 2001 From: zerai Date: Thu, 19 Sep 2024 17:49:47 +0200 Subject: [PATCH 09/18] Added basic metadata validation. --- ...alseMetadataValidationEngineValidation.php | 24 -- .../ForMetadataSchemaValidation.php | 3 + .../MetadataValidationException.php | 5 +- .../MetadataValidator.php | 76 +++++++ .../MetadataValidatorTest.php | 205 ++++++++++++++++++ 5 files changed, 287 insertions(+), 26 deletions(-) delete mode 100644 _metadata/src/Core/MetadataValidationEngine/FixedFalseMetadataValidationEngineValidation.php create mode 100644 _metadata/src/Core/MetadataValidationEngine/MetadataValidator.php create mode 100644 _metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php diff --git a/_metadata/src/Core/MetadataValidationEngine/FixedFalseMetadataValidationEngineValidation.php b/_metadata/src/Core/MetadataValidationEngine/FixedFalseMetadataValidationEngineValidation.php deleted file mode 100644 index 7fee45f..0000000 --- a/_metadata/src/Core/MetadataValidationEngine/FixedFalseMetadataValidationEngineValidation.php +++ /dev/null @@ -1,24 +0,0 @@ -validate([ + 'tags' => 'irrelevant', + ]); + } + + #[Test] + #[DataProvider('invalidCategoryDataprovider')] + public function shouldFailWhenCategoryIsNotValid(array $metadata): void + { + self::expectException(MetadataValidationException::class); + self::expectExceptionMessage('Metadata \'Category\' should be string type'); + + $validator = new MetadataValidator(); + $validator->validate($metadata); + } + + #[Test] + public function shouldFailWhenThereIsNoTagsKey(): void + { + self::expectException(MetadataValidationException::class); + self::expectExceptionMessage('Metadata key \'tags\' not found'); + $validator = new MetadataValidator(); + + $validator->validate([ + 'category' => 'billing', + ]); + } + + #[Test] + #[DataProvider('invalidTagsDataprovider')] + public function shouldFailWhenTagsIsNotValid(array $metadata): void + { + self::expectException(MetadataValidationException::class); + self::expectExceptionMessage('Metadata \'tags\' should be array type'); + + $validator = new MetadataValidator(); + $validator->validate($metadata); + } + + #[Test] + public function shouldPassTheValidation(): void + { + $validator = new MetadataValidator(); + + $result = $validator->validate([ + 'category' => 'billing', + 'tags' => ['fax', 'sms'], + ]); + + self::assertTrue($result); + } + + #[Test] + #[DataProvider('approvedCategoryDataprovider')] + #[DataProvider('approvedTagsDataprovider')] + public function shouldPassTheValidationOnlyWithApprovedCategoryAndTags(array $metadata): void + { + $validator = new MetadataValidator(); + + $result = $validator->validate($metadata); + + self::assertTrue($result); + } + + public static function invalidCategoryDataprovider(): array + { + return [ + [[ + 'category' => null, + 'tags' => 'irrelevant', + ]], + [[ + 'category' => 0, + 'tags' => 'irrelevant', + ]], + [[ + 'category' => [], + 'tags' => 'irrelevant', + ]], + [[ + 'category' => new \stdClass(), + 'tags' => 'irrelevant', + ]], + ]; + } + + public static function invalidTagsDataprovider(): array + { + return [ + [[ + 'category' => 'billing', + 'tags' => null, + ]], + [[ + 'category' => 'billing', + 'tags' => 0, + ]], + [[ + 'category' => 'billing', + 'tags' => 'string', + ]], + [[ + 'category' => 'billing', + 'tags' => new \stdClass(), + ]], + [[ + 'category' => 'billing', + 'tags' => 'not allowed', + ]], + ]; + } + + public static function approvedCategoryDataprovider(): array + { + return [ + [[ + 'category' => 'administration', + 'tags' => ['fax'], + ]], + [[ + 'category' => 'billing', + 'tags' => ['fax'], + ]], + [[ + 'category' => 'ePrescribing', + 'tags' => ['fax'], + ]], + [[ + 'category' => 'miscellaneous', + 'tags' => ['fax'], + ]], + [[ + 'category' => 'telecom', + 'tags' => ['fax'], + ]], + [[ + 'category' => 'telehealth', + 'tags' => ['fax'], + ]], + [[ + 'category' => 'payment', + 'tags' => ['fax'], + ]], + ]; + } + + public static function approvedTagsDataprovider(): array + { + return [ + [[ + 'category' => 'miscellaneous', + 'tags' => ['fax'], + ]], + [[ + 'category' => 'miscellaneous', + 'tags' => ['organizer'], + ]], + [[ + 'category' => 'miscellaneous', + 'tags' => ['scheduler'], + ]], + [[ + 'category' => 'miscellaneous', + 'tags' => ['todo'], + ]], + + + + ]; + } +} From 67b0f0162e81d9aaaa8e3c40a39e3de498c892fb Mon Sep 17 00:00:00 2001 From: zerai Date: Fri, 20 Sep 2024 08:56:10 +0200 Subject: [PATCH 10/18] Update acceptance test (MetadataValidator). --- .../MetadataUpdateCommand.php | 4 +-- .../StatelessWorkflowProcess.php | 26 ++++++++++++------- _metadata/src/Core/MetadataUpdater.php | 5 ++-- ...TrueMetadataValidationEngineValidation.php | 24 ----------------- .../synchronizeModuleMetadata.feature | 4 +-- _metadata/tests/Unit/MetadataUpdaterTest.php | 13 +++++----- 6 files changed, 28 insertions(+), 48 deletions(-) delete mode 100644 _metadata/src/Core/MetadataValidationEngine/FixedTrueMetadataValidationEngineValidation.php diff --git a/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php b/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php index 9203163..fa34b68 100644 --- a/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php +++ b/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php @@ -27,13 +27,13 @@ public function execute(CommandBus $commandBus): void $moduleId = 'foo'; // with error - $repoUrl = 'https://github.com/zerai/foo'; + //$repoUrl = 'https://github.com/zerai/foo'; // without metadata //$repoUrl = 'https://github.com/zerai/oe-module-demo-farm-add-ons'; // with metadata - //$repoUrl = 'https://github.com/MedicalMundi/oe-module-todo-list'; + $repoUrl = 'https://github.com/MedicalMundi/oe-module-todo-list'; $command = new UpdateModuleMetadata($moduleId, $repoUrl); diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php b/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php index 1b48f53..f407108 100644 --- a/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php +++ b/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php @@ -20,6 +20,7 @@ use Ecotone\Modelling\Attribute\CommandHandler; use Github\Api\Repo; use Github\Client as GithubClient; +use Metadata\Core\MetadataValidationEngine\MetadataValidator; use Metadata\Core\ValueObject\Repository; use Nyholm\Psr7\Request; use Psr\Http\Client\ClientInterface; @@ -137,30 +138,35 @@ public function getComposerJson( #[InternalHandler( inputChannelName: 'module.extractMetadata', - outputChannelName: 'image.upload' + outputChannelName: 'module.validateMetadata', + changingHeaders: true, )] public function extractMetadata( UpdateModuleMetadata $command, #[Header('composer_json_content')] string $composerJsonContent, - ): UpdateModuleMetadata { + ): ?array { $ar = (array) json_decode($composerJsonContent, true); if ($this->hasMetadata($ar)) { - print_r($ar['extra']['openemr-module']['metadata']['oe-modules.com']); + return [ + 'metadata' => $ar['extra']['openemr-module']['metadata']['oe-modules.com'], + ]; } - print_r($nodata = 'No metadata found'); - - return $command; + return null; } - #[InternalHandler(inputChannelName: 'image.resize', outputChannelName: 'image.upload')] - public function resizeImage(UpdateModuleMetadata $command): UpdateModuleMetadata + #[InternalHandler(inputChannelName: 'module.validateMetadata')] + public function validateMetadata(#[Header('metadata')] array $metadata, MetadataValidator $metadataValidator): ?UpdateModuleMetadata { - //echo 'xxxx'; + if ($metadataValidator->validate($metadata)) { + echo 'metadata pass'; + } else { + echo 'metadata not pass'; + } - return $command; + return null; } #[InternalHandler(inputChannelName: 'image.upload')] diff --git a/_metadata/src/Core/MetadataUpdater.php b/_metadata/src/Core/MetadataUpdater.php index 28329a6..dcc35bc 100644 --- a/_metadata/src/Core/MetadataUpdater.php +++ b/_metadata/src/Core/MetadataUpdater.php @@ -15,9 +15,9 @@ namespace Metadata\Core; -use Metadata\Core\MetadataValidationEngine\FixedTrueMetadataValidationEngineValidation; use Metadata\Core\MetadataValidationEngine\ForMetadataSchemaValidation; use Metadata\Core\MetadataValidationEngine\MetadataValidationException; +use Metadata\Core\MetadataValidationEngine\MetadataValidator; use Metadata\Core\Port\Driven\ForReadingExternalMetadataSource\ForReadingExternalMetadataSource; use Metadata\Core\Port\Driven\ForReadingExternalMetadataSource\MetadataReaderException; use Metadata\Core\Port\Driven\ForStoringMetadata; @@ -29,10 +29,9 @@ class MetadataUpdater implements ForSynchronizingMetadata public function __construct( private readonly ForStoringMetadata $metadataStore, private readonly ForReadingExternalMetadataSource $metadataReader, - /** TODO: Implement a real validator engine */ private ?ForMetadataSchemaValidation $validatorEngine = null, ) { - $this->validatorEngine = $validatorEngine ?? new FixedTrueMetadataValidationEngineValidation(); + $this->validatorEngine = $validatorEngine ?? new MetadataValidator(); } public function getMetadataForModule(string $moduleId): ?ModuleMetadata diff --git a/_metadata/src/Core/MetadataValidationEngine/FixedTrueMetadataValidationEngineValidation.php b/_metadata/src/Core/MetadataValidationEngine/FixedTrueMetadataValidationEngineValidation.php deleted file mode 100644 index cd296fc..0000000 --- a/_metadata/src/Core/MetadataValidationEngine/FixedTrueMetadataValidationEngineValidation.php +++ /dev/null @@ -1,24 +0,0 @@ -moduleConfigurator()->createMetadata($moduleMetadata); - $app->moduleConfigurator()->setExternalMetadataDto($repoUrl, new ExternalMetadataDto(false, 'performance', ['cache', 'redis'])); + $app->moduleConfigurator()->setExternalMetadataDto($repoUrl, new ExternalMetadataDto(false, 'billing', ['sms', 'organizer'])); $app->metadataUpdater()->synchronizeMetadataFor($moduleIdAsString); $updatedModuleMetadata = $app->metadataUpdater()->getMetadataForModule($moduleIdAsString); self::assertEquals(false, $updatedModuleMetadata->isSynchronizable()); - self::assertEquals('performance', $updatedModuleMetadata->category()); - self::assertEquals(['cache', 'redis'], $updatedModuleMetadata->tags()); + self::assertEquals('billing', $updatedModuleMetadata->category()); + self::assertEquals(['sms', 'organizer'], $updatedModuleMetadata->tags()); } #[Test] public function should_throw_metadata_validation_error() { - self::markTestIncomplete('Implement a real Metadata Validator'); self::expectException(MetadataValidationException::class); self::expectExceptionMessage( - 'Metadata validation error' + 'Category not allowed: performance' ); $app = new MetadataModule( From dd5a6d533285eca4c3a33d477cefe470d4915b21 Mon Sep 17 00:00:00 2001 From: zerai Date: Fri, 20 Sep 2024 09:13:03 +0200 Subject: [PATCH 11/18] Update test (CoverClass attribute). --- .../Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php | 1 + _metadata/tests/Unit/MetadataUpdaterTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/_metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php b/_metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php index e08b5d7..0a0ed32 100644 --- a/_metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php +++ b/_metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php @@ -23,6 +23,7 @@ use PHPUnit\Framework\TestCase; #[CoversClass(MetadataValidator::class)] +#[CoversClass(MetadataValidationException::class)] class MetadataValidatorTest extends TestCase { #[Test] diff --git a/_metadata/tests/Unit/MetadataUpdaterTest.php b/_metadata/tests/Unit/MetadataUpdaterTest.php index 52bec22..aa0a92b 100644 --- a/_metadata/tests/Unit/MetadataUpdaterTest.php +++ b/_metadata/tests/Unit/MetadataUpdaterTest.php @@ -32,6 +32,7 @@ use Ramsey\Uuid\Uuid; #[CoversClass(MetadataUpdater::class)] +#[CoversClass(MetadataValidationException::class)] #[CoversClass(UnreferencedMetadataModuleException::class)] #[UsesClass(MetadataModule::class)] #[UsesClass(FakeForStoringMetadata::class)] From 60da5c99c815b9e577b49dc3cef1007d7da0f4bf Mon Sep 17 00:00:00 2001 From: zerai Date: Fri, 20 Sep 2024 10:53:43 +0200 Subject: [PATCH 12/18] Fix ARK. --- .../VendorDependencies/allowed_in_metadata_adapters.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/phparkitect/VendorDependencies/allowed_in_metadata_adapters.php b/tools/phparkitect/VendorDependencies/allowed_in_metadata_adapters.php index 5dad7c2..9dd0136 100644 --- a/tools/phparkitect/VendorDependencies/allowed_in_metadata_adapters.php +++ b/tools/phparkitect/VendorDependencies/allowed_in_metadata_adapters.php @@ -1,7 +1,13 @@ Date: Fri, 20 Sep 2024 11:04:03 +0200 Subject: [PATCH 13/18] Improved MetadataValidationTest. --- .../MetadataValidatorTest.php | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/_metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php b/_metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php index 0a0ed32..c876e7a 100644 --- a/_metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php +++ b/_metadata/tests/Unit/Core/MetadataValidationEngine/MetadataValidatorTest.php @@ -63,10 +63,10 @@ public function shouldFailWhenThereIsNoTagsKey(): void #[Test] #[DataProvider('invalidTagsDataprovider')] - public function shouldFailWhenTagsIsNotValid(array $metadata): void + public function shouldFailWhenTagsIsNotValid(string $exceptionMessage, array $metadata): void { self::expectException(MetadataValidationException::class); - self::expectExceptionMessage('Metadata \'tags\' should be array type'); + self::expectExceptionMessage($exceptionMessage); $validator = new MetadataValidator(); $validator->validate($metadata); @@ -122,26 +122,34 @@ public static function invalidCategoryDataprovider(): array public static function invalidTagsDataprovider(): array { return [ - [[ + ['Metadata \'tags\' should be array type', [ 'category' => 'billing', 'tags' => null, ]], - [[ + ['Metadata \'tags\' should be array type', [ 'category' => 'billing', 'tags' => 0, ]], - [[ + ['Metadata \'tags\' should be array type', [ 'category' => 'billing', 'tags' => 'string', ]], - [[ + ['Metadata \'tags\' should be array type', [ 'category' => 'billing', 'tags' => new \stdClass(), ]], - [[ + ['Metadata \'tags\' should be array type', [ 'category' => 'billing', 'tags' => 'not allowed', ]], + ['Tag not allowed: not allowed', [ + 'category' => 'billing', + 'tags' => ['not allowed'], + ]], + ['Tag not allowed: not allowed', [ + 'category' => 'billing', + 'tags' => ['sms', 'organizer', 'not allowed'], + ]], ]; } From 9d93d355e0721709c7cca91418f0ec5cb5a15869 Mon Sep 17 00:00:00 2001 From: zerai Date: Sat, 21 Sep 2024 19:16:52 +0200 Subject: [PATCH 14/18] Update ExternalMetadataDto.php (::toArray()). --- .../ExternalMetadataDto.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/_metadata/src/Core/Port/Driven/ForReadingExternalMetadataSource/ExternalMetadataDto.php b/_metadata/src/Core/Port/Driven/ForReadingExternalMetadataSource/ExternalMetadataDto.php index 1f42fbf..dd1ec1f 100644 --- a/_metadata/src/Core/Port/Driven/ForReadingExternalMetadataSource/ExternalMetadataDto.php +++ b/_metadata/src/Core/Port/Driven/ForReadingExternalMetadataSource/ExternalMetadataDto.php @@ -23,4 +23,12 @@ public function __construct( public readonly array $tags, ) { } + + public function toArray(): array + { + return [ + 'category' => $this->category, + 'tags' => $this->tags, + ]; + } } From bbd7a1eb55045cfd42cf6c54eda87f2f1ea62e72 Mon Sep 17 00:00:00 2001 From: zerai Date: Sat, 21 Sep 2024 19:28:03 +0200 Subject: [PATCH 15/18] Init MetadataUpdaterWorkflow. --- .../Core/Process/MetadataUpdaterWorkflow.php | 127 ++++++++++++++++++ .../Core/Process/ModuleMetadataDetected.php | 27 ++++ .../src/Core/Process/UpdateModuleMetadata.php | 25 ++++ .../Process/MetadataUpdaterWorkflowTest.php | 65 +++++++++ 4 files changed, 244 insertions(+) create mode 100644 _metadata/src/Core/Process/MetadataUpdaterWorkflow.php create mode 100644 _metadata/src/Core/Process/ModuleMetadataDetected.php create mode 100644 _metadata/src/Core/Process/UpdateModuleMetadata.php create mode 100644 _metadata/tests/Unit/Core/Process/MetadataUpdaterWorkflowTest.php diff --git a/_metadata/src/Core/Process/MetadataUpdaterWorkflow.php b/_metadata/src/Core/Process/MetadataUpdaterWorkflow.php new file mode 100644 index 0000000..10cbc6b --- /dev/null +++ b/_metadata/src/Core/Process/MetadataUpdaterWorkflow.php @@ -0,0 +1,127 @@ +logger->info('MetadataUpdater - initialize update process for module id: ' . $command->moduleId); + + try { + $repository = Repository::createFromRepositoryUrl($command->repositoryUrl); + + if ($repository->isSupported()) { + return $command; + } else { + $this->logger->info('MetadataUpdater - unsupported repository detected url: ' . $command->repositoryUrl); + } + } catch (UnexpectedValueException $exception) { + $this->logger->info('MetadataUpdater - error: ' . $exception->getMessage()); + } + + return null; + } + + #[InternalHandler( + inputChannelName: 'process.enrich', + outputChannelName: 'process.readMetadata', + changingHeaders: true, + )] + public function enrichCommand(UpdateModuleMetadata $command): array + { + $repository = Repository::createFromRepositoryUrl($command->repositoryUrl); + + return [ + 'repository' => $repository, + ]; + } + + #[InternalHandler( + inputChannelName: 'process.readMetadata', + outputChannelName: 'process.validateMetadata', + changingHeaders: true, + )] + public function readMetadata( + UpdateModuleMetadata $command, + ): ?array { + /** + * if no error enrich command or fail + */ + $metadataDto = $this->metadataReader->readMetadataFromExternalSource($command->repositoryUrl); + + return [ + 'metadata_dto' => $metadataDto, + ]; + } + + #[InternalHandler( + inputChannelName: 'process.validateMetadata', + outputChannelName: 'process.notifyProcessResult', + changingHeaders: true, + )] + public function validateMetadata( + #[Header('metadata_dto')] + ExternalMetadataDto $metadataDto, + MetadataValidator $metadataValidator + ): array { + $metadataValidationResult = $metadataValidator->validate($metadataDto->toArray()); + + return [ + 'metadata_validation_result' => $metadataValidationResult, + ]; + } + + #[InternalHandler( + inputChannelName: 'process.notifyProcessResult', + )] + public function triggerMetadataUpdate( + UpdateModuleMetadata $command, + #[Header('metadata_validation_result')] + bool $validationStatus, + #[Header('metadata_dto')] + ExternalMetadataDto $metadataDto, + EventBus $eventBus + ): void { + if ($validationStatus) { + $eventBus->publish(new ModuleMetadataDetected($command->moduleId, $metadataDto)); + } else { + echo 'complete with no metadata'; + //$eventBus->publish(new ModuleMetadataDetected($command->moduleId, $metadataDto)); + } + } +} diff --git a/_metadata/src/Core/Process/ModuleMetadataDetected.php b/_metadata/src/Core/Process/ModuleMetadataDetected.php new file mode 100644 index 0000000..5701240 --- /dev/null +++ b/_metadata/src/Core/Process/ModuleMetadataDetected.php @@ -0,0 +1,27 @@ +toString(); + $repositoryUrl = 'https://github.com/MedicalMundi/oe-module-todo-list'; + $aMetadataDto = new ExternalMetadataDto( + enableSync: true, + category: 'billing', + tags: ['sms', 'fax'] + ); + $expectedMessage = new ModuleMetadataDetected($moduleId, $aMetadataDto); + $stubAdapterForReadingExternalMetadataSource->setExternalMetadataDto($repositoryUrl, $aMetadataDto); + $ecotoneLite = EcotoneLite::bootstrapFlowTesting( + [MetadataUpdaterWorkflow::class], + [ + MetadataUpdaterWorkflow::class => new MetadataUpdaterWorkflow(new NullLogger(), $stubAdapterForReadingExternalMetadataSource), + MetadataValidator::class => new MetadataValidator(), + ] + ); + + + $this->assertEquals( + [$expectedMessage], + $ecotoneLite + ->sendCommand(new UpdateModuleMetadata($moduleId, $repositoryUrl)) + ->getRecordedEvents() + ); + } +} From ca89e0bd9d8a5617402e401bbba0fd411c289e7e Mon Sep 17 00:00:00 2001 From: zerai Date: Sun, 22 Sep 2024 02:04:25 +0200 Subject: [PATCH 16/18] Added ComposerJsonFile.php VO. --- .../ComposerJsonFile.php | 69 ++++++++++ .../ComposerJsonFile/ComposerJsonFileTest.php | 122 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 _metadata/src/AdapterForReadingExternalMetadataSource/ComposerJsonFile.php create mode 100644 _metadata/tests/Unit/ComposerJsonFile/ComposerJsonFileTest.php diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/ComposerJsonFile.php b/_metadata/src/AdapterForReadingExternalMetadataSource/ComposerJsonFile.php new file mode 100644 index 0000000..71794ed --- /dev/null +++ b/_metadata/src/AdapterForReadingExternalMetadataSource/ComposerJsonFile.php @@ -0,0 +1,69 @@ +source, true); + + if (\array_key_exists('extra', $data)) { + $extraSection = (array) $data['extra']; + + + if (\array_key_exists('openemr-module', $extraSection)) { + $openEmrModuleSection = (array) $extraSection['openemr-module']; + + if (\array_key_exists('metadata', $openEmrModuleSection)) { + $metadataSection = (array) $openEmrModuleSection['metadata']; + + if (\array_key_exists('oe-modules.com', $metadataSection)) { + $result = true; + } + } + } + } + + return $result; + } + + public function getMetadata(): array + { + $data = (array) json_decode($this->source, true); + + /** @psalm-suppress MixedArrayAccess */ + return (array) $data['extra']['openemr-module']['metadata']['oe-modules.com']; + } +} diff --git a/_metadata/tests/Unit/ComposerJsonFile/ComposerJsonFileTest.php b/_metadata/tests/Unit/ComposerJsonFile/ComposerJsonFileTest.php new file mode 100644 index 0000000..65f1186 --- /dev/null +++ b/_metadata/tests/Unit/ComposerJsonFile/ComposerJsonFileTest.php @@ -0,0 +1,122 @@ +hasMetadata()); + } + + #[Test] + #[DataProvider(('jsonWithMetadataDataprovider'))] + public function shouldReturnMetadata(): void + { + $json = '{ + "extra": { + "openemr-module": { + "metadata": { + "oe-modules.com": { + "category": "miscellaneous", + "tags": [ + "todo", + "organizer", + "scheduler" + ] + } + } + } + } + }' + ; + $expectedResult = [ + 'category' => 'miscellaneous', + 'tags' => ['todo', 'organizer', 'scheduler'], + ]; + $sut = ComposerJsonFile::createFromJson($json); + + self::assertSame($expectedResult, $sut->getMetadata()); + } + + public static function jsonWithoutMetadataDataprovider(): iterable + { + return [ + [false, ''], + [false, ' '], + [false, '{}'], + [false, '{"foo": "bar"}'], + ]; + } + + public static function jsonWithMetadataDataprovider(): iterable + { + return [ + [true, '{ + "extra": { + "openemr-module": { + "metadata": { + "oe-modules.com": { + "category": "miscellaneous", + "tags": [ + "todo", + "organizer", + "scheduler" + ] + } + } + } + } + }'], + [true, '{ + "extra": { + "openemr-module": { + "metadata": { + "oe-modules.com": { + "category": "miscellaneous", + "tags": [ + "todo", + "organizer", + "scheduler" + ] + }, + "other-marketplace.com": { + "category": "miscellaneous", + "tags": [ + "todo", + "organizer", + "scheduler" + ] + } + } + } + } + }'], + ]; + } +} From 3103a72b3b4ea5d74bee6a6201413b6e74378519 Mon Sep 17 00:00:00 2001 From: zerai Date: Sun, 22 Sep 2024 14:09:28 +0200 Subject: [PATCH 17/18] MetadataUpdaterWorkflow improved. --- .../MetadataUpdateCommand.php | 4 +- ...dapterForReadingExternalMetadataSource.php | 88 ++++++- .../StatelessWorkflowProcess.php | 216 ------------------ ...dapterForReadingExternalMetadataSource.php | 14 +- .../ForReadingExternalMetadataSource.php | 2 +- .../ModuleMetadataUpdateAbortedWithError.php} | 6 +- ...dataUpdateCompletedWithInvalidMetadata.php | 24 ++ ...leMetadataUpdateCompletedWithMetadata.php} | 4 +- ...MetadataUpdateCompletedWithoutMetadata.php | 24 ++ .../Process/MetadataSynchronizerWorkflow.php | 163 +++++++++++++ .../Core/Process/MetadataUpdaterWorkflow.php | 127 ---------- ...data.php => StartModuleMetadataUpdate.php} | 2 +- .../ValueObject}/ComposerJsonFile.php | 22 +- .../Infrastructure/Framework/services.yaml | 1 + .../MetadataSynchronizerWorkflowTest.php | 202 ++++++++++++++++ .../Process/MetadataUpdaterWorkflowTest.php | 65 ------ .../ValueObject}/ComposerJsonFileTest.php | 23 +- psalm-baseline.xml | 12 +- .../allowed_in_metadata_adapters.php | 1 + .../allowed_in_metadata_core.php | 3 + 20 files changed, 561 insertions(+), 442 deletions(-) delete mode 100644 _metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php rename _metadata/src/{AdapterForReadingExternalMetadataSource/UpdateModuleMetadata.php => Core/Process/Event/ModuleMetadataUpdateAbortedWithError.php} (83%) create mode 100644 _metadata/src/Core/Process/Event/ModuleMetadataUpdateCompletedWithInvalidMetadata.php rename _metadata/src/Core/Process/{ModuleMetadataDetected.php => Event/ModuleMetadataUpdateCompletedWithMetadata.php} (90%) create mode 100644 _metadata/src/Core/Process/Event/ModuleMetadataUpdateCompletedWithoutMetadata.php create mode 100644 _metadata/src/Core/Process/MetadataSynchronizerWorkflow.php delete mode 100644 _metadata/src/Core/Process/MetadataUpdaterWorkflow.php rename _metadata/src/Core/Process/{UpdateModuleMetadata.php => StartModuleMetadataUpdate.php} (95%) rename _metadata/src/{AdapterForReadingExternalMetadataSource => Core/ValueObject}/ComposerJsonFile.php (77%) create mode 100644 _metadata/tests/Unit/Core/Process/MetadataSynchronizerWorkflowTest.php delete mode 100644 _metadata/tests/Unit/Core/Process/MetadataUpdaterWorkflowTest.php rename _metadata/tests/Unit/{ComposerJsonFile => Core/ValueObject}/ComposerJsonFileTest.php (88%) diff --git a/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php b/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php index fa34b68..161e01c 100644 --- a/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php +++ b/_metadata/src/AdapterCli/ForSynchronizingMetadata/MetadataUpdateCommand.php @@ -17,7 +17,7 @@ use Ecotone\Messaging\Attribute\ConsoleCommand; use Ecotone\Modelling\CommandBus; -use Metadata\AdapterForReadingExternalMetadataSource\UpdateModuleMetadata; +use Metadata\Core\Process\StartModuleMetadataUpdate; class MetadataUpdateCommand { @@ -35,7 +35,7 @@ public function execute(CommandBus $commandBus): void // with metadata $repoUrl = 'https://github.com/MedicalMundi/oe-module-todo-list'; - $command = new UpdateModuleMetadata($moduleId, $repoUrl); + $command = new StartModuleMetadataUpdate($moduleId, $repoUrl); $commandBus->send($command); } diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php b/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php index ad158b4..67bad22 100644 --- a/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php +++ b/_metadata/src/AdapterForReadingExternalMetadataSource/GithubAdapterForReadingExternalMetadataSource.php @@ -15,27 +15,99 @@ namespace Metadata\AdapterForReadingExternalMetadataSource; +use Github\Api\Repo; use Github\Client as GithubClient; use Metadata\Core\Port\Driven\ForReadingExternalMetadataSource\ExternalMetadataDto; use Metadata\Core\Port\Driven\ForReadingExternalMetadataSource\ForReadingExternalMetadataSource; use Metadata\Core\Port\Driven\ForReadingExternalMetadataSource\MetadataReaderException; +use Metadata\Core\ValueObject\ComposerJsonFile; +use Metadata\Core\ValueObject\Repository; +use Nyholm\Psr7\Request; +use Psr\Http\Client\ClientInterface; class GithubAdapterForReadingExternalMetadataSource implements ForReadingExternalMetadataSource { public function __construct( - private readonly GithubClient $githubClient + private readonly GithubClient $githubClient, + private readonly ClientInterface $httpclient, ) { } - public function readMetadataFromExternalSource(string $moduleUrl): ExternalMetadataDto + public function readMetadataFromExternalSource(string $moduleUrl): ?ExternalMetadataDto + { + $repository = Repository::createFromRepositoryUrl($moduleUrl); + + $defaultBranch = $this->getDefaultBranchName($repository); + + /** + * TODO: refactor + * $defaultBranch dovrebbe essere sempre un tipo string, + * la funzione sopra non dovrebbe tornare null + * ma eccezione di tipo http del client api + * o eccezione per dati non processabili + */ + if (null === $defaultBranch) { + return null; + } + + $composerJsonFileContent = $this->downloadComposerJsonFileContent($moduleUrl, $defaultBranch); + + $composerFile = ComposerJsonFile::createFromJson($composerJsonFileContent); + + if (! $composerFile->hasMetadata()) { + return null; + } + + $extractedMetadata = (array) $composerFile->getMetadata(); + $metadataDto = new ExternalMetadataDto( + enableSync: true, + category: (string) $extractedMetadata['category'], + tags: (array) $extractedMetadata['tags'], + ); + + return $metadataDto; + } + + private function doDownloadHttpRequest(string $url): string + { + $request = new Request('GET', $url); + + $response = $this->httpclient->sendRequest($request); + + return $response->getBody()->getContents(); + } + + private function downloadComposerJsonFileContent(string $url, string $reference): string { try { - //get composer.json - // extract metadata section - // create metadataDto - return new ExternalMetadataDto(true, 'a category', ['tag-1', 'tag-2']); - } catch (\RuntimeException $exception) { - throw new MetadataReaderException($exception->getMessage()); + $repository = Repository::createFromRepositoryUrl($url); + /** @var Repo $repoApi */ + $repoApi = $this->githubClient->api('repo'); + $fileInfo = (array) $repoApi + ->contents() + ->show($repository->getUsername(), $repository->getName(), 'composer.json', $reference); + + $composerJsonFileContent = $this->doDownloadHttpRequest((string) $fileInfo['download_url']); + + return $composerJsonFileContent; + } catch (\Exception $exception) { + throw new MetadataReaderException('Impossible to read metadata from: ' . $url . ' error: ' . $exception->getMessage()); } } + + private function getDefaultBranchName(Repository $repository): ? string + { + $data = []; + try { + $data = $this->githubClient->repo()->show($repository->getUsername(), $repository->getName()); + } catch (\Exception $exception) { + throw $exception; + } + + if (! \array_key_exists('default_branch', $data)) { + return null; + } + + return (string) $data['default_branch']; + } } diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php b/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php deleted file mode 100644 index f407108..0000000 --- a/_metadata/src/AdapterForReadingExternalMetadataSource/StatelessWorkflowProcess.php +++ /dev/null @@ -1,216 +0,0 @@ -logger->info('MetadataUpdater - init update for module id: ' . $command->moduleId); - - return $command; - } - - #[InternalHandler( - inputChannelName: 'module.enrich', - outputChannelName: 'module.getDefaultBranch', - changingHeaders: true - )] - public function enrichCommand(UpdateModuleMetadata $command, GithubClient $githubClient): array - { - /** - * TODO: handle failure - */ - $repository = Repository::createFromRepositoryUrl($command->repositoryUrl); - - - return [ - 'repository' => $repository, - ]; - } - - #[InternalHandler( - inputChannelName: 'module.getDefaultBranch', - outputChannelName: 'module.getComposerJson', - changingHeaders: true, - )] - public function getDefaultBranch( - UpdateModuleMetadata $command, - #[Header('repository')] - Repository $repository, - GithubClient $githubClient - ): ?array { - /** - * TODO: handle failure - */ - try { - $data = $githubClient->repo()->show($repository->getUsername(), $repository->getName()); - if (\array_key_exists('default_branch', $data)) { - return [ - 'default_branch' => (string) $data['default_branch'], - ]; - } - } catch (\Exception $exception) { - $this->logger->error('Metadata updater error on module id: ' . $command->moduleId . ' ' . $exception->getMessage()); - } - - return null; - } - - #[InternalHandler( - inputChannelName: 'module.getComposerJson', - outputChannelName: 'module.extractMetadata', - changingHeaders: true, - )] - public function getComposerJson( - UpdateModuleMetadata $command, - #[Header('repository')] - Repository $repository, - #[Header('default_branch')] - string $defaultBranchName, - ClientInterface $httpClient, - GithubClient $githubClient, - ): ?array { - $path = 'composer.json'; - - $reference = $defaultBranchName; - - /** - * TODO: handle failure - */ - try { - /** @var Repo $repoApi */ - $repoApi = $githubClient->api('repo'); - $fileInfo = (array) $repoApi - ->contents() - ->show($repository->getUsername(), $repository->getName(), $path, $reference); - $content = $this->getComposerJsonFileContent((string) $fileInfo['download_url'], $httpClient); - - return [ - 'composer_json_content' => $content, - ]; - } catch (\Exception $exception) { - $this->logger->error('Metadata updater error on module id: ' . $command->moduleId . ' ' . $exception->getMessage()); - } - - return null; - } - - #[InternalHandler( - inputChannelName: 'module.extractMetadata', - outputChannelName: 'module.validateMetadata', - changingHeaders: true, - )] - public function extractMetadata( - UpdateModuleMetadata $command, - #[Header('composer_json_content')] - string $composerJsonContent, - ): ?array { - $ar = (array) json_decode($composerJsonContent, true); - - if ($this->hasMetadata($ar)) { - return [ - 'metadata' => $ar['extra']['openemr-module']['metadata']['oe-modules.com'], - ]; - } - - return null; - } - - #[InternalHandler(inputChannelName: 'module.validateMetadata')] - public function validateMetadata(#[Header('metadata')] array $metadata, MetadataValidator $metadataValidator): ?UpdateModuleMetadata - { - if ($metadataValidator->validate($metadata)) { - echo 'metadata pass'; - } else { - echo 'metadata not pass'; - } - - return null; - } - - #[InternalHandler(inputChannelName: 'image.upload')] - public function uploadImage(UpdateModuleMetadata $command): void - { - //$imageUploader->uploadImage($command->path); - echo 'yyyy'; - } - - private function getComposerJsonFileContent(string $url, ClientInterface $httpclient): string - { - $request = new Request('GET', $url); - - $response = $httpclient->sendRequest($request); - - $x = $response->getBody()->getContents(); - - return $x; - } - - /** - * TODO: refactor - */ - private function hasMetadata(array $data): bool - { - $result = false; - - if (\array_key_exists('extra', $data)) { - $extraSection = (array) $data['extra']; - - - if (\array_key_exists('openemr-module', $extraSection)) { - $openEmrModuleSection = (array) $extraSection['openemr-module']; - - if (\array_key_exists('metadata', $openEmrModuleSection)) { - $metadataSection = (array) $openEmrModuleSection['metadata']; - - if (\array_key_exists('oe-modules.com', $metadataSection)) { - $result = true; - } - } - } - } - - return $result; - } -} diff --git a/_metadata/src/AdapterForReadingExternalMetadataSourceStub/StubAdapterForReadingExternalMetadataSource.php b/_metadata/src/AdapterForReadingExternalMetadataSourceStub/StubAdapterForReadingExternalMetadataSource.php index 3454b4c..1d128a4 100644 --- a/_metadata/src/AdapterForReadingExternalMetadataSourceStub/StubAdapterForReadingExternalMetadataSource.php +++ b/_metadata/src/AdapterForReadingExternalMetadataSourceStub/StubAdapterForReadingExternalMetadataSource.php @@ -22,16 +22,26 @@ class StubAdapterForReadingExternalMetadataSource implements ForReadingExternalM { public function __construct( private array $metadataDtosIndexedByUrl = [], + private array $exceptions = [], ) { } - public function readMetadataFromExternalSource(string $moduleUrl): ExternalMetadataDto + public function readMetadataFromExternalSource(string $moduleUrl): ?ExternalMetadataDto { + if (0 < \count($this->exceptions)) { + throw $this->exceptions[0]; + } + return $this->metadataDtosIndexedByUrl[$moduleUrl]; } - public function setExternalMetadataDto(string $url, ExternalMetadataDto $externalMetadataDto): void + public function setExternalMetadataDto(string $url, ?ExternalMetadataDto $externalMetadataDto): void { $this->metadataDtosIndexedByUrl[$url] = $externalMetadataDto; } + + public function setException(object $exception): void + { + $this->exceptions[] = $exception; + } } diff --git a/_metadata/src/Core/Port/Driven/ForReadingExternalMetadataSource/ForReadingExternalMetadataSource.php b/_metadata/src/Core/Port/Driven/ForReadingExternalMetadataSource/ForReadingExternalMetadataSource.php index 87bb21f..5232a55 100644 --- a/_metadata/src/Core/Port/Driven/ForReadingExternalMetadataSource/ForReadingExternalMetadataSource.php +++ b/_metadata/src/Core/Port/Driven/ForReadingExternalMetadataSource/ForReadingExternalMetadataSource.php @@ -20,5 +20,5 @@ interface ForReadingExternalMetadataSource /** * @throws MetadataReaderException */ - public function readMetadataFromExternalSource(string $moduleUrl): ExternalMetadataDto; + public function readMetadataFromExternalSource(string $moduleUrl): ?ExternalMetadataDto; } diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/UpdateModuleMetadata.php b/_metadata/src/Core/Process/Event/ModuleMetadataUpdateAbortedWithError.php similarity index 83% rename from _metadata/src/AdapterForReadingExternalMetadataSource/UpdateModuleMetadata.php rename to _metadata/src/Core/Process/Event/ModuleMetadataUpdateAbortedWithError.php index 4da3340..cf828da 100644 --- a/_metadata/src/AdapterForReadingExternalMetadataSource/UpdateModuleMetadata.php +++ b/_metadata/src/Core/Process/Event/ModuleMetadataUpdateAbortedWithError.php @@ -13,13 +13,13 @@ * @license https://github.com/MedicalMundi/marketplace-engine/blob/main/LICENSE MIT */ -namespace Metadata\AdapterForReadingExternalMetadataSource; +namespace Metadata\Core\Process\Event; -class UpdateModuleMetadata +class ModuleMetadataUpdateAbortedWithError { public function __construct( public readonly string $moduleId, - public readonly string $repositoryUrl + public readonly mixed $error, ) { } } diff --git a/_metadata/src/Core/Process/Event/ModuleMetadataUpdateCompletedWithInvalidMetadata.php b/_metadata/src/Core/Process/Event/ModuleMetadataUpdateCompletedWithInvalidMetadata.php new file mode 100644 index 0000000..eb842a0 --- /dev/null +++ b/_metadata/src/Core/Process/Event/ModuleMetadataUpdateCompletedWithInvalidMetadata.php @@ -0,0 +1,24 @@ +logger->info('Metadata synchronizer - initialize update process for module id: ' . $command->moduleId); + + try { + $repository = Repository::createFromRepositoryUrl($command->repositoryUrl); + + if ($repository->isSupported()) { + return $command; + } else { + $eventBus->publish( + new ModuleMetadataUpdateAbortedWithError($command->moduleId, 'Unsupported githost service provider: ' . $repository->getSource()) + ); + + $this->logger->info('Metadata synchronizer - unsupported repository detected url: ' . $command->repositoryUrl); + } + } catch (UnexpectedValueException $exception) { + $eventBus->publish( + new ModuleMetadataUpdateAbortedWithError($command->moduleId, $exception->getMessage()) + ); + $this->logger->info('Metadata synchronizer - error: ' . $exception->getMessage()); + } + + return null; + } + + #[InternalHandler( + inputChannelName: 'process.enrich', + outputChannelName: 'process.readMetadata', + changingHeaders: true, + )] + public function enrichCommand(StartModuleMetadataUpdate $command): array + { + $repository = Repository::createFromRepositoryUrl($command->repositoryUrl); + + return [ + 'repository' => $repository, + ]; + } + + #[InternalHandler( + inputChannelName: 'process.readMetadata', + outputChannelName: 'process.validateMetadata', + changingHeaders: true, + )] + public function readMetadata( + StartModuleMetadataUpdate $command, + EventBus $eventBus + ): ?array { + try { + $metadataDto = $this->metadataReader->readMetadataFromExternalSource($command->repositoryUrl); + + + if (null === $metadataDto) { + $eventBus->publish( + new ModuleMetadataUpdateCompletedWithoutMetadata($command->moduleId) + ); + } else { + return [ + 'metadata_dto' => $metadataDto, + ]; + } + } catch (MetadataReaderException $exception) { + $eventBus->publish( + new ModuleMetadataUpdateAbortedWithError($command->moduleId, $exception->getMessage()) + ); + $this->logger->info('Metadata synchronizer - error: ' . $exception->getMessage()); + } + + + return null; + } + + #[InternalHandler( + inputChannelName: 'process.validateMetadata', + outputChannelName: 'process.notifyProcessResult', + changingHeaders: true, + )] + public function validateMetadata( + StartModuleMetadataUpdate $command, + #[Header('metadata_dto')] + ExternalMetadataDto $metadataDto, + MetadataValidator $metadataValidator, + EventBus $eventBus, + ): ?array { + try { + $metadataValidationResult = $metadataValidator->validate($metadataDto->toArray()); + + return [ + 'metadata_validation_result' => $metadataValidationResult, + ]; + } catch (MetadataValidationException $exception) { + $eventBus->publish( + new ModuleMetadataUpdateCompletedWithInvalidMetadata($command->moduleId) + ); + $this->logger->info('Metadata synchronizer - error: ' . $exception->getMessage()); + } + + return null; + } + + #[InternalHandler( + inputChannelName: 'process.notifyProcessResult', + )] + public function triggerMetadataUpdate( + StartModuleMetadataUpdate $command, + #[Header('metadata_validation_result')] + bool $validationStatus, + #[Header('metadata_dto')] + ExternalMetadataDto $metadataDto, + EventBus $eventBus + ): void { + if ($validationStatus) { + $eventBus->publish(new ModuleMetadataUpdateCompletedWithMetadata($command->moduleId, $metadataDto)); + } + } +} diff --git a/_metadata/src/Core/Process/MetadataUpdaterWorkflow.php b/_metadata/src/Core/Process/MetadataUpdaterWorkflow.php deleted file mode 100644 index 10cbc6b..0000000 --- a/_metadata/src/Core/Process/MetadataUpdaterWorkflow.php +++ /dev/null @@ -1,127 +0,0 @@ -logger->info('MetadataUpdater - initialize update process for module id: ' . $command->moduleId); - - try { - $repository = Repository::createFromRepositoryUrl($command->repositoryUrl); - - if ($repository->isSupported()) { - return $command; - } else { - $this->logger->info('MetadataUpdater - unsupported repository detected url: ' . $command->repositoryUrl); - } - } catch (UnexpectedValueException $exception) { - $this->logger->info('MetadataUpdater - error: ' . $exception->getMessage()); - } - - return null; - } - - #[InternalHandler( - inputChannelName: 'process.enrich', - outputChannelName: 'process.readMetadata', - changingHeaders: true, - )] - public function enrichCommand(UpdateModuleMetadata $command): array - { - $repository = Repository::createFromRepositoryUrl($command->repositoryUrl); - - return [ - 'repository' => $repository, - ]; - } - - #[InternalHandler( - inputChannelName: 'process.readMetadata', - outputChannelName: 'process.validateMetadata', - changingHeaders: true, - )] - public function readMetadata( - UpdateModuleMetadata $command, - ): ?array { - /** - * if no error enrich command or fail - */ - $metadataDto = $this->metadataReader->readMetadataFromExternalSource($command->repositoryUrl); - - return [ - 'metadata_dto' => $metadataDto, - ]; - } - - #[InternalHandler( - inputChannelName: 'process.validateMetadata', - outputChannelName: 'process.notifyProcessResult', - changingHeaders: true, - )] - public function validateMetadata( - #[Header('metadata_dto')] - ExternalMetadataDto $metadataDto, - MetadataValidator $metadataValidator - ): array { - $metadataValidationResult = $metadataValidator->validate($metadataDto->toArray()); - - return [ - 'metadata_validation_result' => $metadataValidationResult, - ]; - } - - #[InternalHandler( - inputChannelName: 'process.notifyProcessResult', - )] - public function triggerMetadataUpdate( - UpdateModuleMetadata $command, - #[Header('metadata_validation_result')] - bool $validationStatus, - #[Header('metadata_dto')] - ExternalMetadataDto $metadataDto, - EventBus $eventBus - ): void { - if ($validationStatus) { - $eventBus->publish(new ModuleMetadataDetected($command->moduleId, $metadataDto)); - } else { - echo 'complete with no metadata'; - //$eventBus->publish(new ModuleMetadataDetected($command->moduleId, $metadataDto)); - } - } -} diff --git a/_metadata/src/Core/Process/UpdateModuleMetadata.php b/_metadata/src/Core/Process/StartModuleMetadataUpdate.php similarity index 95% rename from _metadata/src/Core/Process/UpdateModuleMetadata.php rename to _metadata/src/Core/Process/StartModuleMetadataUpdate.php index c2b9b60..32d8f3e 100644 --- a/_metadata/src/Core/Process/UpdateModuleMetadata.php +++ b/_metadata/src/Core/Process/StartModuleMetadataUpdate.php @@ -15,7 +15,7 @@ namespace Metadata\Core\Process; -class UpdateModuleMetadata +class StartModuleMetadataUpdate { public function __construct( public readonly string $moduleId, diff --git a/_metadata/src/AdapterForReadingExternalMetadataSource/ComposerJsonFile.php b/_metadata/src/Core/ValueObject/ComposerJsonFile.php similarity index 77% rename from _metadata/src/AdapterForReadingExternalMetadataSource/ComposerJsonFile.php rename to _metadata/src/Core/ValueObject/ComposerJsonFile.php index 71794ed..ab068ff 100644 --- a/_metadata/src/AdapterForReadingExternalMetadataSource/ComposerJsonFile.php +++ b/_metadata/src/Core/ValueObject/ComposerJsonFile.php @@ -13,20 +13,17 @@ * @license https://github.com/MedicalMundi/marketplace-engine/blob/main/LICENSE MIT */ -namespace Metadata\AdapterForReadingExternalMetadataSource; +namespace Metadata\Core\ValueObject; class ComposerJsonFile { private function __construct( - private string $source, + private string $value, ) { } public static function createFromJson(string $json): self { - /** - * TODO: check if valid json - */ return new self($json); } @@ -37,12 +34,11 @@ public function hasMetadata(): bool { $result = false; - $data = (array) json_decode($this->source, true); + $data = (array) json_decode($this->value, true); if (\array_key_exists('extra', $data)) { $extraSection = (array) $data['extra']; - if (\array_key_exists('openemr-module', $extraSection)) { $openEmrModuleSection = (array) $extraSection['openemr-module']; @@ -59,11 +55,15 @@ public function hasMetadata(): bool return $result; } - public function getMetadata(): array + public function getMetadata(): ?array { - $data = (array) json_decode($this->source, true); + $data = (array) json_decode($this->value, true); + + if ($this->hasMetadata()) { + /** @psalm-suppress MixedArrayAccess */ + return (array) $data['extra']['openemr-module']['metadata']['oe-modules.com']; + } - /** @psalm-suppress MixedArrayAccess */ - return (array) $data['extra']['openemr-module']['metadata']['oe-modules.com']; + return null; } } diff --git a/_metadata/src/Infrastructure/Framework/services.yaml b/_metadata/src/Infrastructure/Framework/services.yaml index f6c62f6..4c56db7 100644 --- a/_metadata/src/Infrastructure/Framework/services.yaml +++ b/_metadata/src/Infrastructure/Framework/services.yaml @@ -19,3 +19,4 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + Metadata\Core\Port\Driven\ForReadingExternalMetadataSource\ForReadingExternalMetadataSource: '@Metadata\AdapterForReadingExternalMetadataSource\GithubAdapterForReadingExternalMetadataSource' \ No newline at end of file diff --git a/_metadata/tests/Unit/Core/Process/MetadataSynchronizerWorkflowTest.php b/_metadata/tests/Unit/Core/Process/MetadataSynchronizerWorkflowTest.php new file mode 100644 index 0000000..9650e45 --- /dev/null +++ b/_metadata/tests/Unit/Core/Process/MetadataSynchronizerWorkflowTest.php @@ -0,0 +1,202 @@ +stubAdapterForReadingExternalMetadataSource = new StubAdapterForReadingExternalMetadataSource(); + $this->messagingSystem = EcotoneLite::bootstrapFlowTesting( + [MetadataSynchronizerWorkflow::class], + [ + MetadataSynchronizerWorkflow::class => new MetadataSynchronizerWorkflow(new NullLogger(), $this->stubAdapterForReadingExternalMetadataSource), + MetadataValidator::class => new MetadataValidator(), + ] + ); + } + + #[Test] + public function shouldNotifyProcessCompleteWithMetadata() + { + $moduleId = Uuid::uuid4()->toString(); + $aStubbedMetadataDto = new ExternalMetadataDto( + enableSync: true, + category: 'billing', + tags: ['sms', 'fax'] + ); + $this->stubAdapterForReadingExternalMetadataSource->setExternalMetadataDto(self::A_VALID_REPOSITORY_URL, $aStubbedMetadataDto); + $expectedEvent = new ModuleMetadataUpdateCompletedWithMetadata($moduleId, $aStubbedMetadataDto); + + + $result = $this->messagingSystem + ->sendCommand(new StartModuleMetadataUpdate($moduleId, self::A_VALID_REPOSITORY_URL)) + ->getRecordedEvents(); + + + $this->assertEquals( + [$expectedEvent], + $result + ); + } + + #[Test] + public function shouldNotifyProcessCompletedWithoutMetadata() + { + $moduleId = Uuid::uuid4()->toString(); + $aStubbedMetadataDto = null; + $this->stubAdapterForReadingExternalMetadataSource->setExternalMetadataDto(self::A_VALID_REPOSITORY_URL, $aStubbedMetadataDto); + $expectedEvent = new ModuleMetadataUpdateCompletedWithoutMetadata($moduleId); + + + $result = $this->messagingSystem + ->sendCommand(new StartModuleMetadataUpdate($moduleId, self::A_VALID_REPOSITORY_URL)) + ->getRecordedEvents(); + + + $this->assertEquals( + [$expectedEvent], + $result + ); + } + + #[Test] + public function shouldNotifyProcessCompletedWithInvalidMetadata() + { + $moduleId = Uuid::uuid4()->toString(); + $aStubbedMetadataDto = new ExternalMetadataDto( + enableSync: true, + category: 'invalid-category', + tags: ['invalid-tag-1', 'invalid-tag-2'] + ); + $this->stubAdapterForReadingExternalMetadataSource->setExternalMetadataDto(self::A_VALID_REPOSITORY_URL, $aStubbedMetadataDto); + $expectedEvent = new ModuleMetadataUpdateCompletedWithInvalidMetadata($moduleId); + + + $result = $this->messagingSystem + ->sendCommand(new StartModuleMetadataUpdate($moduleId, self::A_VALID_REPOSITORY_URL)) + ->getRecordedEvents(); + + + $this->assertEquals( + [$expectedEvent], + $result + ); + } + + #[Test] + public function shouldNotifyProcessAbortedWithErrorWhenRepositoryUrlIsInvalid() + { + $moduleId = Uuid::uuid4()->toString(); + $expectedEvent = new ModuleMetadataUpdateAbortedWithError($moduleId, 'Impossible to fetch package by "' . self::AN_INVALID_REPOSITORY_URL . '" repository.'); + + + $result = $this->messagingSystem + ->sendCommand(new StartModuleMetadataUpdate($moduleId, self::AN_INVALID_REPOSITORY_URL)) + ->getRecordedEvents(); + + + $this->assertEquals( + [$expectedEvent], + $result + ); + } + + #[Test] + public function shouldNotifyProcessAbortedWithErrorWhenGitHostingServiceProviderIsNotSupported() + { + $moduleId = Uuid::uuid4()->toString(); + $expectedEvent = new ModuleMetadataUpdateAbortedWithError($moduleId, 'Unsupported githost service provider: unsupportedgithostingservice.com'); + + + $result = $this->messagingSystem + ->sendCommand(new StartModuleMetadataUpdate($moduleId, self::AN_UNSUPPORTED_SERVICE_REPOSITORY_URL)) + ->getRecordedEvents(); + + + $this->assertEquals( + [$expectedEvent], + $result + ); + } + + #[Test] + public function shouldNotifyProcessAbortedWithErrorWhenMetadataReaderHasIssue() + { + $moduleId = Uuid::uuid4()->toString(); + $this->stubAdapterForReadingExternalMetadataSource->setException(new MetadataReaderException('Network error')); + $expectedEvent = new ModuleMetadataUpdateAbortedWithError($moduleId, 'Network error'); + + + $result = $this->messagingSystem + ->sendCommand(new StartModuleMetadataUpdate($moduleId, self::A_VALID_REPOSITORY_URL)) + ->getRecordedEvents(); + + + $this->assertEquals( + [$expectedEvent], + $result + ); + } + + protected function tearDown(): void + { + $this->stubAdapterForReadingExternalMetadataSource = null; + $this->messagingSystem = null; + } +} diff --git a/_metadata/tests/Unit/Core/Process/MetadataUpdaterWorkflowTest.php b/_metadata/tests/Unit/Core/Process/MetadataUpdaterWorkflowTest.php deleted file mode 100644 index 4e1f37a..0000000 --- a/_metadata/tests/Unit/Core/Process/MetadataUpdaterWorkflowTest.php +++ /dev/null @@ -1,65 +0,0 @@ -toString(); - $repositoryUrl = 'https://github.com/MedicalMundi/oe-module-todo-list'; - $aMetadataDto = new ExternalMetadataDto( - enableSync: true, - category: 'billing', - tags: ['sms', 'fax'] - ); - $expectedMessage = new ModuleMetadataDetected($moduleId, $aMetadataDto); - $stubAdapterForReadingExternalMetadataSource->setExternalMetadataDto($repositoryUrl, $aMetadataDto); - $ecotoneLite = EcotoneLite::bootstrapFlowTesting( - [MetadataUpdaterWorkflow::class], - [ - MetadataUpdaterWorkflow::class => new MetadataUpdaterWorkflow(new NullLogger(), $stubAdapterForReadingExternalMetadataSource), - MetadataValidator::class => new MetadataValidator(), - ] - ); - - - $this->assertEquals( - [$expectedMessage], - $ecotoneLite - ->sendCommand(new UpdateModuleMetadata($moduleId, $repositoryUrl)) - ->getRecordedEvents() - ); - } -} diff --git a/_metadata/tests/Unit/ComposerJsonFile/ComposerJsonFileTest.php b/_metadata/tests/Unit/Core/ValueObject/ComposerJsonFileTest.php similarity index 88% rename from _metadata/tests/Unit/ComposerJsonFile/ComposerJsonFileTest.php rename to _metadata/tests/Unit/Core/ValueObject/ComposerJsonFileTest.php index 65f1186..e07e9ee 100644 --- a/_metadata/tests/Unit/ComposerJsonFile/ComposerJsonFileTest.php +++ b/_metadata/tests/Unit/Core/ValueObject/ComposerJsonFileTest.php @@ -13,9 +13,9 @@ * @license https://github.com/MedicalMundi/marketplace-engine/blob/main/LICENSE MIT */ -namespace MetadataTests\Unit\ComposerJsonFile; +namespace MetadataTests\Unit\Core\ValueObject; -use Metadata\AdapterForReadingExternalMetadataSource\ComposerJsonFile; +use Metadata\Core\ValueObject\ComposerJsonFile; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; @@ -35,7 +35,6 @@ public function shouldDetectIfHasMetadata(bool $expectedResult, string $json): v } #[Test] - #[DataProvider(('jsonWithMetadataDataprovider'))] public function shouldReturnMetadata(): void { $json = '{ @@ -64,6 +63,24 @@ public function shouldReturnMetadata(): void self::assertSame($expectedResult, $sut->getMetadata()); } + #[Test] + public function shouldReturnNullWhenThereAreNotMetadata(): void + { + $json = '{ + "extra": { + "openemr-module": { + "metadata": { + } + } + } + }' + ; + + $sut = ComposerJsonFile::createFromJson($json); + + self::assertNull($sut->getMetadata()); + } + public static function jsonWithoutMetadataDataprovider(): iterable { return [ diff --git a/psalm-baseline.xml b/psalm-baseline.xml index a71130a..172a8d5 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -141,7 +141,7 @@ - ExternalMetadataDto + ?ExternalMetadataDto metadataDtosIndexedByUrl[$moduleUrl]]]> @@ -156,6 +156,16 @@ + + category]]> + enableSync]]> + tags]]> + + + category]]> + enableSync]]> + tags]]> + repositoryUrl validate diff --git a/tools/phparkitect/VendorDependencies/allowed_in_metadata_adapters.php b/tools/phparkitect/VendorDependencies/allowed_in_metadata_adapters.php index 9dd0136..bf8326c 100644 --- a/tools/phparkitect/VendorDependencies/allowed_in_metadata_adapters.php +++ b/tools/phparkitect/VendorDependencies/allowed_in_metadata_adapters.php @@ -7,6 +7,7 @@ 'Ecotone\Modelling\CommandBus', 'Github\Client', + 'Nyholm\Psr7\Request', 'RuntimeException', 'UnexpectedValueException' diff --git a/tools/phparkitect/VendorDependencies/allowed_in_metadata_core.php b/tools/phparkitect/VendorDependencies/allowed_in_metadata_core.php index 223076d..34da3b2 100644 --- a/tools/phparkitect/VendorDependencies/allowed_in_metadata_core.php +++ b/tools/phparkitect/VendorDependencies/allowed_in_metadata_core.php @@ -1,6 +1,8 @@ Date: Fri, 18 Oct 2024 11:52:53 +0200 Subject: [PATCH 18/18] Fix composer validate. --- composer.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.lock b/composer.lock index 639f96c..063825a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "90b371171830ac7c8fe3eb17872bba0f", + "content-hash": "7746092136e83d1006604cdd4ed4ee3c", "packages": [ { "name": "babdev/pagerfanta-bundle", @@ -14489,5 +14489,5 @@ "ext-iconv": "*" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" }