Skip to content

Commit

Permalink
BAP-20546: The meta properties are ignored in POST and PATCH API requ…
Browse files Browse the repository at this point in the history
…ests (#29675)
  • Loading branch information
vsoroka committed Apr 19, 2021
1 parent ac3b616 commit 22d4e33
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 12 deletions.
57 changes: 50 additions & 7 deletions src/Oro/Bundle/ApiBundle/ApiDoc/Parser/ApiDocMetadataParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
use Oro\Bundle\ApiBundle\Metadata\AssociationMetadata;
use Oro\Bundle\ApiBundle\Metadata\EntityMetadata;
use Oro\Bundle\ApiBundle\Metadata\FieldMetadata;
use Oro\Bundle\ApiBundle\Metadata\MetaPropertyMetadata;
use Oro\Bundle\ApiBundle\Metadata\PropertyMetadata;
use Oro\Bundle\ApiBundle\Request\ApiAction;
use Oro\Bundle\ApiBundle\Request\DataType;
use Oro\Bundle\ApiBundle\Request\RequestType;
use Oro\Bundle\ApiBundle\Request\ValueNormalizer;
use Oro\Bundle\ApiBundle\Util\ConfigUtil;
use Oro\Bundle\ApiBundle\Util\ValueNormalizerUtil;

/**
Expand Down Expand Up @@ -76,6 +78,7 @@ public function parse(array $item)
*
* @return array [field name => [key => value, ...], ...]
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
private function getApiDocFieldsDefinition(
EntityMetadata $metadata,
Expand All @@ -85,12 +88,28 @@ private function getApiDocFieldsDefinition(
$isOutput
) {
$identifiersData = [];
$metaPropertiesData = [];
$fieldsData = [];
$associationsData = [];

$identifiers = $metadata->getIdentifierFieldNames();
$isReadOnlyIdentifier = ApiAction::CREATE === $action && $metadata->hasIdentifierGenerator();

$metaProperties = $metadata->getMetaProperties();
foreach ($metaProperties as $metaPropertyName => $metaPropertyMetadata) {
if (!$this->isPropertyApplicable($metaPropertyMetadata, $isOutput)) {
continue;
}
if (ConfigUtil::CLASS_NAME === ($metaPropertyMetadata->getPropertyPath() ?? $metaPropertyName)) {
continue;
}
$metaPropertyData = $this->getMetaPropertyData(
$metaPropertyMetadata,
$config->getField($metaPropertyName)
);
$metaPropertiesData[$metaPropertyName] = $metaPropertyData;
}

$fields = $metadata->getFields();
foreach ($fields as $fieldName => $fieldMetadata) {
if ($this->isPropertyApplicable($fieldMetadata, $isOutput)) {
Expand Down Expand Up @@ -133,10 +152,11 @@ private function getApiDocFieldsDefinition(
}

ksort($identifiersData);
ksort($metaPropertiesData);
ksort($fieldsData);
ksort($associationsData);

return array_merge($identifiersData, $fieldsData, $associationsData);
return array_merge($identifiersData, $metaPropertiesData, $fieldsData, $associationsData);
}

/**
Expand All @@ -153,18 +173,18 @@ private function isPropertyApplicable(PropertyMetadata $propertyMetadata, $isOut
}

/**
* @param PropertyMetadata $metadata
* @param MetaPropertyMetadata $metadata
* @param EntityDefinitionFieldConfig $config
*
* @return array
*/
private function getPropertyData(PropertyMetadata $metadata, EntityDefinitionFieldConfig $config)
private function getMetaPropertyData(MetaPropertyMetadata $metadata, EntityDefinitionFieldConfig $config)
{
$dataType = $this->dataTypeConverter->convertDataType($metadata->getDataType());
$dataType = $this->getApiDocDataType($metadata->getDataType());

return [
'description' => $config->getDescription(),
'required' => !$metadata->isNullable(),
'required' => false,
'dataType' => $dataType,
'actualType' => $dataType
];
Expand All @@ -178,7 +198,14 @@ private function getPropertyData(PropertyMetadata $metadata, EntityDefinitionFie
*/
private function getFieldData(FieldMetadata $metadata, EntityDefinitionFieldConfig $config)
{
return $this->getPropertyData($metadata, $config);
$dataType = $this->getApiDocDataType($metadata->getDataType());

return [
'description' => $config->getDescription(),
'required' => !$metadata->isNullable(),
'dataType' => $dataType,
'actualType' => $dataType
];
}

/**
Expand All @@ -193,7 +220,13 @@ private function getAssociationData(
EntityDefinitionFieldConfig $config,
RequestType $requestType
) {
$result = $this->getPropertyData($metadata, $config);
$dataType = $this->getApiDocDataType($metadata->getDataType());
$result = [
'description' => $config->getDescription(),
'required' => !$metadata->isNullable(),
'dataType' => $dataType,
'actualType' => $dataType
];
if (!DataType::isAssociationAsField($metadata->getDataType())) {
$result['subType'] = $this->getEntityType($metadata->getTargetClassName(), $requestType);
$result['actualType'] = $metadata->isCollection()
Expand All @@ -219,4 +252,14 @@ private function getEntityType($entityClass, RequestType $requestType)
false
);
}

/**
* @param string $dataType
*
* @return string
*/
private function getApiDocDataType($dataType)
{
return $this->dataTypeConverter->convertDataType($dataType);
}
}
11 changes: 11 additions & 0 deletions src/Oro/Bundle/ApiBundle/Form/FormHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Oro\Bundle\ApiBundle\Metadata\EntityMetadata;
use Oro\Bundle\ApiBundle\Metadata\PropertyMetadata;
use Oro\Bundle\ApiBundle\Request\DataType;
use Oro\Bundle\ApiBundle\Util\ConfigUtil;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
use Symfony\Component\Form\FormBuilderInterface;
Expand Down Expand Up @@ -89,6 +90,16 @@ public function addFormFields(
EntityMetadata $entityMetadata,
EntityDefinitionConfig $entityConfig
) {
$metaProperties = $entityMetadata->getMetaProperties();
foreach ($metaProperties as $name => $metaProperty) {
if (!$metaProperty->isInput()) {
continue;
}
if (ConfigUtil::CLASS_NAME === ($metaProperty->getPropertyPath() ?? $name)) {
continue;
}
$this->addFormField($formBuilder, $name, $entityConfig->getField($name), $metaProperty);
}
$fields = $entityMetadata->getFields();
foreach ($fields as $name => $field) {
if (!$field->isInput()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
dependencies:
- Oro\Bundle\TestFrameworkBundle\Tests\Functional\DataFixtures\LoadOrganization
- Oro\Bundle\TestFrameworkBundle\Tests\Functional\DataFixtures\LoadBusinessUnit

Oro\Bundle\ApiBundle\Tests\Functional\Environment\Entity\TestDepartment:
entity1:
name: 'Entity 1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
use Oro\Bundle\ApiBundle\Tests\Functional\ApiFeatureTrait;
use Oro\Bundle\ApiBundle\Tests\Functional\Environment\Entity\TestDepartment;
use Oro\Bundle\ApiBundle\Tests\Functional\RestJsonApiTestCase;
use Oro\Bundle\TestFrameworkBundle\Tests\Functional\DataFixtures\LoadBusinessUnit;
use Oro\Bundle\TestFrameworkBundle\Tests\Functional\DataFixtures\LoadOrganization;
use Symfony\Component\HttpFoundation\Response;

/**
Expand All @@ -25,8 +23,6 @@ protected function setUp()
parent::setUp();

$this->loadFixtures([
LoadOrganization::class,
LoadBusinessUnit::class,
'@OroApiBundle/Tests/Functional/DataFixtures/test_department.yml'
]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

namespace Oro\Bundle\ApiBundle\Tests\Functional\RestJsonApi;

use Oro\Bundle\ApiBundle\Tests\Functional\Environment\Entity\TestDepartment;
use Oro\Bundle\ApiBundle\Tests\Functional\RestJsonApiTestCase;

/**
* @dbIsolationPerTest
*/
class MetaPropertyTest extends RestJsonApiTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->loadFixtures([
'@OroApiBundle/Tests/Functional/DataFixtures/test_department.yml'
]);
$this->appendEntityConfig(
TestDepartment::class,
[
'fields' => [
'title' => [
'meta_property' => true,
'form_options' => [
'constraints' => [['NotBlank' => null]]
]
]
]
]
);
}

public function testGetList()
{
$entityType = $this->getEntityType(TestDepartment::class);
$response = $this->cget(['entity' => $entityType]);

$this->assertResponseContains(
[
'data' => [
[
'type' => $entityType,
'id' => '<toString(@entity1->id)>',
'meta' => [
'title' => 'Entity 1'
]
]
]
],
$response
);
}

public function testGet()
{
$entityType = $this->getEntityType(TestDepartment::class);
$response = $this->get(['entity' => $entityType, 'id' => '<toString(@entity1->id)>']);

$this->assertResponseContains(
[
'data' => [
'type' => $entityType,
'id' => '<toString(@entity1->id)>',
'meta' => [
'title' => 'Entity 1'
]
]
],
$response
);
}

public function testCreate()
{
$entityType = $this->getEntityType(TestDepartment::class);
$data = [
'data' => [
'type' => $entityType,
'meta' => [
'title' => 'New Entity'
]
]
];

$response = $this->post(['entity' => $entityType], $data);

$this->assertResponseContains($data, $response);

/** @var TestDepartment $task */
$entity = $this->getEntityManager()->find(TestDepartment::class, (int)$this->getResourceId($response));
self::assertEquals('New Entity', $entity->getName());
}

public function testUpdate()
{
$entityType = $this->getEntityType(TestDepartment::class);
$entityId = $this->getReference('entity1')->getId();
$data = [
'data' => [
'type' => $entityType,
'id' => (string)$entityId,
'meta' => [
'title' => 'Updated Entity 1'
]
]
];

$response = $this->patch(['entity' => $entityType, 'id' => (string)$entityId], $data);

$this->assertResponseContains($data, $response);

/** @var TestDepartment $task */
$entity = $this->getEntityManager()->find(TestDepartment::class, $entityId);
self::assertEquals('Updated Entity 1', $entity->getName());
}

public function testUpdateWithValidationConstraintViolation()
{
$entityType = $this->getEntityType(TestDepartment::class);
$entityId = $this->getReference('entity1')->getId();
$data = [
'data' => [
'type' => $entityType,
'id' => (string)$entityId,
'meta' => [
'title' => ''
]
]
];

$response = $this->patch(['entity' => $entityType, 'id' => (string)$entityId], $data, [], false);

$this->assertResponseValidationError(
[
'title' => 'not blank constraint',
'detail' => 'This value should not be blank.',
'source' => ['pointer' => '/data/meta/title']
],
$response
);
}
}
Loading

0 comments on commit 22d4e33

Please sign in to comment.