diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000..24f9d92 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,28 @@ +name: Integration tests +on: + push: + pull_request: + +permissions: + contents: read # to clone the repos and get release assets (shivammathur/setup-php) + +jobs: + integration: + permissions: + contents: read # to clone the repos and get release assets (shivammathur/setup-php) + name: Integration tests + runs-on: ubuntu-latest + strategy: + matrix: + php: [ '8.1', '8.2' ] + fail-fast: false + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Run ci.sh script + run: | + echo "Starting ci.sh script" + chmod +x ci.sh + ./ci.sh + echo "Script completed" diff --git a/ci.sh b/ci.sh new file mode 100755 index 0000000..7de9cf7 --- /dev/null +++ b/ci.sh @@ -0,0 +1,26 @@ +BASEDIR=$(dirname "$(readlink -f "$0")") +# create a tmp directory and clone the project & check if folder exist before running this cmd +if [ ! -d "/tmp" ]; then + mkdir /tmp +fi +cd /tmp +if [ ! -d "/tmp/prestashop" ]; then + git clone https://github.com/PrestaShop/PrestaShop.git prestashop + # Remove this next line once the file has been transfered from the core to the module. + rm prestashop/src/PrestaShopBundle/ApiPlatform/Resources/Hook.php +fi +cd /tmp/prestashop +git checkout develop +# install the project & mysql +rm -rf /tmp/prestashop/modules/ps_apiresources/* +cp -r $BASEDIR/* /tmp/prestashop/modules/ps_apiresources +PS_INSTALL_AUTO=0 docker compose build --no-cache && docker compose up -d --force-recreate +if [ $? -ne 0 ]; then + exit +fi +bash -c 'while [[ "$(curl -L -s -o /dev/null -w %{http_code} 'http://localhost:8001/install-dev/')" != "200" ]]; do echo "waiting for shop install"; sleep 5; done' +# install the module +docker-compose exec prestashop-git composer i --working-dir=/var/www/html/modules/ps_apiresources +docker-compose exec prestashop-git composer create-test-db +# run tests +docker-compose exec prestashop-git vendor/bin/phpunit modules/ps_apiresources/tests/* -c modules/ps_apiresources/tests/Integration/phpunit.xml \ No newline at end of file diff --git a/composer.json b/composer.json index d738a81..c2511d0 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,11 @@ }, "classmap": ["ps_apiresources.php"] }, + "autoload-dev": { + "psr-4": { + "PsApiResourcesTest\\": "tests/" + } + }, "config": { "preferred-install": "dist", "classmap-authoritative": true, diff --git a/src/ApiPlatform/Resources/Hook.php b/src/ApiPlatform/Resources/Hook.php new file mode 100644 index 0000000..9f4519b --- /dev/null +++ b/src/ApiPlatform/Resources/Hook.php @@ -0,0 +1,107 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace PrestaShop\Module\APIResources\ApiPlatform\Resources; + +use ApiPlatform\Core\Annotation\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\Put; +use PrestaShop\PrestaShop\Core\Domain\Hook\Command\UpdateHookStatusCommand; +use PrestaShop\PrestaShop\Core\Domain\Hook\Exception\HookNotFoundException; +use PrestaShop\PrestaShop\Core\Domain\Hook\Query\GetHook; +use PrestaShop\PrestaShop\Core\Domain\Hook\Query\GetHookStatus; +use PrestaShopBundle\ApiPlatform\Processor\CommandProcessor; +use PrestaShopBundle\ApiPlatform\Provider\QueryProvider; + +#[ApiResource( + operations: [ + new Get( + uriTemplate: '/hook-status/{id}', + requirements: ['id' => '\d+'], + openapiContext: [ + 'summary' => 'Get hook status A', + 'description' => 'Get hook status B', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'path', + 'required' => true, + 'schema' => [ + 'type' => 'string', + ], + 'description' => 'Id of the hook you are requesting the status from', + ], + [ + 'name' => 'Authorization', + 'in' => 'scopes', + 'description' => 'hook_read
hook_write ', + ], + ], + ], + exceptionToStatus: [HookNotFoundException::class => 404], + provider: QueryProvider::class, + extraProperties: [ + 'CQRSQuery' => GetHookStatus::class, + 'scopes' => ['hook_read'], + ] + ), + new Put( + uriTemplate: '/hook-status', + processor: CommandProcessor::class, + extraProperties: [ + 'CQRSCommand' => UpdateHookStatusCommand::class, + 'scopes' => ['hook_write'], + ] + ), + new Get( + uriTemplate: '/hooks/{id}', + requirements: ['id' => '\d+'], + exceptionToStatus: [HookNotFoundException::class => 404], + provider: QueryProvider::class, + extraProperties: [ + 'CQRSQuery' => GetHook::class, + 'scopes' => ['hook_read'], + ] + ), + ], +)] +class Hook +{ + #[ApiProperty(identifier: true)] + public int $id; + + public bool $active; + + public string $name; + + public string $title; + + public string $description; +} diff --git a/tests/Integration/ApiPlatform/ApiTestCase.php b/tests/Integration/ApiPlatform/ApiTestCase.php new file mode 100644 index 0000000..b22cc90 --- /dev/null +++ b/tests/Integration/ApiPlatform/ApiTestCase.php @@ -0,0 +1,100 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace PsApiResourcesTest\Integration\ApiPlatform; + +use ApiPlatform\Symfony\Bundle\Test\Client; +use PrestaShop\PrestaShop\Core\Domain\ApiAccess\Command\AddApiAccessCommand; +use Tests\Resources\DatabaseDump; + +abstract class ApiTestCase extends \ApiPlatform\Symfony\Bundle\Test\ApiTestCase +{ + protected const CLIENT_ID = 'test_client_id'; + protected const CLIENT_NAME = 'test_client_name'; + + protected static ?string $clientSecret = null; + + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + DatabaseDump::restoreTables(['api_access']); + } + + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + DatabaseDump::restoreTables(['api_access']); + self::$clientSecret = null; + } + + protected static function createClient(array $kernelOptions = [], array $defaultOptions = []): Client + { + if (!isset($defaultOptions['headers']['accept'])) { + $defaultOptions['headers']['accept'] = ['application/json']; + } + + return parent::createClient($kernelOptions, $defaultOptions); + } + + protected function getBearerToken(array $scopes = []): string + { + if (null === self::$clientSecret) { + self::createApiAccess($scopes); + } + $client = static::createClient(); + $parameters = ['parameters' => [ + 'client_id' => static::CLIENT_ID, + 'client_secret' => static::$clientSecret, + 'grant_type' => 'client_credentials', + 'scope' => $scopes, + ]]; + $options = ['extra' => $parameters]; + $response = $client->request('POST', '/api/oauth2/token', $options); + + return json_decode($response->getContent())->access_token; + } + + protected static function createApiAccess(array $scopes = [], int $lifetime = 10000): void + { + $client = static::createClient(); + $command = new AddApiAccessCommand( + static::CLIENT_NAME, + static::CLIENT_ID, + true, + '', + $lifetime, + $scopes + ); + + $container = $client->getContainer(); + $commandBus = $container->get('prestashop.core.command_bus'); + $createdApiAccess = $commandBus->handle($command); + + self::$clientSecret = $createdApiAccess->getSecret(); + } +} diff --git a/tests/Integration/ApiPlatform/GetHookStatusTest.php b/tests/Integration/ApiPlatform/GetHookStatusTest.php new file mode 100644 index 0000000..3f53338 --- /dev/null +++ b/tests/Integration/ApiPlatform/GetHookStatusTest.php @@ -0,0 +1,124 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace PsApiResourcesTest\Integration\ApiPlatform; + +use Tests\Resources\DatabaseDump; + +class GetHookStatusTest extends ApiTestCase +{ + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + DatabaseDump::restoreTables(['hook']); + } + + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + DatabaseDump::restoreTables(['hook']); + } + + public function testGetHookStatus(): void + { + $inactiveHook = new \Hook(); + $inactiveHook->name = 'inactiveHook'; + $inactiveHook->active = false; + $inactiveHook->add(); + + $activeHook = new \Hook(); + $activeHook->name = 'activeHook'; + $activeHook->active = true; + $activeHook->add(); + + $bearerToken = $this->getBearerToken([ + 'hook_read', + 'hook_write', + ]); + $response = static::createClient()->request('GET', '/api/hook-status/' . (int) $inactiveHook->id, ['auth_bearer' => $bearerToken]); + self::assertEquals(json_decode($response->getContent())->active, $inactiveHook->active); + self::assertResponseStatusCodeSame(200); + + $response = static::createClient()->request('GET', '/api/hook-status/' . (int) $activeHook->id, ['auth_bearer' => $bearerToken]); + self::assertEquals(json_decode($response->getContent())->active, $activeHook->active); + self::assertResponseStatusCodeSame(200); + + static::createClient()->request('GET', '/api/hook-status/' . 9999, ['auth_bearer' => $bearerToken]); + self::assertResponseStatusCodeSame(404); + + static::createClient()->request('GET', '/api/hook-status/' . $activeHook->id); + self::assertResponseStatusCodeSame(401); + + $inactiveHook->delete(); + $activeHook->delete(); + } + + public function testDisableHook(): void + { + $hook = new \Hook(); + $hook->name = 'disableHook'; + $hook->active = true; + $hook->add(); + + $bearerToken = $this->getBearerToken([ + 'hook_read', + 'hook_write', + ]); + static::createClient()->request('PUT', '/api/hook-status', [ + 'auth_bearer' => $bearerToken, + 'json' => ['id' => (int) $hook->id, 'active' => false], + ]); + self::assertResponseStatusCodeSame(200); + + $response = static::createClient()->request('GET', '/api/hook-status/' . (int) $hook->id, ['auth_bearer' => $bearerToken]); + self::assertEquals(json_decode($response->getContent())->active, false); + self::assertResponseStatusCodeSame(200); + } + + public function testEnableHook(): void + { + $hook = new \Hook(); + $hook->name = 'enableHook'; + $hook->active = false; + $hook->add(); + + $bearerToken = $this->getBearerToken([ + 'hook_read', + 'hook_write', + ]); + static::createClient()->request('PUT', '/api/hook-status', [ + 'auth_bearer' => $bearerToken, + 'json' => ['id' => (int) $hook->id, 'active' => true], + ]); + self::assertResponseStatusCodeSame(200); + + $response = static::createClient()->request('GET', '/api/hook-status/' . (int) $hook->id, ['auth_bearer' => $bearerToken]); + self::assertEquals(json_decode($response->getContent())->active, true); + self::assertResponseStatusCodeSame(200); + } +} diff --git a/tests/Integration/ApiPlatform/GetHookTest.php b/tests/Integration/ApiPlatform/GetHookTest.php new file mode 100644 index 0000000..550302b --- /dev/null +++ b/tests/Integration/ApiPlatform/GetHookTest.php @@ -0,0 +1,72 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace PsApiResourcesTest\Integration\ApiPlatform; + +use Tests\Resources\DatabaseDump; + +class GetHookTest extends ApiTestCase +{ + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + DatabaseDump::restoreTables(['hook']); + } + + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + DatabaseDump::restoreTables(['hook']); + } + + public function testGetHook(): void + { + $hook = new \Hook(); + $hook->name = 'testHook'; + $hook->active = true; + $hook->add(); + + $bearerToken = $this->getBearerToken([ + 'hook_read', + 'hook_write', + ]); + + $response = static::createClient()->request('GET', '/api/hooks/' . (int) $hook->id, ['auth_bearer' => $bearerToken]); + self::assertEquals(json_decode($response->getContent())->active, $hook->active); + self::assertResponseStatusCodeSame(200); + + static::createClient()->request('GET', '/api/hooks/' . 9999, ['auth_bearer' => $bearerToken]); + self::assertResponseStatusCodeSame(404); + + static::createClient()->request('GET', '/api/hooks/' . $hook->id); + self::assertResponseStatusCodeSame(401); + + $hook->delete(); + } +} diff --git a/tests/Integration/bootstrap.php b/tests/Integration/bootstrap.php new file mode 100644 index 0000000..2246967 --- /dev/null +++ b/tests/Integration/bootstrap.php @@ -0,0 +1,42 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ +define('_PS_IN_TEST_', true); +define('_PS_API_FORCE_TLS_VERSION_', false); +define('_PS_ROOT_DIR_', dirname(__DIR__, 4)); +define('_PS_MODULE_DIR_', _PS_ROOT_DIR_ . '/tests/Resources/modules/'); +require_once dirname(__DIR__, 4) . '/vendor/smarty/smarty/libs/functions.php'; +require_once dirname(__DIR__, 4) . '/admin-dev/bootstrap.php'; + +/* + * Following code makes tests run under phpstorm + * Else we get error : Class 'PHPUnit_Util_Configuration' not found + * @see https://stackoverflow.com/questions/33299149/phpstorm-8-and-phpunit-problems-with-runinseparateprocess + */ +if (!defined('PHPUNIT_COMPOSER_INSTALL')) { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__, 4) . '/vendor/autoload.php'); +} + +require_once(dirname(__DIR__, 2) . '/vendor/autoload.php'); \ No newline at end of file diff --git a/tests/Integration/phpunit.xml b/tests/Integration/phpunit.xml new file mode 100644 index 0000000..5611864 --- /dev/null +++ b/tests/Integration/phpunit.xml @@ -0,0 +1,20 @@ + + + + + + + + . + + + + + + isolatedProcess + + +