diff --git a/README.md b/README.md index 5bba1ae..4368a56 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,54 @@ The goal of this Plugin is to ease the pain of dealing with relations in Shopware. +## Shopware native + +Before explaining the solution in this Plugin, please be aware that there is a Shopware native way in the Sync-API +to archive the same result. For example to assign a new category, you can send the following HTTP request: + +```http request +POST /api/_action/sync +Content-Type: application/json + +{ + "change-category": { + "entity": "product", + "action": "upsert", + "payload": [ + { + "id": "", + "categories": [ + {"id": ""} + {"id": ""} + ] + } + ] + }, + "delete-obsolete": { + "entity": "product_category", + "action": "delete", + "criteria": [ + { + "type": "equals", + "field": "productId", + "value": "" + }, + { + "type": "not", + "operator": "and", + "queries": [ + { + "type": "equalsAny", + "field": "categoryId", + "value": ["", ""] + } + ] + } + ] + } +} +``` + ## Quick start After installing the Plugin, you can enable automatic relation cleanup in diff --git a/composer.json b/composer.json index 59c2fa3..254ab8a 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "shopware/core": "~6.6.4" }, "require-dev": { + "de-swebhosting-shopware-plugin/smart-relation-sync-test-plugin": "1.0.0", "de-swebhosting/php-codestyle": "^5.4", "ergebnis/composer-normalize": "^2.45", "friendsofphp/php-cs-fixer": "^3.66", @@ -19,6 +20,12 @@ "shopware/dev-tools": "^1.5", "symplify/phpstan-rules": "^14.4" }, + "repositories": { + "SmartRelationSyncTestPlugin": { + "type": "path", + "url": "./tests/Fixtures/SmartRelationSyncTestPlugin" + } + }, "autoload": { "psr-4": { "Swh\\SmartRelationSync\\": "src/" @@ -26,7 +33,8 @@ }, "autoload-dev": { "psr-4": { - "Swh\\SmartRelationSync\\Tests\\": "tests/" + "Swh\\SmartRelationSync\\Tests\\Compatibility\\": "tests/Compatibility/", + "Swh\\SmartRelationSync\\Tests\\Functional\\": "tests/Functional/" } }, "config": { diff --git a/phpstan.neon b/phpstan.neon index 6de7978..01ae162 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -28,6 +28,19 @@ parameters: consoleApplicationLoader: tests/StaticAnalyze/PHPStan/console-application.php type_perfect: no_mixed: true + ignoreErrors: + - identifier: class.extendsInternalClass + - identifier: classConstant.internalClass + - identifier: method.internal + - identifier: method.internalClass + - identifier: method.internalInterface + - identifier: new.internalClass + - identifier: parameter.internalClass + - identifier: parameter.internalInterface + - identifier: property.internalClass + - identifier: property.internalInterface + - identifier: return.internalClass + - identifier: staticMethod.internal services: - # register the class, so we can decorate it, but don't tag it as a rule, so only our decorator is used by PHPStan diff --git a/src/ApiDefinition/EntitySchemaGeneratorDecorator.php b/src/ApiDefinition/EntitySchemaGeneratorDecorator.php index e5c8537..9eb6995 100644 --- a/src/ApiDefinition/EntitySchemaGeneratorDecorator.php +++ b/src/ApiDefinition/EntitySchemaGeneratorDecorator.php @@ -11,10 +11,9 @@ use Shopware\Core\Framework\Api\Context\AdminApiSource; use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition; use Shopware\Core\Framework\DataAbstractionLayer\Field\Field; -use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField; -use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField; use Shopware\Core\System\SalesChannel\Entity\SalesChannelDefinitionInterface; use Swh\SmartRelationSync\DataAbstractionLayer\WriteCommandExtractorDecorator; +use Swh\SmartRelationSync\ValueObject\RelevantField; /** * @internal @@ -66,7 +65,7 @@ public function getSchema(array $definitions): array $entityProperties = $schema[$entity]['properties']; $relevantFields = $definition->getFields() - ->filter(fn(Field $field) => $this->isRelevantField($field)); + ->filter(static fn(Field $field) => RelevantField::isRelevant($field)); foreach ($relevantFields as $field) { if (!array_key_exists($field->getPropertyName(), $entityProperties)) { @@ -88,12 +87,4 @@ public function supports(string $format, string $api): bool { return $this->decorated->supports($format, $api); } - - /** - * @phpstan-assert-if-true ManyToManyAssociationField|OneToManyAssociationField $field - */ - private function isRelevantField(Field $field): bool - { - return WriteCommandExtractorDecorator::isRelevantField($field); - } } diff --git a/src/ApiDefinition/OpenApiDefinitionSchemaBuilderDecorator.php b/src/ApiDefinition/OpenApiDefinitionSchemaBuilderDecorator.php index 0655e6f..01b56f2 100644 --- a/src/ApiDefinition/OpenApiDefinitionSchemaBuilderDecorator.php +++ b/src/ApiDefinition/OpenApiDefinitionSchemaBuilderDecorator.php @@ -6,6 +6,7 @@ use OpenApi\Annotations\Property; use OpenApi\Annotations\Schema; +use RuntimeException; use Shopware\Core\Framework\Api\ApiDefinition\DefinitionService; use Shopware\Core\Framework\Api\ApiDefinition\Generator\OpenApi\OpenApiDefinitionSchemaBuilder; use Shopware\Core\Framework\Api\Context\AdminApiSource; @@ -13,10 +14,10 @@ use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition; use Shopware\Core\Framework\DataAbstractionLayer\Field\Field; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ApiAware; +use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Extension; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\IgnoreInOpenapiSchema; -use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField; -use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField; use Swh\SmartRelationSync\DataAbstractionLayer\WriteCommandExtractorDecorator; +use Swh\SmartRelationSync\ValueObject\RelevantField; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; class OpenApiDefinitionSchemaBuilderDecorator extends OpenApiDefinitionSchemaBuilder @@ -53,7 +54,7 @@ public function getSchemaByDefinition( } $relevantFields = $definition->getFields() - ->filter(fn(Field $field) => $this->isRelevantField($field)); + ->filter(static fn(Field $field) => RelevantField::isRelevant($field)); foreach ($relevantFields as $field) { if (!$this->shouldFieldBeIncluded($field, $forSalesChannel)) { @@ -68,14 +69,48 @@ public function getSchemaByDefinition( 'writeOnly' => true, ]); - foreach ($relevantSchemas as $schema) { - $schema->properties[] = $property; - } + match ($field->is(Extension::class)) { + true => $this->addExtensionToSchemas($relevantSchemas, $property), + false => $this->addPropertyToSchemas($relevantSchemas, $property), + }; } return $schemas; } + /** + * @param Schema[] $schemas + */ + private function addExtensionToSchemas(array $schemas, Property $property): void + { + foreach ($schemas as $schema) { + $extensionSchema = $this->getExtensionSchema($schema); + + $extensionSchema->properties[] = $property; + } + } + + /** + * @param Schema[] $schemas + */ + private function addPropertyToSchemas(array $schemas, Property $property): void + { + foreach ($schemas as $schema) { + $schema->properties[] = $property; + } + } + + private function getExtensionSchema(Schema $schema): Schema + { + foreach ($schema->properties as $property) { + if ($property->property === 'extensions') { + return $property; + } + } + + throw new RuntimeException('extensions property not found'); + } + /** * @param Schema[] $schemas * @@ -108,14 +143,6 @@ private function getRelevantSchemas(array $schemas, EntityDefinition $definition return $relevantSchemas; } - /** - * @phpstan-assert-if-true ManyToManyAssociationField|OneToManyAssociationField $field - */ - private function isRelevantField(Field $field): bool - { - return WriteCommandExtractorDecorator::isRelevantField($field); - } - private function shouldFieldBeIncluded(Field $field, bool $forSalesChannel): bool { if ($field->getPropertyName() === 'translations' diff --git a/src/DataAbstractionLayer/WriteCommandExtractorDecorator.php b/src/DataAbstractionLayer/WriteCommandExtractorDecorator.php index 2f6366b..60a69c4 100644 --- a/src/DataAbstractionLayer/WriteCommandExtractorDecorator.php +++ b/src/DataAbstractionLayer/WriteCommandExtractorDecorator.php @@ -9,6 +9,7 @@ use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition; use Shopware\Core\Framework\DataAbstractionLayer\Field\Field; use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField; +use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Extension; use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField; use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField; use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField; @@ -17,6 +18,7 @@ use Shopware\Core\Framework\DataAbstractionLayer\Version\VersionDefinition; use Shopware\Core\Framework\DataAbstractionLayer\Write\WriteCommandExtractor; use Shopware\Core\Framework\DataAbstractionLayer\Write\WriteParameterBag; +use Swh\SmartRelationSync\ValueObject\RelevantField; final class WriteCommandExtractorDecorator extends WriteCommandExtractor { @@ -31,15 +33,6 @@ public static function getCleanupEnableFieldName(Field $field): string return sprintf('%sCleanupRelations', $field->getPropertyName()); } - /** - * @phpstan-assert-if-true ManyToManyAssociationField|OneToManyAssociationField $field - */ - public static function isRelevantField(Field $field): bool - { - return $field instanceof ManyToManyAssociationField - || $field instanceof OneToManyAssociationField; - } - /** * @param array $rawData */ @@ -226,6 +219,65 @@ private function getPrimaryKeyFields(EntityDefinition $reference): array return $fields; } + /** + * @param array $rawData + * + * @return array + */ + private function getRelevantRawData(Field $field, array $rawData): array + { + if (!$field->is(Extension::class)) { + return $rawData; + } + + $propertyName = $field->getPropertyName(); + + if (isset($rawData[$propertyName])) { + return $rawData; + } + + if ( + !isset($rawData['extensions']) + || !is_array($rawData['extensions']) + || !isset($rawData['extensions'][$propertyName])) { + return $rawData; + } + + return $rawData['extensions']; + } + + /** + * @param array $rawData + */ + private function registerFieldForCleanup( + RelevantField $relevantField, + array $rawData, + ): void { + $field = $relevantField->field; + + $fieldData = $rawData[$field->getPropertyName()] ?? null; + + if (!is_array($fieldData)) { + return; + } + + $cleanupEnableField = $this->getCleanupEnableFieldName($field); + + if (!array_key_exists($cleanupEnableField, $rawData)) { + return; + } + + $cleanupEnabled = is_bool($rawData[$cleanupEnableField]) && $rawData[$cleanupEnableField]; + + unset($rawData[$cleanupEnableField]); + + if (!$cleanupEnabled) { + return; + } + + $this->registerRelationsForField($relevantField, $fieldData); + } + /** * @param array $rawData */ @@ -247,43 +299,28 @@ private function registerRelations(array $rawData, WriteParameterBag $parameters $primaryKeys = $this->filterVersionFields($primaryKeys, $definition); foreach ($definition->getFields() as $field) { - if (!self::isRelevantField($field)) { - continue; - } - - $cleanupEnableField = $this->getCleanupEnableFieldName($field); + $relevantField = RelevantField::create($field, $primaryKeys); - if (!array_key_exists($cleanupEnableField, $rawData)) { + if ($relevantField === null) { continue; } - $cleanupEnabled = is_bool($rawData[$cleanupEnableField]) && $rawData[$cleanupEnableField]; - - unset($rawData[$cleanupEnableField]); - - if (!$cleanupEnabled) { - continue; // @codeCoverageIgnore - } - - $fieldData = $rawData[$field->getPropertyName()] ?? null; + $rawData = $this->getRelevantRawData($field, $rawData); - if (!is_array($fieldData)) { - continue; - } - - $this->registerRelationsForField($field, $primaryKeys, $fieldData); + $this->registerFieldForCleanup($relevantField, $rawData); } } /** - * @param non-empty-array $parentPrimaryKey * @param array $fieldData */ private function registerRelationsForField( - ManyToManyAssociationField|OneToManyAssociationField $field, - array $parentPrimaryKey, + RelevantField $relevantField, array $fieldData, ): void { + $field = $relevantField->field; + $parentPrimaryKey = $relevantField->parentPrimaryKey; + $reference = $field->getReferenceDefinition(); $cleanupRelationData = match ($field instanceof OneToManyAssociationField) { diff --git a/src/ValueObject/RelevantField.php b/src/ValueObject/RelevantField.php new file mode 100644 index 0000000..b96660f --- /dev/null +++ b/src/ValueObject/RelevantField.php @@ -0,0 +1,42 @@ + $parentPrimaryKey + */ + public function __construct( + public ManyToManyAssociationField|OneToManyAssociationField $field, + public array $parentPrimaryKey, + ) {} + + /** + * @param non-empty-array $parentPrimaryKey + */ + public static function create(Field $field, array $parentPrimaryKey): ?self + { + if (!self::isRelevant($field)) { + return null; + } + + /** @var ManyToManyAssociationField|OneToManyAssociationField $field */ + return new self($field, $parentPrimaryKey); + } + + /** + * @phpstan-assert-if-true ManyToManyAssociationField|OneToManyAssociationField $field + */ + public static function isRelevant(Field $field): bool + { + return $field instanceof ManyToManyAssociationField + || $field instanceof OneToManyAssociationField; + } +} diff --git a/tests/Fixtures/SmartRelationSyncTestPlugin/composer.json b/tests/Fixtures/SmartRelationSyncTestPlugin/composer.json new file mode 100644 index 0000000..5a5fe5f --- /dev/null +++ b/tests/Fixtures/SmartRelationSyncTestPlugin/composer.json @@ -0,0 +1,19 @@ +{ + "name": "de-swebhosting-shopware-plugin/smart-relation-sync-test-plugin", + "description": "SmartRelationSync plugin test plugin", + "version": "1.0.0", + "license": "MIT", + "type": "shopware-platform-plugin", + "autoload": { + "psr-4": { + "Swh\\SmartRelationSyncTestPlugin\\": "src/" + } + }, + "extra": { + "label": { + "de-DE": "SmartRelationSync Test", + "en-GB": "SmartRelationSync Test" + }, + "shopware-plugin-class": "Swh\\SmartRelationSyncTestPlugin\\SmartRelationSyncTestPlugin" + } +} diff --git a/tests/Fixtures/SmartRelationSyncTestPlugin/src/Entity/PropertyGroupOptionExcludeDefinition.php b/tests/Fixtures/SmartRelationSyncTestPlugin/src/Entity/PropertyGroupOptionExcludeDefinition.php new file mode 100644 index 0000000..b471324 --- /dev/null +++ b/tests/Fixtures/SmartRelationSyncTestPlugin/src/Entity/PropertyGroupOptionExcludeDefinition.php @@ -0,0 +1,34 @@ +addFlags(new PrimaryKey(), new Required()), + new ManyToOneAssociationField('option', 'property_group_option_id', PropertyGroupOptionDefinition::class, 'id', false), + + (new FkField('property_group_option_exclude_id', 'excludedOptionId', PropertyGroupOptionDefinition::class))->addFlags(new PrimaryKey(), new Required()), + new ManyToOneAssociationField('excludedOption', 'property_group_option_exclude_id', PropertyGroupOptionDefinition::class, 'id', false), + ]); + } +} diff --git a/tests/Fixtures/SmartRelationSyncTestPlugin/src/Entity/PropertyGroupOptionExcludeExtension.php b/tests/Fixtures/SmartRelationSyncTestPlugin/src/Entity/PropertyGroupOptionExcludeExtension.php new file mode 100644 index 0000000..ba78bfc --- /dev/null +++ b/tests/Fixtures/SmartRelationSyncTestPlugin/src/Entity/PropertyGroupOptionExcludeExtension.php @@ -0,0 +1,36 @@ +add( + (new ManyToManyAssociationField( + self::EXTENSION_NAME, + PropertyGroupOptionDefinition::class, + PropertyGroupOptionExcludeDefinition::class, + 'property_group_option_id', + 'property_group_option_exclude_id', + ) + )->addFlags(new ApiAware(), new CascadeDelete()), + ); + } + + public function getDefinitionClass(): string + { + return PropertyGroupOptionDefinition::class; + } +} diff --git a/tests/Fixtures/SmartRelationSyncTestPlugin/src/Migration/Migration1746808550ProductPropertyOptionGroupExclude.php b/tests/Fixtures/SmartRelationSyncTestPlugin/src/Migration/Migration1746808550ProductPropertyOptionGroupExclude.php new file mode 100644 index 0000000..9544822 --- /dev/null +++ b/tests/Fixtures/SmartRelationSyncTestPlugin/src/Migration/Migration1746808550ProductPropertyOptionGroupExclude.php @@ -0,0 +1,33 @@ +executeStatement($sql); + } +} diff --git a/tests/Fixtures/SmartRelationSyncTestPlugin/src/Resources/config/services.php b/tests/Fixtures/SmartRelationSyncTestPlugin/src/Resources/config/services.php new file mode 100644 index 0000000..efedcbf --- /dev/null +++ b/tests/Fixtures/SmartRelationSyncTestPlugin/src/Resources/config/services.php @@ -0,0 +1,18 @@ +services() + ->defaults() + ->private() + ->autowire() + ->autoconfigure(); + + $services->set(PropertyGroupOptionExcludeDefinition::class); + $services->set(PropertyGroupOptionExcludeExtension::class); +}; diff --git a/tests/Fixtures/SmartRelationSyncTestPlugin/src/SmartRelationSyncTestPlugin.php b/tests/Fixtures/SmartRelationSyncTestPlugin/src/SmartRelationSyncTestPlugin.php new file mode 100644 index 0000000..e2f2664 --- /dev/null +++ b/tests/Fixtures/SmartRelationSyncTestPlugin/src/SmartRelationSyncTestPlugin.php @@ -0,0 +1,9 @@ + $payload */ - abstract protected function upsertProduct(array $payload): void; + abstract protected function upsertEntity(string $entity, array $payload): void; protected function setUp(): void { @@ -36,6 +41,17 @@ protected function setUp(): void $this->ids = new IdsCollection(); } + /** + * @return array + */ + public static function trueFalseDataProvider(): array + { + return [ + [true], + [false], + ]; + } + public function testSyncManyToMany(): void { $builder = $this->createProductBuilder() @@ -53,6 +69,43 @@ public function testSyncManyToMany(): void self::assertSame($this->ids->get('Test 2'), $categories?->first()?->getId()); } + #[DataProvider('trueFalseDataProvider')] + public function testSyncManyToManyExtension(bool $useExtensionsProperty): void + { + $excludedOption1 = ['id' => Uuid::randomHex(), 'name' => 'Excluded option 1', 'group' => ['name' => 'Group 1']]; + $this->upsertEntity('property_group_option', $excludedOption1); + + $excludedOption2 = ['id' => Uuid::randomHex(), 'name' => 'Excluded option 2', 'group' => ['name' => 'Group 2']]; + $this->upsertEntity('property_group_option', $excludedOption2); + + $propertyGroup = $this->buildPropertyGroupData($useExtensionsProperty, $excludedOption1['id']); + + $this->upsertEntity('property_group', $propertyGroup); + + $optionData = $propertyGroup['options'][0]; + + match ($useExtensionsProperty) { + true => $optionData['extensions']['excludedOptions'][0]['id'] = $excludedOption2['id'], + false => $optionData['excludedOptions'][0]['id'] = $excludedOption2['id'], + }; + + $this->upsertEntity('property_group_option', $optionData); + + $criteria = new Criteria([$optionData['id']]); + $criteria->addAssociation('excludedOptions'); + $result = $this->getContainer()->get('property_group_option.repository') + ->search($criteria, $this->context) + ->first(); + + self::assertInstanceOf(PropertyGroupOptionEntity::class, $result); + + $extension = $result->getExtension('excludedOptions'); + self::assertInstanceOf(PropertyGroupOptionCollection::class, $extension); + self::assertCount(1, $extension); + + self::assertSame($excludedOption2['id'], $extension->first()?->getId()); + } + public function testSyncManyToManyWithEmptyArray(): void { $builder = $this->createProductBuilder() @@ -74,12 +127,12 @@ public function testSyncManyToManyWithoutCleanup(): void $builder = $this->createProductBuilder() ->category('Test 1'); - $this->upsertProduct($builder->build()); + $this->upsertEntity('product', $builder->build()); $builder = $this->createProductBuilder() ->category('Test 2'); - $this->upsertProduct($builder->build()); + $this->upsertEntity('product', $builder->build()); $categories = $this->loadCategories(); self::assertCount(2, $categories ?? []); @@ -128,6 +181,45 @@ protected function loadCategories(): ?CategoryCollection return $product->getCategories(); } + /** + * @return array{ + * id: non-empty-string, + * name: non-empty-string, + * options: array{ + * 0: array{ + * id: non-empty-string, + * name: non-empty-string, + * extensions?: array{excludedOptions: array{0: array{id: string}}, excludedOptionsCleanupRelations: bool}, + * excludedOptions?: array{0: array{id: string}}, + * excludedOptionsCleanupRelations?: bool, + * } + * } + * } + */ + private function buildPropertyGroupData(bool $useExtensionsProperty, string $excludedOptionId): array + { + $extensionData = [ + 'excludedOptions' => [['id' => $excludedOptionId]], + 'excludedOptionsCleanupRelations' => true, + ]; + + $optionData = [ + 'id' => Uuid::randomHex(), + 'name' => 'Test option', + ]; + + match ($useExtensionsProperty) { + true => $optionData['extensions'] = $extensionData, + false => $optionData = array_merge($optionData, $extensionData), + }; + + return [ + 'id' => Uuid::randomHex(), + 'name' => 'Test group', + 'options' => [$optionData], + ]; + } + private function createProductBuilder(): ProductBuilder { return (new ProductBuilder($this->ids, self::PRODUCT_NUMBER)) @@ -163,6 +255,6 @@ private function upsertProductWithRelationCleanup(array $payload): void $payload['pricesCleanupRelations'] = true; $payload['categoriesCleanupRelations'] = true; - $this->upsertProduct($payload); + $this->upsertEntity('product', $payload); } } diff --git a/tests/Functional/DataAbstractionLayer/EntityWriteSubscriberApiTest.php b/tests/Functional/DataAbstractionLayer/EntityWriteSubscriberApiTest.php index 793b5bb..49650b8 100644 --- a/tests/Functional/DataAbstractionLayer/EntityWriteSubscriberApiTest.php +++ b/tests/Functional/DataAbstractionLayer/EntityWriteSubscriberApiTest.php @@ -10,14 +10,14 @@ class EntityWriteSubscriberApiTest extends AbstractEntityWriteSubscriberTestCase { use AdminApiTestBehaviour; - protected function upsertProduct(array $payload): void + protected function upsertEntity(string $entity, array $payload): void { $this->getBrowser()->jsonRequest( 'POST', '/api/_action/sync', [ 'write-product' => [ - 'entity' => 'product', + 'entity' => $entity, 'action' => 'upsert', 'payload' => [$payload], ], diff --git a/tests/Functional/DataAbstractionLayer/EntityWriteSubscriberDalTest.php b/tests/Functional/DataAbstractionLayer/EntityWriteSubscriberDalTest.php index 0720fdd..783c523 100644 --- a/tests/Functional/DataAbstractionLayer/EntityWriteSubscriberDalTest.php +++ b/tests/Functional/DataAbstractionLayer/EntityWriteSubscriberDalTest.php @@ -4,11 +4,16 @@ namespace Swh\SmartRelationSync\Tests\Functional\DataAbstractionLayer; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; + class EntityWriteSubscriberDalTest extends AbstractEntityWriteSubscriberTestCase { - protected function upsertProduct(array $payload): void + protected function upsertEntity(string $entity, array $payload): void { - $this->getContainer()->get('product.repository') - ->upsert([$payload], $this->context); + $repository = $this->getContainer()->get($entity . '.repository'); + + assert($repository instanceof EntityRepository); + + $repository->upsert([$payload], $this->context); } } diff --git a/tests/TestBootstrap.php b/tests/TestBootstrap.php index ac7457f..2ffb362 100644 --- a/tests/TestBootstrap.php +++ b/tests/TestBootstrap.php @@ -9,5 +9,6 @@ (new TestBootstrapper()) ->setClassLoader($loader) ->setPlatformEmbedded(true) + ->addActivePlugins('SmartRelationSyncTestPlugin') ->addCallingPlugin(__DIR__ . '/../composer.json') ->bootstrap();