[Product Reviews API] - add API for product reviews

This commit is contained in:
Stoica Paul 2017-10-04 21:53:24 +03:00
parent 127382f6ee
commit aca53a4b11
18 changed files with 1681 additions and 0 deletions

View file

@ -20,7 +20,9 @@ The REST API Reference
payments
product_attributes
product_options
product_reviews
product_variants
product_reviews
products
promotion_coupons
promotions

View file

@ -14,7 +14,9 @@
* :doc:`/api/payments`
* :doc:`/api/product_attributes`
* :doc:`/api/product_options`
* :doc:`/api/product_reviews`
* :doc:`/api/product_variants`
* :doc:`/api/product_reviews`
* :doc:`/api/products`
* :doc:`/api/promotion_coupons`
* :doc:`/api/promotions`

View file

@ -0,0 +1,635 @@
Product Reviews API
====================
These endpoints will allow you to easily manage product reviews. Base URI is `/api/v1/products/{productCode}/reviews/`.
Product Reviews API response structure
--------------------------------------
When you get a collection of resources, you will receive objects with the following fields:
+------------------+------------------------------------------------------------------------------------------------+
| Field | Description |
+==================+================================================================================================+
| id | Id of product review |
+------------------+------------------------------------------------------------------------------------------------+
| title | Title of product review |
+------------------+------------------------------------------------------------------------------------------------+
| comment | Comment of product review |
+------------------+------------------------------------------------------------------------------------------------+
| author | Customer author for product review (This is customer that added the |
| | product review; this will contain customer resource information) |
+------------------+------------------------------------------------------------------------------------------------+
| status | Status of product review (New, Accepted, Rejected) |
+------------------+------------------------------------------------------------------------------------------------+
| reviewSubject | This is the review subject for the product review. For this case of the product review, this |
| | will contain a product resource |
+------------------+------------------------------------------------------------------------------------------------+
.. note::
Read more about :doc:`ProductReviews docs</book/products/product_reviews>`.
Creating a Product Review
--------------------------
To create a new product review you will need to call the ``/api/v1/products/{productCode}/reviews/`` endpoint with the ``POST`` method.
Definition
^^^^^^^^^^
.. code-block:: text
POST /api/v1/products/{productCode}/reviews/
+---------------+----------------+----------------------------------------------------------+
| Parameter | Parameter type | Description |
+===============+================+==========================================================+
| Authorization | header | Token received during authentication |
+---------------+----------------+----------------------------------------------------------+
| productCode | url attribute | Code of product for which the reviews should be created |
+---------------+----------------+----------------------------------------------------------+
| title | request | Product review title |
+---------------+----------------+----------------------------------------------------------+
| comment | request | Product review comment |
+---------------+----------------+----------------------------------------------------------+
| rating | request | Product review rating (1..5) |
+---------------+----------------+----------------------------------------------------------+
| author | request | Product review author |
+---------------+----------------+----------------------------------------------------------+
Example
^^^^^^^
To create a new product review for the product with ``code = MUG-TH`` use the below method.
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/ \
-H "Authorization: Bearer SampleToken" \
-H "Content-Type: application/json" \
-X POST \
--data '
{
"title": "A product review",
"rating": "3",
"comment": "This is a comment review",
"author": {
"email": "test@example.com"
}
}
'
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 201 Created
.. code-block:: json
{
"id": 4,
"title": "A product review",
"rating": 3,
"comment": "This is a comment review",
"author": {
"id": 2,
"email": "test@example.com",
"emailCanonical": "test@example.com",
"gender": "u",
"_links": {
"self": {
"href": "/api/v1/customers/2"
}
}
},
"status": "new",
"reviewSubject": {
"id": 1,
"name": "MUG-TH",
"code": "MUG-TH",
"attributes": [],
"options": [],
"associations": [],
"translations": []
}
}
.. warning::
If you try to create a resource without title, rating, comment or author, you will receive a ``400 Bad Request`` error.
Example
^^^^^^^
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/ \
-H "Authorization: Bearer SampleToken" \
-H "Content-Type: application/json" \
-X POST
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 400 Bad Request
.. code-block:: json
{
"code": 400,
"message": "Validation Failed",
"errors": {
"children": {
"rating": {
"errors": [
"You must check review rating."
],
"children": [
{},
{},
{},
{},
{}
]
},
"title": {
"errors": [
"Review title should not be blank."
]
},
"comment": {
"errors": [
"Review comment should not be blank."
]
},
"author": {
"children": {
"email": {
"errors": [
"Please enter your email."
]
}
}
}
}
}
}
Getting a Single Product Review
--------------------------------
To retrieve the details of a product review you will need to call the ``/api/v1/products/{productCode}/reviews/{id}`` endpoint with the ``GET`` method.
Definition
^^^^^^^^^^
.. code-block:: text
GET /api/v1/products/{productCode}/reviews/{id}
+---------------+----------------+-----------------------------------------------------------+
| Parameter | Parameter type | Description |
+===============+================+===========================================================+
| Authorization | header | Token received during authentication |
+---------------+----------------+-----------------------------------------------------------+
| id | url attribute | Identifier of the product review |
+---------------+----------------+-----------------------------------------------------------+
| productCode | url attribute | Code of product for which the reviews should be displayed |
+---------------+----------------+-----------------------------------------------------------+
Example
^^^^^^^
To see the details of the product review with ``id = 1``, which is defined for the product with ``code = MUG-TH`` use the below method.
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/1 \
-H "Authorization: Bearer SampleToken" \
-H "Accept: application/json"
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 200 OK
.. code-block:: json
{
"id": 1,
"title": "A product review",
"rating": 3,
"comment": "This is a comment review",
"author": {
"id": 2,
"email": "test@example.com",
"emailCanonical": "test@example.com",
"gender": "u",
"_links": {
"self": {
"href": "/api/v1/customers/2"
}
}
},
"status": "new",
"reviewSubject": {
"id": 1,
"name": "MUG-TH",
"code": "MUG-TH",
"attributes": [],
"options": [],
"associations": [],
"translations": []
}
}
Collection of Product Reviews
------------------------------
To retrieve a paginated list of reviews for a selected product you will need to call the ``/api/v1/products/{productCode}/reviews/`` endpoint with the ``GET`` method.
Definition
^^^^^^^^^^
.. code-block:: text
GET /api/v1/products/{productCode}/reviews/
+-------------------------------------+----------------+------------------------------------------------------------+
| Parameter | Parameter type | Description |
+=====================================+================+============================================================+
| Authorization | header | Token received during authentication |
+-------------------------------------+----------------+------------------------------------------------------------+
| productCode | url attribute | Code of product for which the reviews should be displayed |
+-------------------------------------+----------------+------------------------------------------------------------+
| limit | query | *(optional)* Number of items to display per page, |
| | | by default = 10 |
+-------------------------------------+----------------+------------------------------------------------------------+
| sorting['nameOfField']['direction'] | query | *(optional)* Field and direction of sorting, |
| | | by default 'desc' and 'createdAt' |
+-------------------------------------+----------------+------------------------------------------------------------+
Example
^^^^^^^
To see the first page of all product reviews for the product with ``code = MUG-TH`` use the method below.
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/ \
-H "Authorization: Bearer SampleToken" \
-H "Accept: application/json"
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 200 OK
.. code-block:: json
{
"page": 1,
"limit": 10,
"pages": 1,
"total": 3,
"_links": {
"self": {
"href": "/api/v1/products/MUG-TH/reviews/?page=1&limit=10"
},
"first": {
"href": "/api/v1/products/MUG-TH/reviews/?page=1&limit=10"
},
"last": {
"href": "/api/v1/products/MUG-TH/reviews/?page=1&limit=10"
}
},
"_embedded": {
"items": [
{
"id": 4,
"title": "A product review",
"rating": 3,
"comment": "This is a comment review",
"author": {
"id": 2,
"email": "test@example.com",
"_links": {
"self": {
"href": "/api/v1/customers/2"
}
}
},
"status": "new",
"reviewSubject": {
"id": 1,
"name": "MUG-TH",
"code": "MUG-TH",
"options": [],
"averageRating": 0,
"images": [],
"_links": {
"self": {
"href": "/api/v1/products/MUG-TH"
}
}
},
"createdAt": "2017-10-04T20:19:06+03:00",
"updatedAt": "2017-10-04T20:19:06+03:00"
},
{
"id": 3,
"title": "A product review 2",
"rating": 5,
"comment": "This is a comment review 2",
"author": {
"id": 1,
"email": "onetest@example.com",
"_links": {
"self": {
"href": "/api/v1/customers/1"
}
}
},
"status": "new",
"reviewSubject": {
"id": 1,
"name": "MUG-TH",
"code": "MUG-TH",
"options": [],
"averageRating": 0,
"images": [],
"_links": {
"self": {
"href": "/api/v1/products/MUG-TH"
}
}
},
"createdAt": "2017-10-04T18:23:56+03:00",
"updatedAt": "2017-10-04T18:44:08+03:00"
},
{
"id": 1,
"title": "Test review 3",
"rating": 4,
"comment": "This is a comment review 3",
"author": {
"id": 1,
"email": "onetest@example.com",
"_links": {
"self": {
"href": "/api/v1/customers/1"
}
}
},
"status": "accepted",
"reviewSubject": {
"id": 1,
"name": "MUG-TH",
"code": "MUG-TH",
"options": [],
"averageRating": 0,
"images": [],
"_links": {
"self": {
"href": "/api/v1/products/MUG-TH"
}
}
},
"createdAt": "2017-10-03T23:53:24+03:00",
"updatedAt": "2017-10-04T19:18:00+03:00"
}
]
}
}
Updating Product Review
------------------------
To fully update a product review you will need to call the ``/api/v1/products/{productCode}/reviews/{id}`` endpoint with the ``PUT`` method.
Definition
^^^^^^^^^^
.. code-block:: text
PUT /api/v1/products/{productCode}/reviews/{id}
+---------------+----------------+----------------------------------------------------------+
| Parameter | Parameter type | Description |
+===============+================+==========================================================+
| Authorization | header | Token received during authentication |
+---------------+----------------+----------------------------------------------------------+
| id | url attribute | Product review id |
+---------------+----------------+----------------------------------------------------------+
| productCode | url attribute | Code of product for which the reviews should be updated |
+---------------+----------------+----------------------------------------------------------+
| title | request | Product review title |
+---------------+----------------+----------------------------------------------------------+
| comment | request | Product review comment |
+---------------+----------------+----------------------------------------------------------+
| rating | request | Product review rating (1..5) |
+---------------+----------------+----------------------------------------------------------+
Example
^^^^^^^
To fully update the product review with ``id = 1`` for the product with ``code = MUG-TH`` use the below method.
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/1 \
-H "Authorization: Bearer SampleToken" \
-H "Content-Type: application/json" \
-X PUT \
--data '
{
"title": "A product review",
"rating": "4",
"comment": "This is a comment for review"
}
'
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 204 No Content
To partially update a product review you will need to call the ``/api/v1/products/{productCode}/reviews/{id}`` endpoint with the ``PATCH`` method.
Definition
^^^^^^^^^^
.. code-block:: text
PATCH /api/v1/products/{productCode}/reviews/{id}
+------------------------------------+----------------+-----------------------------------------------------------+
| Parameter | Parameter type | Description |
+====================================+================+===========================================================+
| Authorization | header | Token received during authentication |
+------------------------------------+----------------+-----------------------------------------------------------+
| id | url attribute | Identifier of the product review |
+------------------------------------+----------------+-----------------------------------------------------------+
| productCode | url attribute | Code of product for which the reviews should be updated |
+------------------------------------+----------------+-----------------------------------------------------------+
| title | request | Product review title |
+------------------------------------+----------------+-----------------------------------------------------------+
Example
^^^^^^^
To partially update the product review with ``id = 1`` for the product with ``code = MUG-TH`` use the below method.
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/1 \
-H "Authorization: Bearer SampleToken" \
-H "Content-Type: application/json" \
-X PATCH \
--data '
{
"title": "This is an another title for the review"
}
'
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 204 No Content
Deleting a Product Review
--------------------------
To delete a product review you will need to call the ``/api/v1/products/{productCode}/reviews/{id}`` endpoint with the ``DELETE`` method.
Definition
^^^^^^^^^^
.. code-block:: text
DELETE /api/v1/products/{productCode}/reviews/{id}
+---------------+----------------+-----------------------------------------------------------+
| Parameter | Parameter type | Description |
+===============+================+===========================================================+
| Authorization | header | Token received during authentication |
+---------------+----------------+-----------------------------------------------------------+
| id | url attribute | Identifier of the product review |
+---------------+----------------+-----------------------------------------------------------+
| productCode | url attribute | Code of product for which the reviews should be deleted |
+---------------+----------------+-----------------------------------------------------------+
Example
^^^^^^^
To delete the product review with ``id = 1`` from the product with ``code = MUG-TH`` use the below method.
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/1 \
-H "Authorization: Bearer SampleToken" \
-H "Accept: application/json" \
-X DELETE
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 204 No Content
Accept a Product Review
--------------------------
To accept a product review you will need to call the ``/api/v1/products/{productCode}/reviews/{id}/accept`` endpoint with the ``POST``, ``PUT`` or ``PATCH`` method.
Definition
^^^^^^^^^^
.. code-block:: text
POST /api/v1/products/{productCode}/reviews/{id}/accept
+---------------+----------------+-----------------------------------------------------------+
| Parameter | Parameter type | Description |
+===============+================+===========================================================+
| Authorization | header | Token received during authentication |
+---------------+----------------+-----------------------------------------------------------+
| id | url attribute | Identifier of the product review |
+---------------+----------------+-----------------------------------------------------------+
| productCode | url attribute | Code of product for which the reviews should be accepted |
+---------------+----------------+-----------------------------------------------------------+
Example
^^^^^^^
To accept the product review with ``id = 1`` from the product with ``code = MUG-TH`` use the below method.
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/1/accept \
-H "Authorization: Bearer SampleToken" \
-H "Accept: application/json" \
-X POST
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 204 No Content
Reject a Product Review
--------------------------
To reject a product review you will need to call the ``/api/v1/products/{productCode}/reviews/{id}/reject`` endpoint with the ``POST``, ``PUT`` or ``PATCH`` method.
Definition
^^^^^^^^^^
.. code-block:: text
POST /api/v1/products/{productCode}/reviews/{id}/reject
+---------------+----------------+-----------------------------------------------------------+
| Parameter | Parameter type | Description |
+===============+================+===========================================================+
| Authorization | header | Token received during authentication |
+---------------+----------------+-----------------------------------------------------------+
| id | url attribute | Identifier of the product review |
+---------------+----------------+-----------------------------------------------------------+
| productCode | url attribute | Code of product for which the reviews should be rejected |
+---------------+----------------+-----------------------------------------------------------+
Example
^^^^^^^
To reject the product review with ``id = 1`` from the product with ``code = MUG-TH`` use the below method.
.. code-block:: bash
$ curl http://demo.sylius.org/api/v1/products/MUG-TH/reviews/1/reject \
-H "Authorization: Bearer SampleToken" \
-H "Accept: application/json" \
-X POST
Exemplary Response
^^^^^^^^^^^^^^^^^^
.. code-block:: text
STATUS: 204 No Content

