Skip to content

Commit

Permalink
Merge pull request #45 from jolelievre/product-multishop-validation
Browse files Browse the repository at this point in the history
Product multishop updates
  • Loading branch information
jolelievre authored Jan 9, 2025
2 parents 284354c + 75e96f1 commit 0866e13
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 2 deletions.
20 changes: 18 additions & 2 deletions src/ApiPlatform/Resources/ApiClient/ApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,19 @@

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\ApiClientSettings;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Command\AddApiClientCommand;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Command\DeleteApiClientCommand;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Command\EditApiClientCommand;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Exception\ApiClientConstraintException;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Exception\ApiClientNotFoundException;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Query\GetApiClientForEditing;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSCreate;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSDelete;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSGet;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSPartialUpdate;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Constraints as Assert;

#[ApiResource(
operations: [
Expand All @@ -51,8 +55,9 @@
),
new CQRSCreate(
uriTemplate: '/api-client',
validationContext: ['groups' => ['Default', 'Create']],
CQRSCommand: AddApiClientCommand::class,
scopes: ['api_client_write']
scopes: ['api_client_write'],
),
new CQRSPartialUpdate(
uriTemplate: '/api-client/{apiClientId}',
Expand All @@ -63,23 +68,34 @@
),
],
normalizationContext: ['skip_null_values' => false],
exceptionToStatus: [ApiClientNotFoundException::class => 404],
exceptionToStatus: [
ApiClientNotFoundException::class => Response::HTTP_NOT_FOUND,
ApiClientConstraintException::class => Response::HTTP_UNPROCESSABLE_ENTITY,
],
)]
class ApiClient
{
#[ApiProperty(identifier: true)]
public int $apiClientId;

#[Assert\NotBlank(groups: ['Create'])]
#[Assert\Length(min: 1, max: ApiClientSettings::MAX_CLIENT_ID_LENGTH)]
public string $clientId;

#[Assert\NotBlank(groups: ['Create'])]
#[Assert\Length(min: 1, max: ApiClientSettings::MAX_CLIENT_NAME_LENGTH)]
public string $clientName;

#[Assert\Length(max: ApiClientSettings::MAX_DESCRIPTION_LENGTH)]
public string $description;

public ?string $externalIssuer;

#[Assert\NotBlank(groups: ['Create'])]
public bool $enabled;

#[Assert\NotBlank(groups: ['Create'])]
#[Assert\Positive]
public int $lifetime;

public array $scopes;
Expand Down
3 changes: 3 additions & 0 deletions src/ApiPlatform/Resources/Product/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ class Product
#[LocalizedValue]
public array $descriptions;

#[ApiProperty(openapiContext: ['type' => 'array', 'items' => ['type' => 'integer']])]
public array $shopIds;

public const QUERY_MAPPING = [
'[_context][shopConstraint]' => '[shopConstraint]',
'[_context][langId]' => '[displayLanguageId]',
Expand Down
58 changes: 58 additions & 0 deletions src/ApiPlatform/Resources/Product/ProductShops.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <[email protected]>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\Product;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductNotFoundException;
use PrestaShop\PrestaShop\Core\Domain\Product\Query\GetProductForEditing;
use PrestaShop\PrestaShop\Core\Domain\Product\Shop\Command\SetProductShopsCommand;
use PrestaShop\PrestaShop\Core\Domain\Shop\Exception\ShopAssociationNotFound;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSPartialUpdate;
use Symfony\Component\HttpFoundation\Response;

#[ApiResource(
operations: [
new CQRSPartialUpdate(
uriTemplate: '/product/{productId}/shops',
CQRSCommand: SetProductShopsCommand::class,
CQRSQuery: GetProductForEditing::class,
scopes: [
'product_write',
],
CQRSQueryMapping: Product::QUERY_MAPPING,
CQRSCommandMapping: [
'[associatedShopIds]' => '[shopIds]',
],
),
],
exceptionToStatus: [
ProductNotFoundException::class => Response::HTTP_NOT_FOUND,
ShopAssociationNotFound::class => Response::HTTP_NOT_FOUND,
],
)]
class ProductShops extends Product
{
public int $sourceShopId;

#[ApiProperty(openapiContext: ['type' => 'array', 'items' => ['type' => 'integer']])]
public array $associatedShopIds;
}
55 changes: 55 additions & 0 deletions tests/Integration/ApiPlatform/ApiClientEndpointTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

namespace PsApiResourcesTest\Integration\ApiPlatform;

