[API][Shop] Upgrade ProductReview

This commit is contained in:
Jan Goralski 2024-08-02 12:56:40 +02:00
parent 3161ff6656
commit e171fca830
No known key found for this signature in database
GPG key ID: 95D91BA380F31EDD
21 changed files with 80 additions and 178 deletions

View file

@ -8,7 +8,7 @@ Feature: Adding product review as a customer
Given the store operates on a single channel in "United States"
And the store has a product "Necronomicon"
@ui @api
@api @ui
Scenario: Adding product reviews as a logged in customer
Given I am a logged in customer
When I want to review product "Necronomicon"
@ -18,7 +18,7 @@ Feature: Adding product review as a customer
Then I should be notified that my review is waiting for the acceptation
And the "Scary but astonishing" product review of "Necronomicon" product should not be visible for customers
@ui @no-api
@no-api @ui
Scenario: Adding product reviews as a logged in customer with remember me option
Given I am a logged in customer by using remember me option
When I want to review product "Necronomicon"

View file

@ -8,7 +8,7 @@ Feature: Adding product review as a guest
Given the store operates on a single channel in "United States"
And the store has a product "Necronomicon"
@ui @api
@api @ui
Scenario: Adding product reviews as a guest
When I want to review product "Necronomicon"
And I leave a comment "I'm never gonna read this terrible book again.", titled "Never again" as "castiel@heaven.com"

View file

@ -8,7 +8,7 @@ Feature: Product review validation
Given the store operates on a single channel in "United States"
And the store has a product "Necronomicon"
@ui @api
@api @ui
Scenario: Trying to add a product review without specifying a rate
When I want to review product "Necronomicon"
And I leave a comment "This book made me sad, but plot was fine.", titled "Not good, not bad" as "bartholomew@heaven.com"
@ -16,7 +16,7 @@ Feature: Product review validation
And I try to add it
Then I should be notified that I must check review rating
@ui @api
@api @ui
Scenario: Trying to add a product review without specifying a title
When I want to review product "Necronomicon"
And I leave a comment "This book made me sad, but plot was fine." as "bartholomew@heaven.com"
@ -24,7 +24,7 @@ Feature: Product review validation
And I try to add it
Then I should be notified that title is required
@ui @api
@api @ui
Scenario: Trying to add a product review with too short title
When I want to review product "Necronomicon"
And I leave a comment "This book made me sad, but plot was fine.", titled "X" as "bartholomew@heaven.com"
@ -32,7 +32,7 @@ Feature: Product review validation
And I try to add it
Then I should be notified that title must have at least 2 characters
@ui @api
@api @ui
Scenario: Trying to add a product review with too long title
When I want to review product "Necronomicon"
And I leave a comment "This book made me sad, but plot was fine." as "bartholomew@heaven.com"
@ -41,7 +41,7 @@ Feature: Product review validation
And I try to add it
Then I should be notified that title must have at most 255 characters
@ui @api
@api @ui
Scenario: Trying to add a product review without specifying a comment
When I want to review product "Necronomicon"
And I leave a review titled "Not good, not bad" as "bartholomew@heaven.com"
@ -49,7 +49,7 @@ Feature: Product review validation
And I try to add it
Then I should be notified that comment is required
@ui @api
@api @ui
Scenario: Trying to add a product review without specifying an author email
When I want to review product "Necronomicon"
And I leave a comment "Not good, not bad", titled "Not good, not bad"
@ -57,7 +57,7 @@ Feature: Product review validation
And I try to add it
Then I should be notified that I must enter my email
@ui @api
@api @ui
Scenario: Trying to add a product review with specifying already registered author email
Given there is a customer account "sam@winchester.com" identified by "familybusiness"
When I want to review product "Necronomicon"

View file

@ -13,7 +13,7 @@ Feature: Viewing product reviews on product's details page
And this product has also a review titled "Classic" and rated 5 added by customer "sir.terry@pratchett.com"
And I am a logged in customer
@ui @api
@api @ui
Scenario: Viewing last 3 product reviews on product's details page
When I check this product's details
Then I should see 3 product reviews

View file

@ -12,19 +12,19 @@ Feature: Viewing product reviews on product's reviews page
And this product has also a review titled "Too gloomy" and rated 3 added by customer "jrr.tolkien@middle-earth.com"
And I am a logged in customer
@ui @api
@api @ui
Scenario: Viewing all accepted product reviews on product's reviews page
When I check this product's reviews
Then I should see 3 product reviews in the list
@ui @api
@api @ui
Scenario: Viewing only accepted product reviews on product's reviews page
Given this product has also a new review titled "Classic" and rated 5 added by customer "sir.terry@pratchett.com"
When I check this product's reviews
Then I should see 3 product reviews in the list
But I should not see review titled "Classic" in the list
@ui @api
@api @ui
Scenario: Viewing no review message if there are no reviews
Given the store has a product "Lux perpetua"
When I check this product's reviews