View file

@ -7,6 +7,7 @@ imports:
- { resource: "@SyliusAdminApiBundle/Resources/config/grids/cart.yml" }
- { resource: "@SyliusAdminApiBundle/Resources/config/grids/payments.yml" }
- { resource: "@SyliusAdminApiBundle/Resources/config/grids/product.yml" }
- { resource: "@SyliusAdminApiBundle/Resources/config/grids/product_review.yml" }
- { resource: "@SyliusAdminApiBundle/Resources/config/grids/product_variant.yml" }
- { resource: "@SyliusAdminApiBundle/Resources/config/grids/promotion.yml" }
- { resource: "@SyliusAdminApiBundle/Resources/config/grids/shipments.yml" }

View file

@ -0,0 +1,44 @@
# This file is part of the Sylius package.
# (c) Paweł Jędrzejewski
sylius_grid:
grids:
sylius_admin_api_product_review:
driver:
name: doctrine/orm
options:
class: "%sylius.model.product_review.class%"
repository:
method: createQueryBuilderByProductCode
arguments: ["%locale%", $productCode]
sorting:
date: desc
fields:
date:
type: datetime
label: sylius.ui.date
path: createdAt
sortable: createdAt
options:
format: d-m-Y H:i:s
title:
type: string
label: sylius.ui.title
sortable: ~
rating:
type: string
label: sylius.ui.rating
sortable: ~
status:
type: twig
label: sylius.ui.status
reviewSubject:
type: string
label: sylius.ui.product
author:
type: string
label: sylius.ui.customer
filters:
title:
type: string
label: sylius.ui.title