use PrestaShop\PrestaShop\Core\Domain\ApiClient\ApiClientSettings;
use PrestaShop\PrestaShop\Core\Util\String\RandomString;
use Symfony\Component\HttpFoundation\Response;

class ApiClientEndpointTest extends ApiTestCase
{
public static function setUpBeforeClass(): void
Expand Down Expand Up @@ -322,4 +326,55 @@ public function testDeleteApiClient(int $apiClientId): void
]);
self::assertResponseStatusCodeSame(404);
}

public function testCreateInvalidApiClient(): void
{
$bearerToken = $this->getBearerToken(['api_client_write']);
$response = static::createClient()->request('POST', '/api-client', [
'auth_bearer' => $bearerToken,
'json' => [
'clientId' => '',
'clientName' => '',
'description' => RandomString::generate(ApiClientSettings::MAX_DESCRIPTION_LENGTH + 1),
'lifetime' => 0,
],
]);
self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
// Get content without throwing exception since an error occurred, but we expected it
$decodedResponse = json_decode($response->getContent(false), true);
$this->assertNotFalse($decodedResponse);
$this->assertIsArray($decodedResponse);

$expectedErrors = [
[
'propertyPath' => 'clientId',
'message' => 'This value should not be blank.',
],
[
'propertyPath' => 'clientId',
'message' => 'This value is too short. It should have 1 character or more.',
],
[
'propertyPath' => 'clientName',
'message' => 'This value should not be blank.',
],
[
'propertyPath' => 'clientName',
'message' => 'This value is too short. It should have 1 character or more.',
],
[
'propertyPath' => 'lifetime',
'message' => 'This value should be positive.',
],
[
'propertyPath' => 'enabled',
'message' => 'This value should not be blank.',
],
[
'propertyPath' => 'description',
'message' => sprintf('This value is too long. It should have %d characters or less.', ApiClientSettings::MAX_DESCRIPTION_LENGTH),
],
];
$this->assertValidationErrors($decodedResponse, $expectedErrors);
}
}
37 changes: 37 additions & 0 deletions tests/Integration/ApiPlatform/ApiTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,43 @@ protected function prepareUploadedFile(string $assetFilePath): UploadedFile
return new UploadedFile($tmpUploadedImagePath, basename($assetFilePath));
}

protected function assertValidationErrors(array $responseErrors, array $expectedErrors): void
{
foreach ($responseErrors as $errorDetail) {
$this->assertArrayHasKey('propertyPath', $errorDetail);
$this->assertArrayHasKey('message', $errorDetail);
$this->assertArrayHasKey('code', $errorDetail);

$errorFound = false;
foreach ($expectedErrors as $expectedError) {
if (
(empty($expectedError['message']) || $expectedError['message'] === $errorDetail['message'])
&& (empty($expectedError['propertyPath']) || $expectedError['propertyPath'] === $errorDetail['propertyPath'])
) {
$errorFound = true;
break;
}
}

$this->assertTrue($errorFound, 'Found error that was not expected: ' . var_export($errorDetail, true));
}

foreach ($expectedErrors as $expectedError) {
$errorFound = false;
foreach ($responseErrors as $errorDetail) {
if (
(empty($expectedError['message']) || $expectedError['message'] === $errorDetail['message'])
&& (empty($expectedError['propertyPath']) || $expectedError['propertyPath'] === $errorDetail['propertyPath'])
) {
$errorFound = true;
break;
}
}

$this->assertTrue($errorFound, 'Could not find expected error: ' . var_export($expectedError, true));
}
}

protected static function createApiClient(array $scopes = [], int $lifetime = 10000): void
{
$command = new AddApiClientCommand(
Expand Down
12 changes: 12 additions & 0 deletions tests/Integration/ApiPlatform/ProductEndpointTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ public function testAddProduct(): int
'fr-FR' => '',
],
'active' => false,
'shopIds' => [
1,
],
],
$decodedResponse
);
Expand Down Expand Up @@ -188,6 +191,9 @@ public function testPartialUpdateProduct(int $productId): int
'fr-FR' => '',
],
'active' => true,
'shopIds' => [
1,
],
],
$decodedResponse
);
Expand Down Expand Up @@ -221,6 +227,9 @@ public function testPartialUpdateProduct(int $productId): int
'fr-FR' => '',
],
'active' => true,
'shopIds' => [
1,
],
],
$decodedResponse
);
Expand Down Expand Up @@ -257,6 +266,9 @@ public function testGetProduct(int $productId): int
'fr-FR' => '',
],
'active' => true,
'shopIds' => [
1,
],
],
$decodedResponse
);
Expand Down
Loading

0 comments on commit 0866e13

Please sign in to comment.