View file

@ -74,7 +74,10 @@ final class ProductReviewContext implements Context
{
$this->client->addRequestData('title', $title);
$this->client->addRequestData('comment', $comment);
$this->client->addRequestData('email', $email);
if (null !== $email) {
$this->client->addRequestData('email', $email);
}
}
/**

View file

@ -34,7 +34,7 @@ imports:
# - api/order/order_history.yaml
# - api/payment/managing_payment_methods.yaml
- api/payment/managing_payments.yml
# - api/product/adding_product_review.yml
- api/product/adding_product_review.yml
- api/product/managing_product_association_types.yml
- api/product/managing_product_attributes.yml
- api/product/managing_product_options.yml
@ -45,7 +45,7 @@ imports:
# - api/product/viewing_price_history.yml
# - api/product/viewing_price_history_after_catalog_promotions.yml
- api/product/viewing_product_in_admin_panel.yaml
# - api/product/viewing_product_reviews.yml
- api/product/viewing_product_reviews.yml
# - api/product/viewing_product_variants.yml
- api/product/viewing_products.yml
# - api/promotion/applying_catalog_promotions.yml TODO: Enable after making product and product variant resources available in API

View file

@ -13,10 +13,10 @@ declare(strict_types=1);
namespace Sylius\Bundle\ApiBundle\Command\Catalog;
use Sylius\Bundle\ApiBundle\Command\CustomerEmailAwareInterface;
use Sylius\Bundle\ApiBundle\Command\IriToIdentifierConversionAwareInterface;
use Sylius\Bundle\ApiBundle\Command\LoggedInCustomerEmailIfNotSetAwareInterface;
class AddProductReview implements IriToIdentifierConversionAwareInterface, CustomerEmailAwareInterface
class AddProductReview implements IriToIdentifierConversionAwareInterface, LoggedInCustomerEmailIfNotSetAwareInterface
{
public function __construct(
public string $title,
@ -31,9 +31,4 @@ class AddProductReview implements IriToIdentifierConversionAwareInterface, Custo
{
return $this->email;
}
public function setEmail(?string $email): void
{
$this->email = $email;
}
}

View file

@ -16,7 +16,6 @@ namespace Sylius\Bundle\ApiBundle\CommandHandler\Catalog;
use Sylius\Bundle\ApiBundle\Command\Catalog\AddProductReview;
use Sylius\Bundle\ApiBundle\Exception\ProductNotFoundException;
use Sylius\Bundle\CoreBundle\Resolver\CustomerResolverInterface;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
@ -51,7 +50,6 @@ final readonly class AddProductReviewHandler implements MessageHandlerInterface
throw new \InvalidArgumentException('Visitor should provide an email');
}
/** @var CustomerInterface $customer */
$customer = $this->customerResolver->resolve($email);
/** @var ReviewInterface $review */

View file

