Skip to content

Commit 7f88a4b

Browse files
authored
Merge pull request #96 from Codencode/contact-endpoints
Contact endpoints: list/create/update
2 parents 3c65125 + d83562c commit 7f88a4b

File tree

3 files changed

+376
-0
lines changed

3 files changed

+376
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
/**
3+
* Copyright since 2007 PrestaShop SA and Contributors
4+
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5+
*
6+
* NOTICE OF LICENSE
7+
*
8+
* This source file is subject to the Academic Free License version 3.0
9+
* that is bundled with this package in the file LICENSE.md.
10+
* It is also available through the world-wide-web at this URL:
11+
* https://opensource.org/licenses/AFL-3.0
12+
* If you did not receive a copy of the license and are unable to
13+
* obtain it through the world-wide-web, please send an email
14+
* to license@prestashop.com so we can send you a copy immediately.
15+
*
16+
* @author PrestaShop SA and Contributors <contact@prestashop.com>
17+
* @copyright Since 2007 PrestaShop SA and Contributors
18+
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
19+
*/
20+
21+
namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\Contact;
22+
23+
use ApiPlatform\Metadata\ApiProperty;
24+
use ApiPlatform\Metadata\ApiResource;
25+
use PrestaShop\PrestaShop\Core\ConstraintValidator\Constraints\DefaultLanguage;
26+
use PrestaShop\PrestaShop\Core\ConstraintValidator\Constraints\TypedRegex;
27+
use PrestaShop\PrestaShop\Core\Domain\Contact\Command\AddContactCommand;
28+
use PrestaShop\PrestaShop\Core\Domain\Contact\Command\EditContactCommand;
29+
use PrestaShop\PrestaShop\Core\Domain\Contact\Exception\ContactConstraintException;
30+
use PrestaShop\PrestaShop\Core\Domain\Contact\Exception\ContactNotFoundException;
31+
use PrestaShop\PrestaShop\Core\Domain\Contact\Query\GetContactForEditing;
32+
use PrestaShopBundle\ApiPlatform\Metadata\CQRSCreate;
33+
use PrestaShopBundle\ApiPlatform\Metadata\CQRSGet;
34+
use PrestaShopBundle\ApiPlatform\Metadata\CQRSPartialUpdate;
35+
use PrestaShopBundle\ApiPlatform\Metadata\LocalizedValue;
36+
use Symfony\Component\HttpFoundation\Response;
37+
use Symfony\Component\Serializer\Annotation\SerializedName;
38+
use Symfony\Component\Validator\Constraints as Assert;
39+
40+
#[ApiResource(
41+
operations: [
42+
new CQRSGet(
43+
uriTemplate: '/contacts/{contactId}',
44+
CQRSQuery: GetContactForEditing::class,
45+
scopes: [
46+
'contact_read',
47+
],
48+
CQRSQueryMapping: self::QUERY_MAPPING,
49+
),
50+
new CQRSCreate(
51+
uriTemplate: '/contacts',
52+
validationContext: ['groups' => ['Default', 'Create']],
53+
CQRSCommand: AddContactCommand::class,
54+
CQRSQuery: GetContactForEditing::class,
55+
scopes: [
56+
'contact_write',
57+
],
58+
CQRSQueryMapping: self::QUERY_MAPPING,
59+
CQRSCommandMapping: self::CREATE_COMMAND_MAPPING,
60+
),
61+
new CQRSPartialUpdate(
62+
uriTemplate: '/contacts/{contactId}',
63+
validationContext: ['groups' => ['Default', 'Update']],
64+
CQRSCommand: EditContactCommand::class,
65+
CQRSQuery: GetContactForEditing::class,
66+
scopes: [
67+
'contact_write',
68+
],
69+
CQRSQueryMapping: self::QUERY_MAPPING,
70+
CQRSCommandMapping: self::UPDATE_COMMAND_MAPPING,
71+
),
72+
],
73+
exceptionToStatus: [
74+
ContactConstraintException::class => Response::HTTP_UNPROCESSABLE_ENTITY,
75+
ContactNotFoundException::class => Response::HTTP_NOT_FOUND,
76+
],
77+
)]
78+
class Contact
79+
{
80+
#[ApiProperty(identifier: true)]
81+
public int $contactId;
82+
83+
#[LocalizedValue]
84+
#[DefaultLanguage(groups: ['Create'], fieldName: 'names')]
85+
#[DefaultLanguage(groups: ['Update'], fieldName: 'names', allowNull: true)]
86+
#[Assert\All(constraints: [
87+
new TypedRegex([
88+
'type' => TypedRegex::TYPE_CATALOG_NAME,
89+
]),
90+
])]
91+
public array $names;
92+
93+
#[Assert\Email(mode: Assert\Email::VALIDATION_MODE_STRICT)]
94+
public string $email;
95+
96+
#[LocalizedValue]
97+
#[DefaultLanguage(groups: ['Create'], fieldName: 'descriptions', allowNull: true)]
98+
#[DefaultLanguage(groups: ['Update'], fieldName: 'descriptions', allowNull: true)]
99+
#[Assert\All(constraints: [
100+
new TypedRegex([
101+
'type' => TypedRegex::TYPE_CATALOG_NAME,
102+
]),
103+
])]
104+
public array $descriptions;
105+
106+
#[SerializedName('saveMessages')]
107+
public bool $isMessagesSavingEnabled;
108+
109+
#[ApiProperty(openapiContext: ['type' => 'array', 'items' => ['type' => 'integer'], 'example' => [1, 3]])]
110+
#[Assert\NotBlank(allowNull: true)]
111+
public array $shopIds;
112+
113+
public const QUERY_MAPPING = [
114+
'[localisedTitles]' => '[names]',
115+
'[localisedDescription]' => '[descriptions]',
116+
'[messagesSavingEnabled]' => '[isMessagesSavingEnabled]',
117+
'[shopAssociation]' => '[shopIds]',
118+
];
119+
120+
public const CREATE_COMMAND_MAPPING = [
121+
'[names]' => '[localisedTitles]',
122+
'[descriptions]' => '[localisedDescription]',
123+
'[saveMessages]' => '[isMessageSavingEnabled]',
124+
];
125+
126+
public const UPDATE_COMMAND_MAPPING = [
127+
'[names]' => '[localisedTitles]',
128+
'[descriptions]' => '[localisedDescription]',
129+
'[saveMessages]' => '[isMessagesSavingEnabled]',
130+
];
131+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
/**
3+
* Copyright since 2007 PrestaShop SA and Contributors
4+
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5+
*
6+
* NOTICE OF LICENSE
7+
*
8+
* This source file is subject to the Academic Free License version 3.0
9+
* that is bundled with this package in the file LICENSE.md.
10+
* It is also available through the world-wide-web at this URL:
11+
* https://opensource.org/licenses/AFL-3.0
12+
* If you did not receive a copy of the license and are unable to
13+
* obtain it through the world-wide-web, please send an email
14+
* to license@prestashop.com so we can send you a copy immediately.
15+
*
16+
* @author PrestaShop SA and Contributors <contact@prestashop.com>
17+
* @copyright Since 2007 PrestaShop SA and Contributors
18+
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
19+
*/
20+
21+
namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\Contact;
22+
23+
use ApiPlatform\Metadata\ApiProperty;
24+
use ApiPlatform\Metadata\ApiResource;
25+
use PrestaShop\PrestaShop\Core\Search\Filters\ContactFilters;
26+
use PrestaShopBundle\ApiPlatform\Metadata\PaginatedList;
27+
28+
#[ApiResource(
29+
operations: [
30+
new PaginatedList(
31+
uriTemplate: '/contacts',
32+
scopes: [
33+
'contact_read',
34+
],
35+
ApiResourceMapping: self::MAPPING,
36+
gridDataFactory: 'prestashop.core.grid.data_provider.contacts',
37+
filtersClass: ContactFilters::class,
38+
filtersMapping: [
39+
'[contactId]' => '[id_contact]',
40+
],
41+
),
42+
]
43+
)]
44+
class ContactList
45+
{
46+
#[ApiProperty(identifier: true)]
47+
public int $contactId;
48+
49+
public string $name;
50+
51+
public string $email;
52+
53+
public string $description;
54+
55+
public const MAPPING = [
56+
'[id_contact]' => '[contactId]',
57+
];
58+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
/**
3+
* Copyright since 2007 PrestaShop SA and Contributors
4+
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5+
*
6+
* NOTICE OF LICENSE
7+
*
8+
* This source file is subject to the Academic Free License version 3.0
9+
* that is bundled with this package in the file LICENSE.md.
10+
* It is also available through the world-wide-web at this URL:
11+
* https://opensource.org/licenses/AFL-3.0
12+
* If you did not receive a copy of the license and are unable to
13+
* obtain it through the world-wide-web, please send an email
14+
* to license@prestashop.com so we can send you a copy immediately.
15+
*
16+
* @author PrestaShop SA and Contributors <contact@prestashop.com>
17+
* @copyright Since 2007 PrestaShop SA and Contributors
18+
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace PsApiResourcesTest\Integration\ApiPlatform;
24+
25+
use Symfony\Component\HttpFoundation\Response;
26+
use Tests\Resources\DatabaseDump;
27+
use Tests\Resources\Resetter\LanguageResetter;
28+
29+
class ContactEndpointTest extends ApiTestCase
30+
{
31+
public static function setUpBeforeClass(): void
32+
{
33+
parent::setUpBeforeClass();
34+
LanguageResetter::resetLanguages();
35+
self::addLanguageByLocale('fr-FR');
36+
self::resetTables();
37+
self::createApiClient(['contact_read', 'contact_write']);
38+
}
39+
40+
public static function tearDownAfterClass(): void
41+
{
42+
parent::tearDownAfterClass();
43+
LanguageResetter::resetLanguages();
44+
self::resetTables();
45+
}
46+
47+
protected static function resetTables(): void
48+
{
49+
DatabaseDump::restoreTables([
50+
'contact',
51+
'contact_lang',
52+
'contact_shop',
53+
]);
54+
}
55+
56+
public static function getProtectedEndpoints(): iterable
57+
{
58+
yield 'get endpoint' => [
59+
'GET',
60+
'/contacts/1',
61+
];
62+
63+
yield 'create endpoint' => [
64+
'POST',
65+
'/contacts',
66+
];
67+
68+
yield 'patch endpoint' => [
69+
'PATCH',
70+
'/contacts/1',
71+
];
72+
73+
yield 'list endpoint' => [
74+
'GET',
75+
'/contacts',
76+
];
77+
}
78+
79+
public function testAddContact(): int
80+
{
81+
$postData = [
82+
'names' => [
83+
'en-US' => 'Contact EN',
84+
'fr-FR' => 'Contact FR',
85+
],
86+
'descriptions' => [
87+
'en-US' => 'Description EN',
88+
'fr-FR' => 'Description FR',
89+
],
90+
'email' => 'test@test.com',
91+
'saveMessages' => true,
92+
'shopIds' => [1],
93+
];
94+
95+
$contact = $this->createItem('/contacts', $postData, ['contact_write']);
96+
$this->assertArrayHasKey('contactId', $contact);
97+
$contactId = $contact['contactId'];
98+
99+
$this->assertSame($postData['names'], $contact['names']);
100+
101+
return $contactId;
102+
}
103+
104+
/**
105+
* @depends testAddContact
106+
*/
107+
public function testGetContact(int $contactId): int
108+
{
109+
$contact = $this->getItem('/contacts/' . $contactId, ['contact_read']);
110+
$this->assertEquals($contactId, $contact['contactId']);
111+
$this->assertArrayHasKey('names', $contact);
112+
113+
return $contactId;
114+
}
115+
116+
/**
117+
* @depends testGetContact
118+
*/
119+
public function testPartialUpdateContact(int $contactId): int
120+
{
121+
$patchData = [
122+
'names' => [
123+
'en-US' => 'Updated Contact EN',
124+
'fr-FR' => 'Updated Contact FR',
125+
],
126+
'email' => 'updated@test.com',
127+
'saveMessages' => false,
128+
'shopIds' => [1],
129+
];
130+
131+
$updatedContact = $this->partialUpdateItem('/contacts/' . $contactId, $patchData, ['contact_write']);
132+
$this->assertSame($patchData['names'], $updatedContact['names']);
133+
$this->assertSame($patchData['email'], $updatedContact['email']);
134+
$this->assertSame((bool) $patchData['saveMessages'], (bool) $updatedContact['saveMessages']);
135+
136+
return $contactId;
137+
}
138+
139+
/**
140+
* @depends testPartialUpdateContact
141+
*/
142+
public function testListContacts(int $contactId): int
143+
{
144+
$paginatedContacts = $this->listItems('/contacts?orderBy=contactId&sortOrder=desc', ['contact_read']);
145+
$this->assertGreaterThanOrEqual(1, $paginatedContacts['totalItems']);
146+
$this->assertEquals('contactId', $paginatedContacts['orderBy']);
147+
148+
$firstContact = $paginatedContacts['items'][0];
149+
$this->assertEquals($contactId, $firstContact['contactId']);
150+
151+
return $contactId;
152+
}
153+
154+
public function testInvalidContact(): void
155+
{
156+
$invalidData = [
157+
'names' => [
158+
'fr-FR' => 'Invalid<',
159+
],
160+
'email' => 'invalidemail@',
161+
'saveMessages' => true,
162+
'shopIds' => [],
163+
];
164+
165+
$validationErrorsResponse = $this->createItem('/contacts', $invalidData, ['contact_write'], Response::HTTP_UNPROCESSABLE_ENTITY);
166+
$this->assertIsArray($validationErrorsResponse);
167+
168+
$this->assertValidationErrors([
169+
[
170+
'propertyPath' => 'names',
171+
'message' => 'The field names is required at least in your default language.',
172+
],
173+
[
174+
'propertyPath' => 'names[fr-FR]',
175+
'message' => '"Invalid<" is invalid',
176+
],
177+
[
178+
'propertyPath' => 'email',
179+
'message' => 'This value is not a valid email address.',
180+
],
181+
[
182+
'propertyPath' => 'shopIds',
183+
'message' => 'This value should not be blank.',
184+
],
185+
], $validationErrorsResponse);
186+
}
187+
}

0 commit comments

Comments
 (0)