View file

@ -60,6 +60,10 @@ sylius_api_product_association_type:
sylius_api_product_option:
resource: "@SyliusAdminApiBundle/Resources/config/routing/product_option.yml"
sylius_api_product_review:
resource: "@SyliusAdminApiBundle/Resources/config/routing/product_review.yml"
prefix: /products/{productCode}
sylius_api_product_taxon_position:
resource: "@SyliusAdminApiBundle/Resources/config/routing/product_taxon_position.yml"

View file

@ -0,0 +1,90 @@
# This file is part of the Sylius package.
# (c) Paweł Jędrzejewski
sylius_admin_api_product_review:
resource: |
path: 'reviews'
grid: sylius_admin_api_product_review
alias: sylius.product_review
section: admin_api
only: ['index']
serialization_version: $version
vars:
route:
parameters:
productCode: $productCode
type: sylius.resource_api
sylius_admin_api_product_review_create:
path: /reviews/
methods: [POST]
defaults:
_controller: sylius.controller.product_review:createAction
_sylius:
serialization_groups: [Default, Detailed]
serialization_version: $version
section: admin_api
form: Sylius\Bundle\CoreBundle\Form\Type\Product\ProductReviewType
factory:
method: createForSubject
arguments:
- expr:notFoundOnNull(service('sylius.repository.product').findOneByCode($productCode))
sylius_admin_api_product_review_update:
path: /reviews/{id}
methods: [PUT, PATCH]
defaults:
_controller: sylius.controller.product_review:updateAction
_sylius:
serialization_version: $version
section: admin_api
form: Sylius\Bundle\CoreBundle\Form\Type\Product\ProductReviewType
repository:
method: findOneByIdAndProductCode
arguments: [$id, $productCode]
sylius_admin_api_product_review_delete:
path: /reviews/{id}
methods: [DELETE]
defaults:
_controller: sylius.controller.product_review:deleteAction
_sylius:
serialization_version: $version
section: admin_api
repository:
method: findOneByIdAndProductCode
arguments: [$id, $productCode]
csrf_protection: false
sylius_admin_api_product_review_show:
path: /reviews/{code}
methods: [GET]
defaults:
_controller: sylius.controller.product_review:showAction
_sylius:
serialization_version: $version
section: admin_api
serialization_groups: [Default, Detailed]
repository:
method: findOneByIdAndProductCode
arguments: [$code, $productCode]
sylius_admin_api_product_review_accept:
path: /reviews/{id}/accept
methods: [POST, PUT, PATCH]
defaults:
_controller: sylius.controller.product_review:applyStateMachineTransitionAction
_sylius:
state_machine:
graph: sylius_product_review
transition: accept
sylius_admin_api_product_review_reject:
path: /reviews/{id}/reject
methods: [POST, PUT, PATCH]
defaults:
_controller: sylius.controller.product_review:applyStateMachineTransitionAction
_sylius:
state_machine:
graph: sylius_product_review
transition: reject

