From 1bff629fb8d8863fe0e6b980ab9a0056a5bf219b Mon Sep 17 00:00:00 2001 From: tleon Date: Mon, 11 Mar 2024 16:52:14 +0100 Subject: [PATCH] chore(api): product listing endpoint --- src/ApiPlatform/Resources/Hook.php | 4 +- src/ApiPlatform/Resources/ProductList.php | 76 ++++++++++++++ .../ProductListingEndpointTest.php | 98 +++++++++++++++++++ 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 src/ApiPlatform/Resources/ProductList.php create mode 100644 tests/Integration/ApiPlatform/ProductListingEndpointTest.php diff --git a/src/ApiPlatform/Resources/Hook.php b/src/ApiPlatform/Resources/Hook.php index 8c98dbb..b0a0981 100644 --- a/src/ApiPlatform/Resources/Hook.php +++ b/src/ApiPlatform/Resources/Hook.php @@ -35,6 +35,7 @@ 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 PrestaShop\PrestaShop\Core\Search\Filters; use PrestaShopBundle\ApiPlatform\Metadata\CQRSGet; use PrestaShopBundle\ApiPlatform\Metadata\CQRSUpdate; use PrestaShopBundle\ApiPlatform\Metadata\DQBPaginatedList; @@ -86,7 +87,8 @@ provider: QueryListProvider::class, scopes: ['hook_read'], ApiResourceMapping: ['[id_hook]' => '[id]'], - queryBuilder: 'prestashop.core.api.query_builder.hook' + queryBuilder: 'prestashop.core.api.query_builder.hook', + filterClass: Filters::class, ), ], )] diff --git a/src/ApiPlatform/Resources/ProductList.php b/src/ApiPlatform/Resources/ProductList.php new file mode 100644 index 0000000..fd16007 --- /dev/null +++ b/src/ApiPlatform/Resources/ProductList.php @@ -0,0 +1,76 @@ + + * @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\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductNotFoundException; +use PrestaShop\PrestaShop\Core\Domain\Shop\Exception\ShopAssociationNotFound; +use PrestaShop\PrestaShop\Core\Search\Filters\ProductFilters; +use PrestaShopBundle\ApiPlatform\Metadata\DQBPaginatedList; +use PrestaShopBundle\ApiPlatform\Provider\QueryListProvider; +use Symfony\Component\HttpFoundation\Response; + +#[ApiResource( + operations: [ + new DQBPaginatedList( + uriTemplate: '/products', + provider: QueryListProvider::class, + scopes: ['product_read'], + ApiResourceMapping: [ + '[id_product]' => '[productId]', + '[final_price_tax_excluded]' => '[price]', + ], + queryBuilder: 'prestashop.core.grid.query_builder.product', + filterClass: ProductFilters::class, + ), + ], + exceptionToStatus: [ + ProductNotFoundException::class => Response::HTTP_NOT_FOUND, + ShopAssociationNotFound::class => Response::HTTP_NOT_FOUND, + ], +)] +class ProductList +{ + #[ApiProperty(identifier: true)] + public int $productId; + + public string $type; + + public bool $active; + + public string $name; + + public int $quantity; + + public string $price; + + public string $category; +} diff --git a/tests/Integration/ApiPlatform/ProductListingEndpointTest.php b/tests/Integration/ApiPlatform/ProductListingEndpointTest.php new file mode 100644 index 0000000..6c4b270 --- /dev/null +++ b/tests/Integration/ApiPlatform/ProductListingEndpointTest.php @@ -0,0 +1,98 @@ + + * @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\Resetter\LanguageResetter; +use Tests\Resources\Resetter\ProductResetter; +use Tests\Resources\ResourceResetter; + +class ProductListingEndpointTest extends ApiTestCase +{ + protected static int $frenchLangId; + + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + (new ResourceResetter())->backupTestModules(); + ProductResetter::resetProducts(); + LanguageResetter::resetLanguages(); + self::$frenchLangId = self::addLanguageByLocale('fr-FR'); + self::createApiClient(['product_read']); + } + + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + ProductResetter::resetProducts(); + LanguageResetter::resetLanguages(); + // Reset modules folder that are removed with the FR language + (new ResourceResetter())->resetTestModules(); + } + + public function getProtectedEndpoints(): iterable + { + yield 'list endpoint' => [ + 'GET', + '/api/products', + ]; + } + + public function testListProducts(): void + { + $bearerToken = $this->getBearerToken([ + 'product_read', + ]); + + $response = static::createClient()->request('GET', '/api/products', ['auth_bearer' => $bearerToken]); + self::assertResponseStatusCodeSame(200); + self::assertCount(19, json_decode($response->getContent())->items); + + $response = static::createClient()->request('GET', '/api/products?limit=10', ['auth_bearer' => $bearerToken]); + self::assertResponseStatusCodeSame(200); + self::assertCount(10, json_decode($response->getContent())->items); + + $response = static::createClient()->request('GET', '/api/products?limit=1&orderBy=id_product&sortOrder=desc', ['auth_bearer' => $bearerToken]); + self::assertResponseStatusCodeSame(200); + self::assertCount(1, json_decode($response->getContent())->items); + $returnedProduct = json_decode($response->getContent()); + self::assertEquals('id_product', $returnedProduct->orderBy); + self::assertEquals('desc', $returnedProduct->sortOrder); + self::assertEquals(1, $returnedProduct->limit); + self::assertEquals([], $returnedProduct->filters); + self::assertEquals('Customizable mug', $returnedProduct->items[0]->name); + self::assertEquals(300, $returnedProduct->items[0]->quantity); + self::assertEquals('13.900000', $returnedProduct->items[0]->price); + self::assertEquals('Home Accessories', $returnedProduct->items[0]->category); + self::assertTrue($returnedProduct->items[0]->active); + + static::createClient()->request('GET', '/api/products'); + self::assertResponseStatusCodeSame(401); + } +}