@ -17,12 +17,16 @@ use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
use Sylius\Component\Core\Model\ProductReviewInterface;
use Sylius\Component\Review\Model\ReviewInterface;
final readonly class AcceptedExtension implements QueryCollectionExtensionInterface
{
public function __construct(private string $productReviewClass)
{
public function __construct(
private SectionProviderInterface $sectionProvider,
) {
}
/**
@ -35,11 +39,11 @@ final readonly class AcceptedExtension implements QueryCollectionExtensionInterf
?Operation $operation = null,
array $context = [],
): void {
if ($this->productReviewClass !== $resourceClass || null === $operation) {
if (!is_a($resourceClass, ProductReviewInterface::class, true)) {
return;
}
if ($operation->getName() !== 'shop_get') {
if (!$this->sectionProvider->getSection() instanceof ShopApiSection) {
return;
}

View file

@ -15,7 +15,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://api-platform.com/schema/metadata/properties-3.0 https://api-platform.com/schema/metadata/properties-3.0.xsd"
>
<property resource="Sylius\Component\Core\Model\TaxonImage" name="id" identifier="true" writable="false"/>
<property resource="Sylius\Component\Core\Model\TaxonImage" name="id" identifier="true" writable="false" />
<property resource="Sylius\Component\Core\Model\TaxonImage" name="path" required="true"/>
<property resource="Sylius\Component\Core\Model\TaxonImage" name="owner" required="true"/>
<property resource="Sylius\Component\Core\Model\TaxonImage" name="type" />

View file

@ -18,7 +18,7 @@
>
<resource class="Sylius\Component\Core\Model\AvatarImage" uriTemplate="/admin/administrators/{id}/avatar-image">
<uriVariables>
<uriVariable parameterName="id" fromClass="Sylius\Component\Core\Model\AdminUser" fromProperty="avatar"/>
<uriVariable parameterName="id" fromClass="Sylius\Component\Core\Model\AdminUser" fromProperty="avatar" />
</uriVariables>
<validationContext>

View file

@ -36,9 +36,7 @@
</normalizationContext>
<filters>
<filter>sylius_api.order_filter.admin.product_review</filter>
<filter>sylius_api.order_filter.common.product_review</filter>
<filter>sylius_api.search_filter.admin.product_review</filter>
<filter>sylius_api.search_filter.common.product_review</filter>
</filters>
</operation>
@ -85,10 +83,10 @@
</operation>
<operation
class="ApiPlatform\Metadata\Patch"
uriTemplate="/admin/product-reviews/{id}/accept"
input="false"
controller="Sylius\Bundle\ApiBundle\Applicator\ProductReviewStateMachineTransitionApplicatorInterface::accept"
class="ApiPlatform\Metadata\Patch"
uriTemplate="/admin/product-reviews/{id}/accept"
input="false"
controller="Sylius\Bundle\ApiBundle\Applicator\ProductReviewStateMachineTransitionApplicatorInterface::accept"
>
<normalizationContext>
<values>
@ -152,8 +150,8 @@
</values>
</normalizationContext>
<filters>
<filter>sylius_api.order_filter.common.product_review</filter>
<filter>sylius_api.search_filter.common.product_review</filter>
<filter>sylius_api.order_filter.shop.product_review</filter>
<filter>sylius_api.search_filter.shop.product_review</filter>
</filters>
</operation>
@ -170,10 +168,12 @@
</operation>
<operation
class="ApiPlatform\Metadata\Post"
uriTemplate="/shop/product-reviews"
messenger="input"
input="Sylius\Bundle\ApiBundle\Command\Catalog\AddProductReview">
class="ApiPlatform\Metadata\Post"
uriTemplate="/shop/product-reviews"
itemUriTemplate="/shop/product-reviews/{id}"
messenger="input"
input="Sylius\Bundle\ApiBundle\Command\Catalog\AddProductReview"
>
<denormalizationContext>
<values>
<value name="groups">

View file

@ -1,81 +0,0 @@
<?xml version="1.0" ?>
<!--
This file is part of the Sylius package.
(c) Sylius Sp. z o.o.
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<resources xmlns="https://api-platform.com/schema/metadata"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd"
>
<resource class="%sylius.model.product_review.class%" shortName="ProductReview">
<attribute name="validation_groups">sylius</attribute>
<collectionOperations>
<collectionOperation name="shop_post">
<attribute name="method">POST</attribute>
<attribute name="path">/shop/product-reviews</attribute>
<attribute name="messenger">input</attribute>
<attribute name="input">Sylius\Bundle\ApiBundle\Command\Catalog\AddProductReview</attribute>
<attribute name="denormalization_context">
<attribute name="groups">
<attribute>shop:product_review:create</attribute>
<attribute>sylius:shop:product_review:create</attribute>
</attribute>
</attribute>
<attribute name="normalization_context">
<attribute name="groups">
<attribute>shop:product_review:show</attribute>
<attribute>sylius:shop:product_review:show</attribute>
</attribute>
</attribute>
</collectionOperation>
<collectionOperation name="shop_get">
<attribute name="method">GET</attribute>
<attribute name="path">/shop/product-reviews</attribute>
<attribute name="filters">
<attribute>sylius.api.product_review_product_filter</attribute>
<attribute>sylius.api.product_review_date_filter</attribute>
</attribute>
<attribute name="normalization_context">
<attribute name="groups">
<attribute>shop:product_review:index</attribute>
<attribute>sylius:shop:product_review:index</attribute>
</attribute>
</attribute>
</collectionOperation>
</collectionOperations>
<itemOperations>
<itemOperation name="shop_get">
<attribute name="method">GET</attribute>
<attribute name="path">/shop/product-reviews/{id}</attribute>
<attribute name="normalization_context">
<attribute name="groups">
<attribute>shop:product_review:show</attribute>
<attribute>sylius:shop:product_review:show</attribute>
</attribute>
</attribute>
</itemOperation>
</itemOperations>
<property name="id" identifier="true" writable="false" />
<property name="createdAt" writable="false" />
<property name="updatedAt" writable="false" />
<property name="title" required="true" readable="true" writable="true" />
<property name="rating" readable="true" writable="true" />
<property name="comment" readable="true" writable="true" />
<property name="author" readable="true" writable="true" />
<property name="status" readable="true" writable="true" />
<property name="reviewSubject" readable="true" writable="true" />
</resource>
</resources>

View file

@ -17,43 +17,28 @@
>
<class name="Sylius\Bundle\ApiBundle\Command\Catalog\AddProductReview">
<attribute name="title">
<group>shop:product_review:create</group>
<group>sylius:shop:product_review:create</group>
<group>shop:product_review:index</group>
<group>sylius:shop:product_review:index</group>
<group>shop:product_review:show</group>
<group>sylius:shop:product_review:show</group>
</attribute>
<attribute name="rating">
<group>shop:product_review:create</group>
<group>sylius:shop:product_review:create</group>
<group>shop:product_review:index</group>
<group>sylius:shop:product_review:index</group>
<group>shop:product_review:show</group>
<group>sylius:shop:product_review:show</group>
</attribute>
<attribute name="comment">
<group>shop:product_review:create</group>
<group>sylius:shop:product_review:create</group>
<group>shop:product_review:index</group>
<group>sylius:shop:product_review:index</group>
<group>shop:product_review:show</group>
<group>sylius:shop:product_review:show</group>
</attribute>
<attribute name="productCode" serialized-name="product">
<group>shop:product_review:create</group>
<group>sylius:shop:product_review:create</group>
<group>shop:product_review:index</group>
<group>sylius:shop:product_review:index</group>
<group>shop:product_review:show</group>
<group>sylius:shop:product_review:show</group>
</attribute>
<attribute name="email">
<group>shop:product_review:create</group>
<group>sylius:shop:product_review:create</group>
<group>shop:product_review:index</group>
<group>sylius:shop:product_review:index</group>
<group>shop:product_review:show</group>
<group>sylius:shop:product_review:show</group>
</attribute>
</class>

View file

@ -77,17 +77,6 @@
<!-- <argument type="service" id="sylius.context.channel" />-->
<!-- </service>-->
<!-- <service id="Sylius\Bundle\ApiBundle\SectionResolver\AdminApiUriBasedSectionResolver">-->
<!-- <argument>%sylius.security.new_api_admin_route%</argument>-->
<!-- <tag name="sylius.uri_based_section_resolver" priority="30" />-->
<!-- </service>-->
<!-- <service id="Sylius\Bundle\ApiBundle\SectionResolver\ShopApiUriBasedSectionResolver">-->
<!-- <argument>%sylius.security.new_api_shop_route%</argument>-->
<!-- <argument>orders</argument>-->
<!-- <tag name="sylius.uri_based_section_resolver" priority="40" />-->
<!-- </service>-->
<!-- <service id="Sylius\Bundle\ApiBundle\EventListener\ApiCartBlamerListener">-->
<!-- <argument type="service" id="sylius.context.cart" />-->
<!-- <argument type="service" id="sylius.section_resolver.uri_based_section_resolver" />-->

View file

@ -22,7 +22,7 @@
</service>
<service id="sylius_api.doctrine.orm.query_extension.shop.product_review.accepted" class="Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\ProductReview\AcceptedExtension">
<argument>%sylius.model.product_review.class%</argument>
<argument type="service" id="sylius.section_resolver.uri_based_section_resolver" />
<tag name="api_platform.doctrine.orm.query_extension.collection" />
</service>

View file

@ -118,33 +118,35 @@
<tag name="api_platform.filter" />
</service>
<service id="sylius_api.search_filter.common.product_review" parent="api_platform.doctrine.orm.search_filter" public="true">
<service id="sylius_api.order_filter.admin.product_review" parent="api_platform.doctrine.orm.order_filter" public="true">
<argument type="collection">
<argument key="reviewSubject">exact</argument>
<argument key="createdAt" />
<argument key="title" />
<argument key="rating" />
<argument key="status" />
</argument>
<tag name="api_platform.filter" />
</service>
<service id="sylius_api.search_filter.admin.product_review" parent="api_platform.doctrine.orm.search_filter" public="true">
<argument type="collection">
<argument key="reviewSubject">exact</argument>
<argument key="status">exact</argument>
<argument key="title">partial</argument>
</argument>
<tag name="api_platform.filter" />
</service>
<service id="sylius_api.order_filter.common.product_review" parent="api_platform.doctrine.orm.order_filter" public="true">
<service id="sylius_api.order_filter.shop.product_review" parent="api_platform.doctrine.orm.order_filter" public="true">
<argument type="collection">
<argument key="createdAt" />
</argument>
<tag name="api_platform.filter" />
</service>
<service id="sylius_api.order_filter.admin.product_review" parent="api_platform.doctrine.orm.order_filter" public="true">
<service id="sylius_api.search_filter.shop.product_review" parent="api_platform.doctrine.orm.search_filter" public="true">
<argument type="collection">
<argument key="title" />
<argument key="rating" />
<argument key="status" />
<argument key="reviewSubject">exact</argument>
</argument>
<tag name="api_platform.filter" />
</service>

View file

@ -14,54 +14,64 @@ declare(strict_types=1);
namespace spec\Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\ProductReview;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Get;
use Doctrine\ORM\QueryBuilder;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
use Sylius\Bundle\CoreBundle\SectionResolver\SectionInterface;
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
use Sylius\Component\Core\Model\ProductReview;
use Sylius\Component\Review\Model\ReviewInterface;
final class AcceptedExtensionSpec extends ObjectBehavior
{
function let(): void
function let(SectionProviderInterface $sectionProvider): void
{
$this->beConstructedWith(ProductReview::class);
$this->beConstructedWith($sectionProvider);
}
function it_does_nothing_if_current_resource_is_not_a_product_review(
SectionProviderInterface $sectionProvider,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
): void {
$queryBuilder->getRootAliases()->shouldNotBeCalled();
$sectionProvider->getSection()->shouldNotBeCalled();
$queryBuilder->getRootAliases()->shouldNotBeCalled();
$queryBuilder->andWhere(Argument::any())->shouldNotBeCalled();
$queryBuilder->setParameter(Argument::cetera())->shouldNotBeCalled();
$this->applyToCollection($queryBuilder, $queryNameGenerator, \stdClass::class, new Get(name: 'shop_get'));
$this->applyToCollection($queryBuilder, $queryNameGenerator, \stdClass::class);
}
function it_does_nothing_if_operation_name_is_not_shop_get(
function it_does_nothing_when_the_operation_is_outside_shop_api_section(
SectionProviderInterface $sectionProvider,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
SectionInterface $section,
): void {
$queryBuilder->getRootAliases()->shouldNotBeCalled();
$sectionProvider->getSection()->willReturn($section);
$queryBuilder->getRootAliases()->shouldNotBeCalled();
$queryBuilder->andWhere(Argument::any())->shouldNotBeCalled();
$queryBuilder->setParameter(Argument::cetera())->shouldNotBeCalled();
$this->applyToCollection($queryBuilder, $queryNameGenerator, ProductReview::class, new Get(name: 'admin_get'));
$this->applyToCollection($queryBuilder, $queryNameGenerator, ProductReview::class);
}
function it_filters_accepted_product_reviews(
SectionProviderInterface $sectionProvider,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
): void {
$sectionProvider->getSection()->willReturn(new ShopApiSection());
$queryNameGenerator->generateParameterName('status')->shouldBeCalled()->willReturn('status');
$queryBuilder->getRootAliases()->willReturn(['o']);
$queryBuilder->andWhere('o.status = :status')->willReturn($queryBuilder);
$queryBuilder->setParameter('status', ReviewInterface::STATUS_ACCEPTED)->shouldBeCalled();
$this->applyToCollection($queryBuilder, $queryNameGenerator, ProductReview::class, new Get(name: 'shop_get'));
$this->applyToCollection($queryBuilder, $queryNameGenerator, ProductReview::class);
}
}

View file

@ -55,9 +55,15 @@
"hydra:totalItems": 3,
"hydra:search": {
"@type": "hydra:IriTemplate",
"hydra:template": "\/api\/v2\/shop\/product-reviews{?reviewSubject,reviewSubject[],order[createdAt]}",
"hydra:template": "\/api\/v2\/shop\/product-reviews{?order[createdAt],reviewSubject,reviewSubject[]}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
"@type": "IriTemplateMapping",
"variable": "order[createdAt]",
"property": "createdAt",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "reviewSubject",
@ -69,12 +75,6 @@
"variable": "reviewSubject[]",
"property": "reviewSubject",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "order[createdAt]",
"property": "createdAt",
"required": false
}
]
}

View file

@ -91,9 +91,6 @@ final class ProductReviewsTest extends JsonApiTestCase
{
$this->loadFixturesFromFiles([
'channel.yaml',
'tax_category.yaml',
'shipping_category.yaml',
'product/product_variant.yaml',
]);
$this->client->request(