View file

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\ORM;
use Doctrine\ORM\QueryBuilder;
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Repository\ProductReviewRepositoryInterface;
@ -60,4 +61,35 @@ class ProductReviewRepository extends EntityRepository implements ProductReviewR
->getResult()
;
}
/**
* {@inheritdoc}
*/
public function createQueryBuilderByProductCode(string $locale, string $productCode): QueryBuilder
{
return $this->createQueryBuilder('o')
->innerJoin('o.reviewSubject', 'product')
->innerJoin('product.translations', 'translation')
->andWhere('translation.locale = :locale')
->andWhere('product.code = :productCode')
->setParameter('locale', $locale)
->setParameter('productCode', $productCode)
;
}
/**
* {@inheritdoc}
*/
public function findOneByIdAndProductCode($id, string $productCode): ?ReviewInterface
{
return $this->createQueryBuilder('o')
->innerJoin('o.reviewSubject', 'product')
->andWhere('product.code = :productCode')
->andWhere('o.id = :id')
->setParameter('productCode', $productCode)
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult()
;
}
}

View file

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Sylius\Component\Core\Repository;
use Doctrine\ORM\QueryBuilder;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\Review\Model\ReviewInterface;
@ -38,4 +39,20 @@ interface ProductReviewRepositoryInterface extends RepositoryInterface
* @return array|ReviewInterface[]
*/
public function findAcceptedByProductSlugAndChannel(string $slug, string $locale, ChannelInterface $channel): array;
/**
* @param string $locale
* @param string $productCode
*
* @return QueryBuilder
*/
public function createQueryBuilderByProductCode(string $locale, string $productCode): QueryBuilder;
/**
* @param mixed $id
* @param string $productCode
*
* @return ReviewInterface|null
*/
public function findOneByIdAndProductCode($id, string $productCode): ?ReviewInterface;
}

View file

@ -0,0 +1,359 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Tests\Controller;
use Lakion\ApiTestCase\JsonApiTestCase;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Review\Model\ReviewInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* @author Paul Stoica <paul.stoica18@gmail.com>
*/
final class ProductReviewApiTest extends JsonApiTestCase
{
/**
* @var array
*/
private static $authorizedHeaderWithContentType = [
'HTTP_Authorization' => 'Bearer SampleTokenNjZkNjY2MDEwMTAzMDkxMGE0OTlhYzU3NzYyMTE0ZGQ3ODcyMDAwM2EwMDZjNDI5NDlhMDdlMQ',
'CONTENT_TYPE' => 'application/json',
];
/**
* @var array
*/
private static $authorizedHeaderWithAccept = [
'HTTP_Authorization' => 'Bearer SampleTokenNjZkNjY2MDEwMTAzMDkxMGE0OTlhYzU3NzYyMTE0ZGQ3ODcyMDAwM2EwMDZjNDI5NDlhMDdlMQ',
'ACCEPT' => 'application/json',
];
/**
* @test
*/
public function it_does_not_allow_to_show_product_review_list_when_access_is_denied()
{
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
$this->client->request('GET', $this->getReviewListUrl($product));
$response = $this->client->getResponse();
$this->assertResponse($response, 'authentication/access_denied_response', Response::HTTP_UNAUTHORIZED);
}
/**
* @test
*/
public function it_does_not_allow_to_show_product_review_when_it_does_not_exist()
{
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
$this->loadFixturesFromFile('authentication/api_administrator.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
$this->client->request('GET', $this->getReviewListUrl($product) . '0', [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'error/not_found_response', Response::HTTP_NOT_FOUND);
}
/**
* @test
*/
public function it_allows_showing_product_review()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
/** @var ReviewInterface $productReview */
$productReview = $productReviewsData['productReview1'];
$this->client->request('GET', $this->getReviewUrl($product, $productReview), [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'product_review/show_response', Response::HTTP_OK);
}
/**
* @test
*/
public function it_allows_indexing_product_reviews()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
$this->client->request('GET', $this->getReviewListUrl($product), [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'product_review/index_response', Response::HTTP_OK);
}
/**
* @test
*/
public function it_allows_create_product_review()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
$data =
<<<EOT
{
"title": "J_REVIEW",
"rating": "3",
"comment": "J_REVIEW_COMMENT",
"author": {
"email": "j@example.com"
}
}
EOT;
$this->client->request('POST', $this->getReviewListUrl($product), [], [], static::$authorizedHeaderWithContentType, $data);
$response = $this->client->getResponse();
$this->assertResponse($response, 'product_review/create_response', Response::HTTP_CREATED);
}
/**
* @test
*/
public function it_does_not_allow_to_create_product_review_without_required_fields()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
$this->client->request('POST', $this->getReviewListUrl($product), [], [], static::$authorizedHeaderWithContentType, []);
$response = $this->client->getResponse();
$this->assertResponse($response, 'product_review/create_validation_fail_response', Response::HTTP_BAD_REQUEST);
}
/**
* @test
*/
public function it_does_not_allow_delete_product_review_if_it_does_not_exist()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
$this->client->request('DELETE', $this->getReviewListUrl($product) . '0', [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'error/not_found_response', Response::HTTP_NOT_FOUND);
}
/**
* @test
*/
public function it_allows_delete_product_review()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
/** @var ReviewInterface $productReview */
$productReview = $productReviewsData['productReview1'];
$this->client->request('DELETE', $this->getReviewUrl($product, $productReview), [], [], static::$authorizedHeaderWithContentType, []);
$response = $this->client->getResponse();
$this->assertResponseCode($response, Response::HTTP_NO_CONTENT);
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
$this->client->request('GET', $this->getReviewUrl($product, $productReview), [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'error/not_found_response', Response::HTTP_NOT_FOUND);
}
/**
* @test
*/
public function it_allows_updating_information_about_product_review()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
/** @var ReviewInterface $productReview */
$productReview = $productReviewsData['productReview1'];
$data =
<<<EOT
{
"title": "NEW_REVIEW_TITLE",
"rating": "1",
"comment": "NEW_REVIEW_COMMENT"
}
EOT;
$this->client->request('PUT', $this->getReviewUrl($product, $productReview), [], [], static::$authorizedHeaderWithContentType, $data);
$response = $this->client->getResponse();
$this->assertResponseCode($response, Response::HTTP_NO_CONTENT);
}
/**
* @test
*/
public function it_allows_updating_partial_information_about_product_review()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
$this->loadFixturesFromFile('resources/locales.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
/** @var ReviewInterface $productReview */
$productReview = $productReviewsData['productReview1'];
$data =
<<<EOT
{
"comment": "A_NEW_REVIEW_COMMENT"
}
EOT;
$this->client->request('PATCH', $this->getReviewUrl($product, $productReview), [], [], static::$authorizedHeaderWithContentType, $data);
$response = $this->client->getResponse();
$this->assertResponseCode($response, Response::HTTP_NO_CONTENT);
}
/**
* @test
*/
public function it_allows_accept_product_review()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
/** @var ReviewInterface $productReview */
$productReview = $productReviewsData['productReview1'];
$this->client->request('PATCH', $this->getReviewUrl($product, $productReview) . '/accept', [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'product_review/accept_response', Response::HTTP_OK);
}
/**
* @test
*/
public function it_does_not_allows_accept_product_review_if_it_has_not_new_status()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
/** @var ReviewInterface $productReview */
$productReview = $productReviewsData['productReview3'];
$this->client->request('POST', $this->getReviewUrl($product, $productReview) . '/accept', [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'product_review/change_status_fail_response', Response::HTTP_BAD_REQUEST);
}
/**
* @test
*/
public function it_allows_reject_product_review()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
/** @var ReviewInterface $productReview */
$productReview = $productReviewsData['productReview1'];
$this->client->request('PATCH', $this->getReviewUrl($product, $productReview) . '/reject', [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'product_review/reject_response', Response::HTTP_OK);
}
/**
* @test
*/
public function it_does_not_allows_reject_product_review_if_it_has_not_new_status()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$productReviewsData = $this->loadFixturesFromFile('resources/product_reviews.yml');
/** @var ProductInterface $product */
$product = $productReviewsData['product1'];
/** @var ReviewInterface $productReview */
$productReview = $productReviewsData['productReview3'];
$this->client->request('POST', $this->getReviewUrl($product, $productReview) . '/accept', [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'product_review/change_status_fail_response', Response::HTTP_BAD_REQUEST);
}
/**
* @param ProductInterface $product
*
* @return string
*/
private function getReviewListUrl(ProductInterface $product): string
{
return sprintf('/api/v1/products/%s/reviews/', $product->getCode());
}
/**
* @param ProductInterface $product
* @param ReviewInterface $productReview
*
* @return string
*/
private function getReviewUrl(ProductInterface $product, ReviewInterface $productReview): string
{
return sprintf('%s%s', $this->getReviewListUrl($product), $productReview->getId());
}
}

View file

@ -0,0 +1,44 @@
Sylius\Component\Core\Model\Customer:
customer_example1:
firstName: Example
lastName: Queen
email: example.queen@example.com
emailCanonical: example.queen@example.com
customer_example2:
firstName: Example
lastName: King
email: example.king@example.com
emailCanonical: example.king@example.com
Sylius\Component\Core\Model\Product:
product1:
fallbackLocale: en_US
currentLocale: en
code: MUG_REVIEW_BEST
product2:
fallbackLocale: en_US
currentLocale: en
code: MUG_REVIEW_GOOD
Sylius\Component\Core\Model\ProductReview:
productReview1:
title: mug_review_best
rating: 4
comment: "mug_review_best_comment"
author: "@customer_example1"
status: "new"
reviewSubject: "@product1"
productReview2:
title: mug_review_good
rating: 3
comment: "mug_review_good_comment"
author: "@customer_example1"
status: "new"
reviewSubject: "@product1"
productReview3:
title: mug_review_bad
rating: 1
comment: "mug_review_bad_comment"
author: "@customer_example1"
status: "rejected"
reviewSubject: "@product1"

View file

@ -0,0 +1,92 @@
{
"id": @integer@,
"title": "mug_review_best",
"rating": 4,
"comment": "mug_review_best_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "accepted",
"reviewSubject": {
"id": @integer@,
"code": "MUG_REVIEW_BEST",
"attributes": [],
"options": [],
"associations": [],
"translations": {
"en": {
"locale": "en"
}
},
"productTaxons": [],
"channels": [],
"reviews": {
"1": {
"id": @integer@,
"title": "mug_review_good",
"rating": 3,
"comment": "mug_review_good_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "new",
"createdAt": @string@,
"updatedAt": @string@
},
"2": {
"id": @integer@,
"title": "mug_review_bad",
"rating": 1,
"comment": "mug_review_bad_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "rejected",
"createdAt": @string@,
"updatedAt": @string@
}
},
"averageRating": 4,
"images": [],
"_links": {
"self": {
"href": "/api/v1/products/MUG_REVIEW_BEST"
},
"variants": {
"href": "/api/v1/products/MUG_REVIEW_BEST/variants/"
}
}
},
"createdAt": @string@,
"updatedAt": @string@
}

View file

@ -0,0 +1,4 @@
{
"code":400,
"message":""
}

View file

@ -0,0 +1,112 @@
{
"id": @integer@,
"title": "J_REVIEW",
"rating": 3,
"comment": "J_REVIEW_COMMENT",
"author": {
"id": @integer@,
"email": "j@example.com",
"emailCanonical": "j@example.com",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "new",
"reviewSubject": {
"id": @integer@,
"code": "MUG_REVIEW_BEST",
"attributes": [],
"options": [],
"associations": [],
"translations": {
"en": {
"locale": "en"
}
},
"productTaxons": [],
"channels": [],
"reviews": [
{
"id": @integer@,
"title": "mug_review_best",
"rating": 4,
"comment": "mug_review_best_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "new",
"createdAt": @string@,
"updatedAt": @string@
},
{
"id": @integer@,
"title": "mug_review_good",
"rating": 3,
"comment": "mug_review_good_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "new",
"createdAt": @string@,
"updatedAt": @string@
},
{
"id": @integer@,
"title": "mug_review_bad",
"rating": 1,
"comment": "mug_review_bad_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "rejected",
"createdAt": @string@,
"updatedAt": @string@
}
],
"averageRating": 0,
"images": [],
"_links": {
"self": {
"href": "\/api\/v1\/products\/MUG_REVIEW_BEST"
},
"variants": {
"href": "\/api\/v1\/products\/MUG_REVIEW_BEST\/variants\/"
}
}
},
"createdAt": @string@,
"updatedAt": @string@
}

View file

@ -0,0 +1,39 @@
{
"code": 400,
"message": "Validation Failed",
"errors": {
"children": {
"rating": {
"errors": [
"You must check review rating."
],
"children": [
{},
{},
{},
{},
{}
]
},
"title": {
"errors": [
"Review title should not be blank."
]
},
"comment": {
"errors": [
"Review comment should not be blank."
]
},
"author": {
"children": {
"email": {
"errors": [
"Please enter your email."
]
}
}
}
}
}
}

View file

@ -0,0 +1,20 @@
{
"page": 1,
"limit": 10,
"pages": 1,
"total": 0,
"_links": {
"self": {
"href": "\/api\/v1\/products\/MUG_REVIEW_BEST\/reviews\/?page=1&limit=10"
},
"first": {
"href": "\/api\/v1\/products\/MUG_REVIEW_BEST\/reviews\/?page=1&limit=10"
},
"last": {
"href": "\/api\/v1\/products\/MUG_REVIEW_BEST\/reviews\/?page=1&limit=10"
}
},
"_embedded": {
"items": []
}
}

View file

@ -0,0 +1,92 @@
{
"id": @integer@,
"title": "mug_review_best",
"rating": 4,
"comment": "mug_review_best_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "rejected",
"reviewSubject": {
"id": @integer@,
"code": "MUG_REVIEW_BEST",
"attributes": [],
"options": [],
"associations": [],
"translations": {
"en": {
"locale": "en"
}
},
"productTaxons": [],
"channels": [],
"reviews": {
"1": {
"id": @integer@,
"title": "mug_review_good",
"rating": 3,
"comment": "mug_review_good_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "new",
"createdAt": @string@,
"updatedAt": @string@
},
"2": {
"id": @integer@,
"title": "mug_review_bad",
"rating": 1,
"comment": "mug_review_bad_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "rejected",
"createdAt": @string@,
"updatedAt": @string@
}
},
"averageRating": 0,
"images": [],
"_links": {
"self": {
"href": "/api/v1/products/MUG_REVIEW_BEST"
},
"variants": {
"href": "/api/v1/products/MUG_REVIEW_BEST/variants/"
}
}
},
"createdAt": @string@,
"updatedAt": @string@
}

View file

@ -0,0 +1,92 @@
{
"id": @integer@,
"title": "mug_review_best",
"rating": 4,
"comment": "mug_review_best_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "new",
"reviewSubject": {
"id": @integer@,
"code": "MUG_REVIEW_BEST",
"attributes": [],
"options": [],
"associations": [],
"translations": {
"en": {
"locale": "en"
}
},
"productTaxons": [],
"channels": [],
"reviews": {
"1": {
"id": @integer@,
"title": "mug_review_good",
"rating": 3,
"comment": "mug_review_good_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "new",
"createdAt": @string@,
"updatedAt": @string@
},
"2": {
"id": @integer@,
"title": "mug_review_bad",
"rating": 1,
"comment": "mug_review_bad_comment",
"author": {
"id": @integer@,
"email": "example.queen@example.com",
"emailCanonical": "example.queen@example.com",
"firstName": "Example",
"lastName": "Queen",
"gender": "u",
"_links": {
"self": {
"href": @string@
}
}
},
"status": "rejected",
"createdAt": @string@,
"updatedAt": @string@
}
},
"averageRating": 0,
"images": [],
"_links": {
"self": {
"href": "\/api\/v1\/products\/MUG_REVIEW_BEST"
},
"variants": {
"href": "\/api\/v1\/products\/MUG_REVIEW_BEST\/variants\/"
}
}
},
"createdAt": @string@,
"updatedAt": @string@
}