diff --git a/src/Controller/StatusController.php b/src/Controller/StatusController.php index 66de83eac..55ee590c7 100644 --- a/src/Controller/StatusController.php +++ b/src/Controller/StatusController.php @@ -247,6 +247,12 @@ public function checkConnection(Request $request, Context $context): JsonRespons throw MigrationException::noConnectionFound(); } + $credentialFields = $request->request->all('credentialFields'); + + if (!empty($credentialFields)) { + $connection->setCredentialFields($credentialFields); + } + $migrationContext = $this->migrationContextFactory->createByConnection($connection); $information = $this->migrationDataFetcher->getEnvironmentInformation($migrationContext, $context); diff --git a/src/Core/Migration/Migration1764145444AddFingerprintToConnectionTable.php b/src/Core/Migration/Migration1764145444AddFingerprintToConnectionTable.php new file mode 100644 index 000000000..e1eea34e7 --- /dev/null +++ b/src/Core/Migration/Migration1764145444AddFingerprintToConnectionTable.php @@ -0,0 +1,38 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Core\Migration; + +use Doctrine\DBAL\Connection; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Migration\MigrationStep; + +/** + * @internal + */ +#[Package('fundamentals@after-sales')] +class Migration1764145444AddFingerprintToConnectionTable extends MigrationStep +{ + public const TABLE = 'swag_migration_connection'; + + public const COLUMN = 'source_system_fingerprint'; + + public function getCreationTimestamp(): int + { + return 1764145444; + } + + public function update(Connection $connection): void + { + $this->addColumn( + connection: $connection, + table: self::TABLE, + column: self::COLUMN, + type: 'VARCHAR(255)', + ); + } +} diff --git a/src/DataProvider/Service/EnvironmentService.php b/src/DataProvider/Service/EnvironmentService.php index 131301c1c..e093a1016 100644 --- a/src/DataProvider/Service/EnvironmentService.php +++ b/src/DataProvider/Service/EnvironmentService.php @@ -9,6 +9,7 @@ use Shopware\Core\Defaults; use Shopware\Core\Framework\Api\Context\SystemSource; +use Shopware\Core\Framework\App\ShopId\ShopIdProvider; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; @@ -18,6 +19,7 @@ use Shopware\Core\Framework\Store\Services\StoreClient; use Shopware\Core\System\Currency\CurrencyCollection; use Shopware\Core\System\Language\LanguageCollection; +use Shopware\Core\System\SystemConfig\SystemConfigService; #[Package('fundamentals@after-sales')] class EnvironmentService implements EnvironmentServiceInterface @@ -33,11 +35,12 @@ public function __construct( private readonly string $shopwareRevision, private readonly StoreClient $storeClient, private readonly AbstractExtensionDataProvider $extensionDataProvider, + private readonly SystemConfigService $systemConfigService, ) { } /** - * @return array> + * @return array|null> */ public function getEnvironmentData(Context $context): array { @@ -72,9 +75,21 @@ public function getEnvironmentData(Context $context): array 'revision' => $this->shopwareRevision, 'additionalData' => [], 'updateAvailable' => $updateAvailable, + 'shopIdV2' => $this->getShopIdV2(), ]; } + private function getShopIdV2(): ?string + { + $response = $this->systemConfigService->get(ShopIdProvider::SHOP_ID_SYSTEM_CONFIG_KEY_V2); + + if (\is_array($response) && isset($response['id'])) { + return $response['id']; + } + + return null; + } + private function isPluginUpdateAvailable(Context $context): bool { $criteria = new Criteria(); diff --git a/src/DependencyInjection/dataProvider.xml b/src/DependencyInjection/dataProvider.xml index 2bb1e8393..4e9d72701 100644 --- a/src/DependencyInjection/dataProvider.xml +++ b/src/DependencyInjection/dataProvider.xml @@ -278,6 +278,7 @@ %kernel.shopware_version_revision% + diff --git a/src/DependencyInjection/migration.xml b/src/DependencyInjection/migration.xml index 22f83d773..c6a9022c5 100644 --- a/src/DependencyInjection/migration.xml +++ b/src/DependencyInjection/migration.xml @@ -175,6 +175,24 @@ + + + + + + + + + + + + + + + + + + @@ -192,6 +210,7 @@ + diff --git a/src/Exception/MigrationException.php b/src/Exception/MigrationException.php index e61c946ff..8fefdd21d 100644 --- a/src/Exception/MigrationException.php +++ b/src/Exception/MigrationException.php @@ -13,6 +13,9 @@ use Shopware\Core\Framework\Log\Package; use Symfony\Component\HttpFoundation\Response; +/** + * @codeCoverageIgnore + */ #[Package('fundamentals@after-sales')] class MigrationException extends HttpException { @@ -110,6 +113,8 @@ class MigrationException extends HttpException public const INVALID_ID = 'SWAG_MIGRATION__INVALID_ID'; + public const DUPLICATE_SOURCE_CONNECTION = 'SWAG_MIGRATION__DUPLICATE_SOURCE_CONNECTION'; + public static function associationEntityRequiredMissing(string $entity, string $missingEntity): self { return new AssociationEntityRequiredMissingException( @@ -581,4 +586,13 @@ public static function invalidId(string $entityId, string $entityName): self ['entityId' => $entityId, 'entityName' => $entityName] ); } + + public static function duplicateSourceConnection(): self + { + return new self( + Response::HTTP_CONFLICT, + self::DUPLICATE_SOURCE_CONNECTION, + 'A connection to this source system already exists.', + ); + } } diff --git a/src/Migration/Connection/Fingerprint/MigrationFingerprintService.php b/src/Migration/Connection/Fingerprint/MigrationFingerprintService.php new file mode 100644 index 000000000..574afd8f1 --- /dev/null +++ b/src/Migration/Connection/Fingerprint/MigrationFingerprintService.php @@ -0,0 +1,75 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Migration\Connection\Fingerprint; + +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter; +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\Provider\MigrationFingerprintProviderInterface; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionCollection; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; + +#[Package('fundamentals@after-sales')] +readonly class MigrationFingerprintService implements MigrationFingerprintServiceInterface +{ + /** + * @internal + * + * @param MigrationFingerprintProviderInterface[] $providers + * @param EntityRepository $connectionRepo + */ + public function __construct( + private iterable $providers, + private EntityRepository $connectionRepo, + ) { + } + + /** + * @param array|null $credentialFields + */ + public function generate(?array $credentialFields, SwagMigrationConnectionEntity $connection): ?string + { + if (empty($credentialFields)) { + return null; + } + + foreach ($this->providers as $provider) { + if ($provider->supports($connection->getProfileName())) { + try { + return $provider->provide($credentialFields, $connection); + } catch (\Throwable) { + return null; + } + } + } + + return null; + } + + public function check(?string $fingerprint, Context $context, ?string $excludeConnectionId): bool + { + if (empty($fingerprint)) { + return false; + } + + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('sourceSystemFingerprint', $fingerprint)); + + if (isset($excludeConnectionId)) { + $criteria->addFilter(new NotFilter(MultiFilter::CONNECTION_AND, [ + new EqualsFilter('id', $excludeConnectionId), + ])); + } + + return $this->connectionRepo->searchIds($criteria, $context)->getTotal() > 0; + } +} diff --git a/src/Migration/Connection/Fingerprint/MigrationFingerprintServiceInterface.php b/src/Migration/Connection/Fingerprint/MigrationFingerprintServiceInterface.php new file mode 100644 index 000000000..ad89ddca7 --- /dev/null +++ b/src/Migration/Connection/Fingerprint/MigrationFingerprintServiceInterface.php @@ -0,0 +1,23 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Migration\Connection\Fingerprint; + +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; + +#[Package('fundamentals@after-sales')] +interface MigrationFingerprintServiceInterface +{ + /** + * @param array|null $credentialFields + */ + public function generate(?array $credentialFields, SwagMigrationConnectionEntity $connection): ?string; + + public function check(?string $fingerprint, Context $context, ?string $excludeConnectionId): bool; +} diff --git a/src/Migration/Connection/Fingerprint/Provider/MigrationFingerprintProviderInterface.php b/src/Migration/Connection/Fingerprint/Provider/MigrationFingerprintProviderInterface.php new file mode 100644 index 000000000..4dd363662 --- /dev/null +++ b/src/Migration/Connection/Fingerprint/Provider/MigrationFingerprintProviderInterface.php @@ -0,0 +1,22 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Migration\Connection\Fingerprint\Provider; + +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; + +#[Package('fundamentals@after-sales')] +interface MigrationFingerprintProviderInterface +{ + public static function supports(string $profileName): bool; + + /** + * @param array|null $credentialFields + */ + public function provide(?array $credentialFields, SwagMigrationConnectionEntity $connection): ?string; +} diff --git a/src/Migration/Connection/Fingerprint/Provider/Shopware5FingerprintProvider.php b/src/Migration/Connection/Fingerprint/Provider/Shopware5FingerprintProvider.php new file mode 100644 index 000000000..8428d2517 --- /dev/null +++ b/src/Migration/Connection/Fingerprint/Provider/Shopware5FingerprintProvider.php @@ -0,0 +1,118 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Migration\Connection\Fingerprint\Provider; + +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Util\Hasher; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; +use SwagMigrationAssistant\Migration\Gateway\Reader\EnvironmentReaderInterface; +use SwagMigrationAssistant\Migration\MigrationContextFactoryInterface; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Api\Reader\EnvironmentReader as ApiEnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Api\ShopwareApiGateway; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Local\Reader\EnvironmentReader as LocalEnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Local\ShopwareLocalGateway; +use SwagMigrationAssistant\Profile\Shopware54\Shopware54Profile; +use SwagMigrationAssistant\Profile\Shopware55\Shopware55Profile; +use SwagMigrationAssistant\Profile\Shopware56\Shopware56Profile; +use SwagMigrationAssistant\Profile\Shopware57\Shopware57Profile; + +/** + * @internal + */ +#[Package('fundamentals@after-sales')] +class Shopware5FingerprintProvider implements MigrationFingerprintProviderInterface +{ + public function __construct( + private readonly MigrationContextFactoryInterface $migrationContextFactory, + private readonly ApiEnvironmentReader $apiEnvironmentReader, + private readonly LocalEnvironmentReader $localEnvironmentReader, + ) { + } + + public static function supports(string $profileName): bool + { + return \in_array( + $profileName, + [ + Shopware54Profile::PROFILE_NAME, + Shopware55Profile::PROFILE_NAME, + Shopware56Profile::PROFILE_NAME, + Shopware57Profile::PROFILE_NAME, + ], + true + ); + } + + /** + * @param array|null $credentialFields + */ + public function provide(?array $credentialFields, SwagMigrationConnectionEntity $connection): ?string + { + if (empty($credentialFields)) { + return null; + } + + $connection->setCredentialFields($credentialFields); + $migrationContext = $this->migrationContextFactory->createByConnection($connection); + + $environmentReader = $this->getEnvironmentReader($connection->getGatewayName()); + + if ($environmentReader === null) { + return null; + } + + $response = $environmentReader->read($migrationContext); + $data = $this->extractData($response); + + if ($data === null) { + return null; + } + + return Hasher::hash($data['esdKey'] . $data['installationDate']); + } + + /** + * @param array $response + * + * @return array{esdKey: string, installationDate: string}|null + */ + private function extractData(array $response): ?array + { + $config = null; + + if (\array_key_exists('config', $response)) { + $config = $response['config']; + } + + if (\array_key_exists('environmentInformation', $response)) { + $config = $response['environmentInformation']['config'] ?? null; + } + + if ($config === null) { + return null; + } + + return [ + 'esdKey' => $config['esdKey'], + 'installationDate' => $config['installationDate'], + ]; + } + + private function getEnvironmentReader(string $gatewayName): ?EnvironmentReaderInterface + { + if ($gatewayName === ShopwareApiGateway::GATEWAY_NAME) { + return $this->apiEnvironmentReader; + } + + if ($gatewayName === ShopwareLocalGateway::GATEWAY_NAME) { + return $this->localEnvironmentReader; + } + + return null; + } +} diff --git a/src/Migration/Connection/Fingerprint/Provider/Shopware6FingerprintProvider.php b/src/Migration/Connection/Fingerprint/Provider/Shopware6FingerprintProvider.php new file mode 100644 index 000000000..6aeb07a62 --- /dev/null +++ b/src/Migration/Connection/Fingerprint/Provider/Shopware6FingerprintProvider.php @@ -0,0 +1,49 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Migration\Connection\Fingerprint\Provider; + +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; +use SwagMigrationAssistant\Migration\MigrationContextFactoryInterface; +use SwagMigrationAssistant\Profile\Shopware6\Gateway\Api\Reader\EnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware6\Shopware6MajorProfile; + +/** + * @internal + */ +#[Package('fundamentals@after-sales')] +class Shopware6FingerprintProvider implements MigrationFingerprintProviderInterface +{ + public function __construct( + private readonly MigrationContextFactoryInterface $migrationContextFactory, + private readonly EnvironmentReader $environmentReader, + ) { + } + + public static function supports(string $profileName): bool + { + return $profileName === Shopware6MajorProfile::PROFILE_NAME; + } + + /** + * @param array|null $credentialFields + */ + public function provide(?array $credentialFields, SwagMigrationConnectionEntity $connection): ?string + { + if (empty($credentialFields)) { + return null; + } + + $connection->setCredentialFields($credentialFields); + $migrationContext = $this->migrationContextFactory->createByConnection($connection); + + $response = $this->environmentReader->read($migrationContext); + + return $response['environmentInformation']['shopIdV2'] ?? null; + } +} diff --git a/src/Migration/Connection/SwagMigrationConnectionDefinition.php b/src/Migration/Connection/SwagMigrationConnectionDefinition.php index eebe9b006..41d8e9431 100644 --- a/src/Migration/Connection/SwagMigrationConnectionDefinition.php +++ b/src/Migration/Connection/SwagMigrationConnectionDefinition.php @@ -54,6 +54,7 @@ protected function defineFields(): FieldCollection new PremappingField('premapping', 'premapping'), (new StringField('profile_name', 'profileName'))->addFlags(new Required()), (new StringField('gateway_name', 'gatewayName'))->addFlags(new Required()), + new StringField('source_system_fingerprint', 'sourceSystemFingerprint'), new CreatedAtField(), new UpdatedAtField(), new OneToManyAssociationField('runs', SwagMigrationRunDefinition::class, 'connection_id'), diff --git a/src/Migration/Connection/SwagMigrationConnectionEntity.php b/src/Migration/Connection/SwagMigrationConnectionEntity.php index 9982f6adf..5197bccb9 100644 --- a/src/Migration/Connection/SwagMigrationConnectionEntity.php +++ b/src/Migration/Connection/SwagMigrationConnectionEntity.php @@ -36,6 +36,8 @@ class SwagMigrationConnectionEntity extends Entity protected string $gatewayName = ''; + protected ?string $sourceSystemFingerprint = null; + protected ?SwagMigrationRunCollection $runs = null; protected ?SwagMigrationMappingCollection $mappings = null; @@ -104,6 +106,16 @@ public function setGatewayName(string $gatewayName): void $this->gatewayName = $gatewayName; } + public function getSourceSystemFingerprint(): ?string + { + return $this->sourceSystemFingerprint; + } + + public function setSourceSystemFingerprint(string $sourceSystemFingerprint): void + { + $this->sourceSystemFingerprint = $sourceSystemFingerprint; + } + public function getRuns(): ?SwagMigrationRunCollection { return $this->runs; diff --git a/src/Migration/Run/RunService.php b/src/Migration/Run/RunService.php index c052f627c..ae881e338 100644 --- a/src/Migration/Run/RunService.php +++ b/src/Migration/Run/RunService.php @@ -23,6 +23,7 @@ use Shopware\Storefront\Theme\ThemeDefinition; use Shopware\Storefront\Theme\ThemeService; use SwagMigrationAssistant\Exception\MigrationException; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\MigrationFingerprintServiceInterface; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionCollection; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; use SwagMigrationAssistant\Migration\DataSelection\DataSelectionCollection; @@ -74,6 +75,7 @@ public function __construct( private readonly MigrationContextFactoryInterface $migrationContextFactory, private readonly PremappingServiceInterface $premappingService, private readonly RunTransitionServiceInterface $runTransitionService, + private readonly MigrationFingerprintServiceInterface $connectionFingerprintService, ) { } @@ -134,7 +136,7 @@ public function getRunStatus(Context $context): MigrationState } /** - * @param array|null $credentialFields + * @param array|null $credentialFields */ public function updateConnectionCredentials(Context $context, string $connectionUuid, ?array $credentialFields): void { @@ -142,11 +144,33 @@ public function updateConnectionCredentials(Context $context, string $connection throw MigrationException::migrationIsAlreadyRunning(); } - $context->scope(MigrationContext::SOURCE_CONTEXT, function (Context $context) use ($connectionUuid, $credentialFields): void { + $connection = $this->connectionRepo->search(new Criteria([$connectionUuid]), $context)->getEntities()->first(); + + if ($connection === null) { + throw MigrationException::noConnectionFound(); + } + + $fingerprint = $this->connectionFingerprintService->generate( + $credentialFields, + $connection, + ); + + $hasDuplicate = $this->connectionFingerprintService->check( + $fingerprint, + $context, + $connection->getId(), + ); + + if ($hasDuplicate) { + throw MigrationException::duplicateSourceConnection(); + } + + $context->scope(MigrationContext::SOURCE_CONTEXT, function (Context $context) use ($connectionUuid, $credentialFields, $fingerprint): void { $this->connectionRepo->update([ [ 'id' => $connectionUuid, 'credentialFields' => $credentialFields, + 'sourceSystemFingerprint' => $fingerprint, ], ], $context); }); diff --git a/src/Profile/Shopware/Gateway/Local/Reader/EnvironmentReader.php b/src/Profile/Shopware/Gateway/Local/Reader/EnvironmentReader.php index d5da58acd..01c486655 100644 --- a/src/Profile/Shopware/Gateway/Local/Reader/EnvironmentReader.php +++ b/src/Profile/Shopware/Gateway/Local/Reader/EnvironmentReader.php @@ -7,6 +7,7 @@ namespace SwagMigrationAssistant\Profile\Shopware\Gateway\Local\Reader; +use Doctrine\DBAL\ArrayParameterType; use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\FetchModeHelper; use Shopware\Core\Framework\Log\Package; use SwagMigrationAssistant\Migration\Gateway\Reader\EnvironmentReaderInterface; @@ -27,9 +28,40 @@ public function read(MigrationContextInterface $migrationContext): array 'host' => $this->getHost($migrationContext), 'additionalData' => $this->getAdditionalData($migrationContext), 'defaultCurrency' => $this->getDefaultCurrency($migrationContext), + 'config' => $this->getConfig($migrationContext), ]; } + /** + * @return array + */ + protected function getConfig(MigrationContextInterface $migrationContext): array + { + $connection = $this->getConnection($migrationContext); + + $configNames = [ + 'esdKey', + 'installationDate', + ]; + + $query = $connection->createQueryBuilder(); + + $query->select('config.name', 'config.value') + ->from('s_core_config_elements', 'config') + ->where('config.name IN (:configNames)') + ->setParameter('configNames', $configNames, ArrayParameterType::STRING); + + $rows = $query->executeQuery()->fetchAllAssociative(); + + $result = []; + + foreach ($rows as $row) { + $result[$row['name']] = \unserialize($row['value'], ['allowed_classes' => false]); + } + + return $result; + } + protected function getDefaultCurrency(MigrationContextInterface $migrationContext): string { $connection = $this->getConnection($migrationContext); diff --git a/src/Resources/app/administration/src/core/service/api/swag-migration.api.service.ts b/src/Resources/app/administration/src/core/service/api/swag-migration.api.service.ts index 9c878aac6..749c5c24e 100644 --- a/src/Resources/app/administration/src/core/service/api/swag-migration.api.service.ts +++ b/src/Resources/app/administration/src/core/service/api/swag-migration.api.service.ts @@ -102,17 +102,24 @@ export default class MigrationApiService extends ApiService { async checkConnection( connectionId: string, + credentialFields?: Record, additionalHeaders: AdditionalHeaders = {}, ): Promise { // @ts-ignore const headers = this.getBasicHeaders(additionalHeaders); + const payload: { connectionId: string; credentialFields?: Record } = { connectionId }; + + if (credentialFields) { + payload.credentialFields = credentialFields; + } + // @ts-ignore return this.httpClient .post( // @ts-ignore `_action/${this.getApiBasePath()}/check-connection`, - { connectionId }, + payload, { ...this.basicConfig, headers, diff --git a/src/Resources/app/administration/src/module/swag-migration/page/wizard/swag-migration-wizard/index.ts b/src/Resources/app/administration/src/module/swag-migration/page/wizard/swag-migration-wizard/index.ts index 26ef1a686..33909e504 100644 --- a/src/Resources/app/administration/src/module/swag-migration/page/wizard/swag-migration-wizard/index.ts +++ b/src/Resources/app/administration/src/module/swag-migration/page/wizard/swag-migration-wizard/index.ts @@ -286,40 +286,42 @@ export default Shopware.Component.wrapComponentConfig({ }); }, - onConnect() { + async onConnect() { this.isLoading = true; this.errorMessageSnippet = ''; this.trimCredentials(); - return this.migrationApiService - .updateConnectionCredentials(this.connection.id, this.connection.credentialFields) - .then((response) => { - if (response.errors && response.errors.length > 0) { - this.isLoading = false; - this.onResponseError(''); - } - return this.doConnectionCheck(); - }) - .catch((error) => { - this.isLoading = false; - this.onResponseError(error.response.data.errors[0].code); - }); + try { + const isValid = await this.doConnectionCheck(this.connection.credentialFields); + + if (isValid) { + await this.migrationApiService.updateConnectionCredentials( + this.connection.id, + this.connection.credentialFields, + ); + } + } catch (error) { + this.onResponseError(error.response.data.errors[0].code); + } finally { + this.isLoading = false; + } }, - doConnectionCheck() { + doConnectionCheck(credentialFields?: Record) { this.isLoading = true; return this.migrationApiService - .checkConnection(this.connection.id) + .checkConnection(this.connection.id, credentialFields) .then((connectionCheckResponse) => { this.migrationStore.setConnectionId(this.connection.id); this.isLoading = false; if (!connectionCheckResponse) { this.onResponseError(-1); - return; + return false; } + this.migrationStore.setEnvironmentInformation(connectionCheckResponse); this.migrationStore.setDataSelectionIds([]); this.migrationStore.setPremapping([]); @@ -327,7 +329,7 @@ export default Shopware.Component.wrapComponentConfig({ if (connectionCheckResponse.requestStatus === undefined) { this.navigateToRoute(this.routes.credentialsSuccess); - return; + return true; } if ( @@ -335,7 +337,7 @@ export default Shopware.Component.wrapComponentConfig({ connectionCheckResponse.requestStatus.isWarning === false ) { this.onResponseError(connectionCheckResponse.requestStatus.code); - return; + return false; } // create warning for success page @@ -349,6 +351,8 @@ export default Shopware.Component.wrapComponentConfig({ } this.navigateToRoute(this.routes.credentialsSuccess); + + return true; }) .catch((error) => { this.isLoading = false; @@ -358,6 +362,8 @@ export default Shopware.Component.wrapComponentConfig({ this.migrationStore.setPremapping([]); this.migrationStore.setDataSelectionTableData([]); this.onResponseError(error.response.data.errors[0].code); + + return false; }); }, diff --git a/src/Resources/app/administration/src/module/swag-migration/snippet/de.json b/src/Resources/app/administration/src/module/swag-migration/snippet/de.json index 051b56105..d1101f37f 100644 --- a/src/Resources/app/administration/src/module/swag-migration/snippet/de.json +++ b/src/Resources/app/administration/src/module/swag-migration/snippet/de.json @@ -168,7 +168,8 @@ "SWAG_MIGRATION__DATABASE_CONNECTION_ERROR": "Die Datenbank Verbindung konnte nicht hergestellt werden.", "SWAG_MIGRATION__DATABASE_CONNECTION_ATTRIBUTES_WRONG": "Die Datenbankverbindung hat nicht die richtigen Attribute und diese können nicht gesetzt werden.", "SWAG_MIGRATION__IS_RUNNING": "Eine Migration ist zurzeit im Gange. Du kannst deshalb die Zugangsdaten nicht bearbeiten, bis die Migration abgeschlossen ist.", - "SWAG_MIGRATION__SSL_REQUIRED": "Wir haben festgestellt, dass die angegebene Shop-Domain eine SSL-Verbindung erfordert." + "SWAG_MIGRATION__SSL_REQUIRED": "Wir haben festgestellt, dass die angegebene Shop-Domain eine SSL-Verbindung erfordert.", + "SWAG_MIGRATION__DUPLICATE_SOURCE_CONNECTION": "Es besteht bereits eine Verbindung zu demselben Quellsystem. Bitte verwende eine andere Verbindung." } } } diff --git a/src/Resources/app/administration/src/module/swag-migration/snippet/en.json b/src/Resources/app/administration/src/module/swag-migration/snippet/en.json index e18062e41..0ee48c1d0 100644 --- a/src/Resources/app/administration/src/module/swag-migration/snippet/en.json +++ b/src/Resources/app/administration/src/module/swag-migration/snippet/en.json @@ -168,7 +168,8 @@ "SWAG_MIGRATION__DATABASE_CONNECTION_ERROR": "Database connection could not be established.", "SWAG_MIGRATION__DATABASE_CONNECTION_ATTRIBUTES_WRONG": "Database connection does not have the right attributes and they can not be set.", "SWAG_MIGRATION__IS_RUNNING": "A migration is currently in progress. Therefore, you cannot edit the credentials until the migration is complete.", - "SWAG_MIGRATION__SSL_REQUIRED": "We have determined that the given shop domain is required a SSL connection." + "SWAG_MIGRATION__SSL_REQUIRED": "We have determined that the given shop domain is required a SSL connection.", + "SWAG_MIGRATION__DUPLICATE_SOURCE_CONNECTION": "A connection to the same source system already exists. Please use another connection." } } } diff --git a/tests/Core/Migration/Migration1754896654TruncateMigrationLogsTest.php b/tests/Core/Migration/Migration1754896654TruncateMigrationLogsTest.php index f1f913f7c..59ae55a7c 100644 --- a/tests/Core/Migration/Migration1754896654TruncateMigrationLogsTest.php +++ b/tests/Core/Migration/Migration1754896654TruncateMigrationLogsTest.php @@ -5,7 +5,7 @@ * file that was distributed with this source code. */ -namespace Core\Migration; +namespace SwagMigrationAssistant\Test\Core\Migration; use Doctrine\DBAL\Connection; use PHPUnit\Framework\Attributes\CoversClass; diff --git a/tests/Core/Migration/Migration1754897550AddFieldsToMigrationLogsTest.php b/tests/Core/Migration/Migration1754897550AddFieldsToMigrationLogsTest.php index ab3d83477..1c509f694 100644 --- a/tests/Core/Migration/Migration1754897550AddFieldsToMigrationLogsTest.php +++ b/tests/Core/Migration/Migration1754897550AddFieldsToMigrationLogsTest.php @@ -5,7 +5,7 @@ * file that was distributed with this source code. */ -namespace Core\Migration; +namespace SwagMigrationAssistant\Test\Core\Migration; use Doctrine\DBAL\Connection; use PHPUnit\Framework\Attributes\CoversClass; diff --git a/tests/Core/Migration/Migration1757598733AddMigrationFixesTableTest.php b/tests/Core/Migration/Migration1757598733AddMigrationFixesTableTest.php index dc8e36af0..54f7fd898 100644 --- a/tests/Core/Migration/Migration1757598733AddMigrationFixesTableTest.php +++ b/tests/Core/Migration/Migration1757598733AddMigrationFixesTableTest.php @@ -5,7 +5,7 @@ * file that was distributed with this source code. */ -namespace Core\Migration; +namespace SwagMigrationAssistant\Test\Core\Migration; use Doctrine\DBAL\Connection; use PHPUnit\Framework\Attributes\CoversClass; diff --git a/tests/Core/Migration/Migration1759000000AddIsResettingChecksumsToSettingTest.php b/tests/Core/Migration/Migration1759000000AddIsResettingChecksumsToSettingTest.php index 70a2056cf..8593a3081 100644 --- a/tests/Core/Migration/Migration1759000000AddIsResettingChecksumsToSettingTest.php +++ b/tests/Core/Migration/Migration1759000000AddIsResettingChecksumsToSettingTest.php @@ -5,66 +5,36 @@ * file that was distributed with this source code. */ -namespace Core\Migration; +namespace SwagMigrationAssistant\Test\Core\Migration; -use Doctrine\DBAL\Connection; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Shopware\Core\Framework\Log\Package; use Shopware\Core\Framework\Test\TestCaseBase\KernelLifecycleManager; -use Shopware\Core\Framework\Test\TestCaseBase\KernelTestBehaviour; use SwagMigrationAssistant\Core\Migration\Migration1759000000AddIsResettingChecksumsToSetting; +use SwagMigrationAssistant\Test\TableHelperTrait; #[Package('fundamentals@after-sales')] #[CoversClass(Migration1759000000AddIsResettingChecksumsToSetting::class)] class Migration1759000000AddIsResettingChecksumsToSettingTest extends TestCase { - use KernelTestBehaviour; - - private Connection $connection; - - protected function setUp(): void - { - parent::setUp(); - - $this->connection = KernelLifecycleManager::getConnection(); - } + use TableHelperTrait; public function testShouldAddIsResettingChecksumsFieldToSettingTable(): void { - static::assertTrue($this->tableExists()); + $connection = KernelLifecycleManager::getConnection(); - if ($this->columnExists()) { - $this->connection->executeStatement(\sprintf( - 'ALTER TABLE %s DROP COLUMN %s', - Migration1759000000AddIsResettingChecksumsToSetting::TABLE, - Migration1759000000AddIsResettingChecksumsToSetting::COLUMN - )); - } + $table = Migration1759000000AddIsResettingChecksumsToSetting::TABLE; + $column = Migration1759000000AddIsResettingChecksumsToSetting::COLUMN; - $migration = new Migration1759000000AddIsResettingChecksumsToSetting(); - $migration->update($this->connection); - $migration->update($this->connection); + static::assertTrue($this->tableExists($connection, $table)); - static::assertTrue($this->columnExists()); - } + $this->dropColumnIfExists($connection, $table, $column); - private function tableExists(): bool - { - try { - $this->connection->fetchOne('SELECT 1 FROM ' . Migration1759000000AddIsResettingChecksumsToSetting::TABLE . ' LIMIT 1'); - - return true; - } catch (\Exception) { - return false; - } - } - - private function columnExists(): bool - { - $schemaManager = $this->connection->createSchemaManager(); - $columns = $schemaManager->listTableColumns(Migration1759000000AddIsResettingChecksumsToSetting::TABLE); + $migration = new Migration1759000000AddIsResettingChecksumsToSetting(); + $migration->update($connection); + $migration->update($connection); - return isset($columns[Migration1759000000AddIsResettingChecksumsToSetting::COLUMN]); + static::assertTrue($this->columnExists($connection, $table, $column)); } } diff --git a/tests/Core/Migration/Migration1762346793RenameColumnOfMappingTableTest.php b/tests/Core/Migration/Migration1762346793RenameColumnOfMappingTableTest.php index 57b30d6f1..d7c678581 100644 --- a/tests/Core/Migration/Migration1762346793RenameColumnOfMappingTableTest.php +++ b/tests/Core/Migration/Migration1762346793RenameColumnOfMappingTableTest.php @@ -5,7 +5,7 @@ * file that was distributed with this source code. */ -namespace Core\Migration; +namespace SwagMigrationAssistant\Test\Core\Migration; use PHPUnit\Framework\TestCase; use Shopware\Core\Framework\Log\Package; diff --git a/tests/Core/Migration/Migration1764145444AddFingerprintToConnectionTableTest.php b/tests/Core/Migration/Migration1764145444AddFingerprintToConnectionTableTest.php new file mode 100644 index 000000000..3fbebe6d5 --- /dev/null +++ b/tests/Core/Migration/Migration1764145444AddFingerprintToConnectionTableTest.php @@ -0,0 +1,43 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Test\Core\Migration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Test\TestCaseBase\KernelLifecycleManager; +use SwagMigrationAssistant\Core\Migration\Migration1764145444AddFingerprintToConnectionTable; +use SwagMigrationAssistant\Test\TableHelperTrait; + +/** + * @internal + */ +#[Package('fundamentals@after-sales')] +#[CoversClass(Migration1764145444AddFingerprintToConnectionTable::class)] +class Migration1764145444AddFingerprintToConnectionTableTest extends TestCase +{ + use TableHelperTrait; + + public function testShouldAddFingerprintFieldToConnectionTable(): void + { + $connection = KernelLifecycleManager::getConnection(); + + $table = Migration1764145444AddFingerprintToConnectionTable::TABLE; + $column = Migration1764145444AddFingerprintToConnectionTable::COLUMN; + + static::assertTrue($this->tableExists($connection, $table)); + + $this->dropColumnIfExists($connection, $table, $column); + + $migration = new Migration1764145444AddFingerprintToConnectionTable(); + $migration->update($connection); + $migration->update($connection); + + static::assertTrue($this->columnExists($connection, $table, $column)); + } +} diff --git a/tests/DataProvider/Service/EnvironmentServiceTest.php b/tests/DataProvider/Service/EnvironmentServiceTest.php index 92f885002..b65b3b344 100644 --- a/tests/DataProvider/Service/EnvironmentServiceTest.php +++ b/tests/DataProvider/Service/EnvironmentServiceTest.php @@ -25,6 +25,7 @@ use Shopware\Core\System\Language\LanguageDefinition; use Shopware\Core\System\Language\LanguageEntity; use Shopware\Core\System\Locale\LocaleEntity; +use Shopware\Core\System\SystemConfig\SystemConfigService; use Shopware\Core\Test\Stub\DataAbstractionLayer\StaticEntityRepository; use SwagMigrationAssistant\DataProvider\Service\EnvironmentService; use SwagMigrationAssistant\DataProvider\Service\EnvironmentServiceInterface; @@ -35,9 +36,9 @@ class EnvironmentServiceTest extends TestCase private EnvironmentServiceInterface $environmentService; #[DataProvider('provideEnvironments')] - public function testGetEnvironmentData(string $shopwareVersion, string $defaultCurrency, string $defaultLocale, bool $updateAvailable): void + public function testGetEnvironmentData(string $shopwareVersion, string $defaultCurrency, string $defaultLocale, ?string $shopIdV2, bool $updateAvailable): void { - $this->createEnvironmentService($shopwareVersion, $defaultCurrency, $defaultLocale, $updateAvailable); + $this->createEnvironmentService($shopwareVersion, $defaultCurrency, $defaultLocale, $shopIdV2, $updateAvailable); $data = $this->environmentService->getEnvironmentData(Context::createDefaultContext()); static::assertSame($data, [ @@ -48,19 +49,20 @@ public function testGetEnvironmentData(string $shopwareVersion, string $defaultC 'revision' => $shopwareVersion, 'additionalData' => [], 'updateAvailable' => $updateAvailable, + 'shopIdV2' => $shopIdV2, ]); } public static function provideEnvironments(): array { return [ - ['6.5.6.1', 'EUR', 'de-DE', false], - ['6.5.6.2', 'USD', 'en-GB', false], - ['6.5.0.0', 'USD', 'en-GB', true], + ['6.5.6.1', 'EUR', 'de-DE', null, false], + ['6.5.6.2', 'USD', 'en-GB', 'shop-id', false], + ['6.5.0.0', 'USD', 'en-GB', 'shop-id', true], ]; } - protected function createEnvironmentService(string $shopwareVersion = '6.5.6.1', string $defaultCurrency = 'EUR', string $defaultLocale = 'de-DE', bool $updateAvailable = false): void + protected function createEnvironmentService(string $shopwareVersion = '6.5.6.1', string $defaultCurrency = 'EUR', string $defaultLocale = 'de-DE', ?string $shopIdV2 = 'shop-id', bool $updateAvailable = false): void { $currencyEntity = new CurrencyEntity(); $currencyEntity->setId(Defaults::CURRENCY); @@ -109,6 +111,11 @@ protected function createEnvironmentService(string $shopwareVersion = '6.5.6.1', ); $extensionDataProviderStub = static::createStub(AbstractExtensionDataProvider::class); + $systemConfigService = $this->createMock(SystemConfigService::class); + $systemConfigService->method('get')->willReturn($shopIdV2 ? [ + 'id' => $shopIdV2, + ] : null); + $this->environmentService = new EnvironmentService( $currencyRepo, $languageRepo, @@ -116,6 +123,7 @@ protected function createEnvironmentService(string $shopwareVersion = '6.5.6.1', $shopwareVersion, $storeClientStub, $extensionDataProviderStub, + $systemConfigService, ); } } diff --git a/tests/Migration/Controller/StatusControllerTest.php b/tests/Migration/Controller/StatusControllerTest.php index f2ddedd2b..173f48a7f 100644 --- a/tests/Migration/Controller/StatusControllerTest.php +++ b/tests/Migration/Controller/StatusControllerTest.php @@ -22,6 +22,7 @@ use Shopware\Storefront\Theme\ThemeService; use SwagMigrationAssistant\Controller\StatusController; use SwagMigrationAssistant\Exception\MigrationException; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\MigrationFingerprintService; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionCollection; use SwagMigrationAssistant\Migration\DataSelection\DataSelectionRegistry; use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; @@ -183,6 +184,7 @@ protected function setUp(): void static::getContainer()->get(MigrationContextFactory::class), static::getContainer()->get(PremappingService::class), static::getContainer()->get(RunTransitionService::class), + static::getContainer()->get(MigrationFingerprintService::class), ), new DataSelectionRegistry([ new ProductDataSelection(), @@ -316,8 +318,9 @@ public function testUpdateConnectionCredentials(): void $params = [ 'connectionId' => $this->connectionId, 'credentialFields' => [ - 'testCredentialField1' => 'field1', - 'testCredentialField2' => 'field2', + 'dbHost' => 'localhost', + 'dbPort' => '3306', + 'dbName' => 'shopware', ], ]; diff --git a/tests/Migration/Services/MigrationDataWriterTest.php b/tests/Migration/Services/MigrationDataWriterTest.php index d7f1bd3be..068143965 100644 --- a/tests/Migration/Services/MigrationDataWriterTest.php +++ b/tests/Migration/Services/MigrationDataWriterTest.php @@ -43,6 +43,7 @@ use Shopware\Core\System\StateMachine\Aggregation\StateMachineState\StateMachineStateCollection; use Shopware\Core\System\StateMachine\StateMachineCollection; use Shopware\Storefront\Theme\ThemeCollection; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\MigrationFingerprintService; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionCollection; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; use SwagMigrationAssistant\Migration\Data\SwagMigrationDataCollection; @@ -312,6 +313,7 @@ public function initServices(): void $migrationContextFactoryMock, $premappingService, static::getContainer()->get(RunTransitionService::class), + static::getContainer()->get(MigrationFingerprintService::class), ); } diff --git a/tests/Migration/Services/RunServiceTest.php b/tests/Migration/Services/RunServiceTest.php index 74ea93aa3..95c33632b 100644 --- a/tests/Migration/Services/RunServiceTest.php +++ b/tests/Migration/Services/RunServiceTest.php @@ -11,7 +11,8 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\IdSearchResult; use Shopware\Core\Framework\Log\Package; use Shopware\Core\Framework\Store\Services\TrackingEventClient; use Shopware\Core\Framework\Uuid\Uuid; @@ -20,18 +21,18 @@ use Shopware\Storefront\Theme\ThemeCollection; use Shopware\Storefront\Theme\ThemeService; use SwagMigrationAssistant\Exception\MigrationException; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\MigrationFingerprintServiceInterface; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionCollection; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionDefinition; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; -use SwagMigrationAssistant\Migration\Data\SwagMigrationDataCollection; use SwagMigrationAssistant\Migration\DataSelection\DataSelectionRegistry; use SwagMigrationAssistant\Migration\EnvironmentInformation; use SwagMigrationAssistant\Migration\Logging\LoggingService; use SwagMigrationAssistant\Migration\Mapping\MappingService; -use SwagMigrationAssistant\Migration\Media\SwagMigrationMediaFileCollection; use SwagMigrationAssistant\Migration\MessageQueue\Message\MigrationProcessMessage; use SwagMigrationAssistant\Migration\MigrationContext; use SwagMigrationAssistant\Migration\MigrationContextFactory; +use SwagMigrationAssistant\Migration\MigrationContextFactoryInterface; use SwagMigrationAssistant\Migration\Premapping\PremappingEntityStruct; use SwagMigrationAssistant\Migration\Premapping\PremappingStruct; use SwagMigrationAssistant\Migration\Run\MigrationProgress; @@ -44,14 +45,18 @@ use SwagMigrationAssistant\Migration\Run\SwagMigrationRunDefinition; use SwagMigrationAssistant\Migration\Run\SwagMigrationRunEntity; use SwagMigrationAssistant\Migration\Service\MigrationDataFetcher; +use SwagMigrationAssistant\Migration\Service\MigrationDataFetcherInterface; use SwagMigrationAssistant\Migration\Service\PremappingService; +use SwagMigrationAssistant\Migration\Service\PremappingServiceInterface; use SwagMigrationAssistant\Migration\Setting\GeneralSettingCollection; use SwagMigrationAssistant\Migration\Setting\GeneralSettingDefinition; use SwagMigrationAssistant\Migration\Setting\GeneralSettingEntity; use SwagMigrationAssistant\Migration\TotalStruct; use SwagMigrationAssistant\Profile\Shopware\DataSelection\ProductDataSelection; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Api\ShopwareApiGateway; use SwagMigrationAssistant\Profile\Shopware\Gateway\Local\ShopwareLocalGateway; use SwagMigrationAssistant\Profile\Shopware55\Shopware55Profile; +use SwagMigrationAssistant\Profile\Shopware57\Shopware57Profile; use SwagMigrationAssistant\Test\Mock\Migration\Run\DummyRunTransitionService; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; @@ -136,6 +141,182 @@ protected function setUp(): void )); } + public function testUpdateConnectionCredentialsWhenMigrationIsRunning(): void + { + $run = new SwagMigrationRunEntity(); + $run->setId(Uuid::randomHex()); + + $this->runRepo = new StaticEntityRepository([ + new SwagMigrationRunCollection([$run]), + ], new SwagMigrationRunDefinition()); + + static::expectExceptionObject(MigrationException::migrationIsAlreadyRunning()); + + $this->createRunService()->updateConnectionCredentials( + Context::createDefaultContext(), + Uuid::randomHex(), + [], + ); + } + + public function testUpdateConnectionCredentialsWhenNoConnectionFound(): void + { + $this->runRepo = new StaticEntityRepository([ + new SwagMigrationRunCollection([]), + ], new SwagMigrationRunDefinition()); + + $this->connectionRepo = new StaticEntityRepository([ + new SwagMigrationConnectionCollection([]), + ], new SwagMigrationConnectionDefinition()); + + static::expectExceptionObject(MigrationException::noConnectionFound()); + + $this->createRunService()->updateConnectionCredentials( + Context::createDefaultContext(), + Uuid::randomHex(), + [], + ); + } + + public function testUpdateConnectionCredentialsWhenDuplicateFingerprintFound(): void + { + $this->runRepo = new StaticEntityRepository([ + new SwagMigrationRunCollection([]), + ], new SwagMigrationRunDefinition()); + + $this->connectionRepo = new StaticEntityRepository([ + new SwagMigrationConnectionCollection([ + (static function (): SwagMigrationConnectionEntity { + $connection = new SwagMigrationConnectionEntity(); + $connection->setId(Uuid::randomHex()); + $connection->setProfileName(Shopware57Profile::PROFILE_NAME); + $connection->setGatewayName(ShopwareApiGateway::GATEWAY_NAME); + + return $connection; + })(), + ]), + new IdSearchResult( + 1, + [], + new Criteria(), + Context::createDefaultContext() + ), + ], new SwagMigrationConnectionDefinition()); + + /** @var MockObject&MigrationFingerprintServiceInterface $fingerprintService */ + $fingerprintService = $this->createMock(MigrationFingerprintServiceInterface::class); + $fingerprintService->expects(static::once())->method('generate')->willReturn('fingerprint'); + $fingerprintService->expects(static::once())->method('check')->willReturn(true); + + static::expectExceptionObject(MigrationException::duplicateSourceConnection()); + + $this->createRunService( + connectionRepo: $this->connectionRepo, + runRepo: $this->runRepo, + fingerprintService: $fingerprintService, + )->updateConnectionCredentials( + Context::createDefaultContext(), + Uuid::randomHex(), + [ + 'endpoint' => 'https://shopware-instance.com/api', + ], + ); + } + + public function testUpdateConnectionCredentialsSuccessfullyWithFingerprint(): void + { + $this->runRepo = new StaticEntityRepository([ + new SwagMigrationRunCollection([]), + ], new SwagMigrationRunDefinition()); + + $connectionEntity = new SwagMigrationConnectionEntity(); + $connectionEntity->setId(Uuid::randomHex()); + $connectionEntity->setProfileName(Shopware57Profile::PROFILE_NAME); + $connectionEntity->setGatewayName(ShopwareApiGateway::GATEWAY_NAME); + + $this->connectionRepo = new StaticEntityRepository([ + new SwagMigrationConnectionCollection([ + $connectionEntity, + ]), + new IdSearchResult( + 0, + [], + new Criteria(), + Context::createDefaultContext() + ), + ], new SwagMigrationConnectionDefinition()); + + $fingerprint = Uuid::randomHex(); + + /** @var MockObject&MigrationFingerprintServiceInterface $fingerprintService */ + $fingerprintService = $this->createMock(MigrationFingerprintServiceInterface::class); + $fingerprintService->expects(static::once())->method('generate')->willReturn($fingerprint); + $fingerprintService->expects(static::once())->method('check')->willReturn(false); + + $runService = $this->createRunService( + connectionRepo: $this->connectionRepo, + runRepo: $this->runRepo, + fingerprintService: $fingerprintService, + ); + + $runService->updateConnectionCredentials( + Context::createDefaultContext(), + $connectionEntity->getId(), + [ + 'endpoint' => 'https://shopware-instance.com/api', + ], + ); + + static::assertCount(1, $this->connectionRepo->updates); + static::assertSame($fingerprint, $this->connectionRepo->updates[0][0]['sourceSystemFingerprint']); + } + + public function testUpdateConnectionCredentialsSuccessfullyWithoutFingerprint(): void + { + $this->runRepo = new StaticEntityRepository([ + new SwagMigrationRunCollection([]), + ], new SwagMigrationRunDefinition()); + + $connectionEntity = new SwagMigrationConnectionEntity(); + $connectionEntity->setId(Uuid::randomHex()); + $connectionEntity->setProfileName(Shopware57Profile::PROFILE_NAME); + $connectionEntity->setGatewayName(ShopwareApiGateway::GATEWAY_NAME); + + $this->connectionRepo = new StaticEntityRepository([ + new SwagMigrationConnectionCollection([ + $connectionEntity, + ]), + new IdSearchResult( + 0, + [], + new Criteria(), + Context::createDefaultContext() + ), + ], new SwagMigrationConnectionDefinition()); + + /** @var MockObject&MigrationFingerprintServiceInterface $fingerprintService */ + $fingerprintService = $this->createMock(MigrationFingerprintServiceInterface::class); + $fingerprintService->expects(static::once())->method('generate')->willReturn(null); + $fingerprintService->expects(static::once())->method('check')->willReturn(false); + + $runService = $this->createRunService( + connectionRepo: $this->connectionRepo, + runRepo: $this->runRepo, + fingerprintService: $fingerprintService, + ); + + $runService->updateConnectionCredentials( + Context::createDefaultContext(), + $connectionEntity->getId(), + [ + 'endpoint' => 'https://shopware-instance.com/api', + ], + ); + + static::assertCount(1, $this->connectionRepo->updates); + static::assertNull($this->connectionRepo->updates[0][0]['sourceSystemFingerprint']); + } + public function testStartMigrationRunSuccessfully(): void { $trackingEventClient = $this->createMock(TrackingEventClient::class); @@ -297,23 +478,10 @@ public function testResumeMigrationInWrongStep(): void ]), ], new SwagMigrationRunDefinition()); - $runService = new RunService( - $runRepo, - $this->createMock(EntityRepository::class), - $this->createMock(MigrationDataFetcher::class), - $this->createMock(DataSelectionRegistry::class), - $this->createMock(EntityRepository::class), - $this->createMock(EntityRepository::class), - $this->createMock(EntityRepository::class), - $this->createMock(ThemeService::class), - $this->createMock(MappingService::class), - $this->createMock(Connection::class), - $this->createMock(LoggingService::class), - $this->createMock(TrackingEventClient::class), - $messageBus, - $this->createMock(MigrationContextFactory::class), - $this->createMock(PremappingService::class), - $runTransitionService + $runService = $this->createRunService( + messageBus: $messageBus, + runTransitionService: $runTransitionService, + runRepo: $runRepo ); try { @@ -349,61 +517,59 @@ public function testResumeMigration(): void ]), ], new SwagMigrationRunDefinition()); - $runService = new RunService( - $runRepo, - $this->createMock(EntityRepository::class), - $this->createMock(MigrationDataFetcher::class), - $this->createMock(DataSelectionRegistry::class), - $this->createMock(EntityRepository::class), - $this->createMock(EntityRepository::class), - $this->createMock(EntityRepository::class), - $this->createMock(ThemeService::class), - $this->createMock(MappingService::class), - $this->createMock(Connection::class), - $this->createMock(LoggingService::class), - $this->createMock(TrackingEventClient::class), - $messageBus, - $this->createMock(MigrationContextFactory::class), - $this->createMock(PremappingService::class), - $runTransitionService + $runService = $this->createRunService( + messageBus: $messageBus, + runTransitionService: $runTransitionService, + runRepo: $runRepo ); $runService->resumeAfterFixes($this->context); } + /** + * @param StaticEntityRepository|null $runRepo + * @param StaticEntityRepository|null $connectionRepo + * @param StaticEntityRepository|null $generalSettingRepo + */ private function createRunService( - MockObject&TrackingEventClient $trackingEventClient, - MockObject&MessageBusInterface $messageBus, - MockObject&PremappingService $premappingService, + (MockObject&TrackingEventClient)|null $trackingEventClient = null, + (MockObject&MessageBusInterface)|null $messageBus = null, + (MockObject&PremappingServiceInterface)|null $premappingService = null, + (MockObject&RunTransitionServiceInterface)|null $runTransitionService = null, + ?StaticEntityRepository $runRepo = null, + ?StaticEntityRepository $connectionRepo = null, + ?StaticEntityRepository $generalSettingRepo = null, + (MockObject&MigrationDataFetcherInterface)|null $dataFetcher = null, + (MockObject&MigrationContextFactoryInterface)|null $migrationContextFactory = null, + (MockObject&MigrationFingerprintServiceInterface)|null $fingerprintService = null, ): RunService { - /** @var StaticEntityRepository $migrationDataRepository */ - $migrationDataRepository = new StaticEntityRepository([]); - /** @var StaticEntityRepository $mediaFileRepository */ - $mediaFileRepository = new StaticEntityRepository([]); + $connectionRepository = $connectionRepo ?? $this->connectionRepo; + /** @var StaticEntityRepository $salesChannelRepository */ $salesChannelRepository = new StaticEntityRepository([]); /** @var StaticEntityRepository $themeRepository */ $themeRepository = new StaticEntityRepository([]); return new RunService( - $this->runRepo, - $this->connectionRepo, - $this->dataFetcher, + $runRepo ?? $this->runRepo, + $connectionRepository, + $dataFetcher ?? $this->dataFetcher, new DataSelectionRegistry([ new ProductDataSelection(), ]), $salesChannelRepository, $themeRepository, - $this->generalSettingRepo, + $generalSettingRepo ?? $this->generalSettingRepo, $this->createMock(ThemeService::class), $this->createMock(MappingService::class), $this->createMock(Connection::class), $this->createMock(LoggingService::class), - $trackingEventClient, - $messageBus, - $this->migrationContextFactory, - $premappingService, - new DummyRunTransitionService(MigrationStep::WAITING_FOR_APPROVE), + $trackingEventClient ?? $this->createMock(TrackingEventClient::class), + $messageBus ?? $this->createMock(MessageBusInterface::class), + $migrationContextFactory ?? $this->migrationContextFactory, + $premappingService ?? $this->createMock(PremappingService::class), + $runTransitionService ?? new DummyRunTransitionService(MigrationStep::WAITING_FOR_APPROVE), + $fingerprintService ?? $this->createMock(MigrationFingerprintServiceInterface::class), ); } } diff --git a/tests/TableHelperTrait.php b/tests/TableHelperTrait.php index 6dbbdb602..189e1dc20 100644 --- a/tests/TableHelperTrait.php +++ b/tests/TableHelperTrait.php @@ -27,13 +27,27 @@ protected function indexExists(Connection $connection, string $table, string $in return !empty($exists); } - protected function dropTable(Connection $connection, string $table): void + protected function columnExists(Connection $connection, string $table, string $column): bool + { + $exists = $connection->fetchOne('SHOW COLUMNS FROM `' . $table . '` LIKE :columnName', ['columnName' => $column]); + + return !empty($exists); + } + + protected function tableExists(Connection $connection, string $table): bool + { + $exists = $connection->fetchOne('SHOW TABLES LIKE :tableName', ['tableName' => $table]); + + return !empty($exists); + } + + protected function dropTableIfExists(Connection $connection, string $table): void { $sql = \sprintf('DROP TABLE IF EXISTS `%s`', $table); $connection->executeStatement($sql); } - protected function dropColumn(Connection $connection, string $table, string $columnName): void + protected function dropColumnIfExists(Connection $connection, string $table, string $columnName): void { if (!$this->columnExists($connection, $table, $columnName)) { return; @@ -42,7 +56,7 @@ protected function dropColumn(Connection $connection, string $table, string $col $connection->executeStatement(\sprintf('ALTER TABLE `%s` DROP COLUMN `%s`', $table, $columnName)); } - protected function dropIndex(Connection $connection, string $table, string $indexName): void + protected function dropIndexIfExists(Connection $connection, string $table, string $indexName): void { if (!$this->indexExists($connection, $table, $indexName)) { return; @@ -92,7 +106,7 @@ protected function addForeignKey( $connection->executeStatement($sql); } - protected function dropForeignKey(Connection $connection, string $table, string $foreignKeyName): void + protected function dropForeignKeyIfExists(Connection $connection, string $table, string $foreignKeyName): void { if (!$this->foreignKeyExists($connection, $table, $foreignKeyName)) { return; diff --git a/tests/unit/Migration/Connection/Fingerprint/MigrationFingerprintServiceTest.php b/tests/unit/Migration/Connection/Fingerprint/MigrationFingerprintServiceTest.php new file mode 100644 index 000000000..ad77a2d9f --- /dev/null +++ b/tests/unit/Migration/Connection/Fingerprint/MigrationFingerprintServiceTest.php @@ -0,0 +1,254 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace unit\Migration\Connection\Fingerprint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter; +use Shopware\Core\Framework\DataAbstractionLayer\Search\IdSearchResult; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Uuid\Uuid; +use Shopware\Core\Test\Stub\DataAbstractionLayer\StaticEntityRepository; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\MigrationFingerprintService; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\Provider\Shopware5FingerprintProvider; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\Provider\Shopware6FingerprintProvider; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionCollection; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionDefinition; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; +use SwagMigrationAssistant\Migration\MigrationContextFactoryInterface; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Api\Reader\EnvironmentReader as ApiEnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Local\Reader\EnvironmentReader as LocalEnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware57\Shopware57Profile; +use SwagMigrationAssistant\Profile\Shopware6\Gateway\Api\Reader\EnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware6\Shopware6MajorProfile; + +#[Package('fundamentals@after-sales')] +#[CoversClass(MigrationFingerprintService::class)] +class MigrationFingerprintServiceTest extends TestCase +{ + private MockObject&Shopware5FingerprintProvider $shopware5Provider; + + private MockObject&Shopware6FingerprintProvider $shopware6Provider; + + protected function setUp(): void + { + $this->shopware5Provider = $this->getMockBuilder(Shopware5FingerprintProvider::class) + ->setConstructorArgs([ + $this->createMock(MigrationContextFactoryInterface::class), + $this->createMock(ApiEnvironmentReader::class), + $this->createMock(LocalEnvironmentReader::class), + ]) + ->onlyMethods(['provide']) + ->getMock(); + + $this->shopware6Provider = $this->getMockBuilder(Shopware6FingerprintProvider::class) + ->setConstructorArgs([ + $this->createMock(MigrationContextFactoryInterface::class), + $this->createMock(EnvironmentReader::class), + ]) + ->onlyMethods(['provide']) + ->getMock(); + } + + public function testGenerateNoFingerprintForEmptyCredentials(): void + { + $this->shopware5Provider->expects(static::never())->method('provide'); + $this->shopware6Provider->expects(static::never())->method('provide'); + + $service = $this->createService(); + + $fingerprint = $service->generate([], new SwagMigrationConnectionEntity()); + + static::assertNull($fingerprint); + } + + public function testGenerateNoFingerprintForUnsupportedProfile(): void + { + $this->shopware5Provider->expects(static::never())->method('provide'); + $this->shopware6Provider->expects(static::never())->method('provide'); + + $service = $this->createService(); + + $connection = new SwagMigrationConnectionEntity(); + $connection->setProfileName('unsupported-profile'); + + $fingerprint = $service->generate(['some' => 'data'], $connection); + + static::assertNull($fingerprint); + } + + public function testGenerateFingerprintSuccessfullyForShopware5(): void + { + $expected = Uuid::randomHex(); + + $this->shopware6Provider->expects(static::never())->method('provide'); + $this->shopware5Provider->expects(static::once()) + ->method('provide') + ->willReturn($expected); + + $service = $this->createService(); + + $connection = new SwagMigrationConnectionEntity(); + $connection->setProfileName(Shopware57Profile::PROFILE_NAME); + + $fingerprint = $service->generate(['some' => 'data'], $connection); + + static::assertSame($expected, $fingerprint); + } + + public function testGenerateFingerprintSuccessfullyForShopware6(): void + { + $expected = Uuid::randomHex(); + + $this->shopware5Provider->expects(static::never())->method('provide'); + $this->shopware6Provider->expects(static::once()) + ->method('provide') + ->willReturn($expected); + + $service = $this->createService(); + + $connection = new SwagMigrationConnectionEntity(); + $connection->setProfileName(Shopware6MajorProfile::PROFILE_NAME); + + $fingerprint = $service->generate(['some' => 'data'], $connection); + + static::assertSame($expected, $fingerprint); + } + + public function testReturnNullWhenProviderThrowsException(): void + { + $this->shopware5Provider->expects(static::never())->method('provide'); + $this->shopware6Provider->expects(static::once()) + ->method('provide') + ->willThrowException(new \RuntimeException('Error generating fingerprint')); + + $service = $this->createService(); + + $connection = new SwagMigrationConnectionEntity(); + $connection->setProfileName(Shopware6MajorProfile::PROFILE_NAME); + + $fingerprint = $service->generate(['some' => 'data'], $connection); + + static::assertNull($fingerprint); + } + + public function testCheckReturnsFalseForEmptyFingerprint(): void + { + $service = $this->createService(); + + $result = $service->check( + null, + Context::createDefaultContext(), + null, + ); + + static::assertFalse($result); + } + + public function testCheckReturnsFalseWhenNoMatchingFingerprintFound(): void + { + $idSearchResult = new IdSearchResult( + 0, + [], + new Criteria(), + Context::createDefaultContext(), + ); + + $service = $this->createService([$idSearchResult]); + + $result = $service->check( + 'non-existing-fingerprint', + Context::createDefaultContext(), + null, + ); + + static::assertFalse($result); + } + + public function testCheckReturnsTrueWhenMatchingFingerprintFound(): void + { + $idSearchResult = new IdSearchResult( + 1, + [], + new Criteria(), + Context::createDefaultContext(), + ); + + $service = $this->createService([$idSearchResult]); + + $result = $service->check( + 'existing-fingerprint', + Context::createDefaultContext(), + null, + ); + + static::assertTrue($result); + } + + public function testCheckExcludesConnectionIdWhenProvided(): void + { + $connectionId = 'excluded-connection-id'; + + /** @var MockObject&EntityRepository $connectionRepo */ + $connectionRepo = $this->createMock(EntityRepository::class); + $connectionRepo->expects(static::once()) + ->method('searchIds') + ->with(static::callback(function (Criteria $criteria) use ($connectionId) { + static::assertCount(2, $criteria->getFilters()); + + $query = $criteria->getFilters()[1]; + static::assertInstanceOf(NotFilter::class, $query); + + $filter = $query->getQueries()[0]; + static::assertInstanceOf(EqualsFilter::class, $filter); + + return $filter->getValue() === $connectionId; + })); + + $service = new MigrationFingerprintService( + [ + $this->shopware5Provider, + $this->shopware6Provider, + ], + $connectionRepo, + ); + + $result = $service->check( + 'existing-fingerprint', + Context::createDefaultContext(), + $connectionId, + ); + + static::assertFalse($result); + } + + /** + * @param IdSearchResult[] $idSearchResults + */ + private function createService(array $idSearchResults = []): MigrationFingerprintService + { + /** @var StaticEntityRepository $connectionRepo */ + $connectionRepo = new StaticEntityRepository( + $idSearchResults, + new SwagMigrationConnectionDefinition() + ); + + return new MigrationFingerprintService( + [ + $this->shopware5Provider, + $this->shopware6Provider, + ], + $connectionRepo, + ); + } +} diff --git a/tests/unit/Migration/Connection/Fingerprint/Provider/Shopware5FingerprintProviderTest.php b/tests/unit/Migration/Connection/Fingerprint/Provider/Shopware5FingerprintProviderTest.php new file mode 100644 index 000000000..d7fceeccd --- /dev/null +++ b/tests/unit/Migration/Connection/Fingerprint/Provider/Shopware5FingerprintProviderTest.php @@ -0,0 +1,149 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace unit\Migration\Connection\Fingerprint\Provider; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Util\Hasher; +use Shopware\Core\Framework\Uuid\Uuid; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\Provider\Shopware5FingerprintProvider; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; +use SwagMigrationAssistant\Migration\MigrationContextFactoryInterface; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Api\Reader\EnvironmentReader as ApiEnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Api\ShopwareApiGateway; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Local\Reader\EnvironmentReader as LocalEnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware\Gateway\Local\ShopwareLocalGateway; +use SwagMigrationAssistant\Profile\Shopware54\Shopware54Profile; +use SwagMigrationAssistant\Profile\Shopware55\Shopware55Profile; +use SwagMigrationAssistant\Profile\Shopware56\Shopware56Profile; +use SwagMigrationAssistant\Profile\Shopware57\Shopware57Profile; + +/** + * @internal + */ +#[Package('fundamentals@after-sales')] +#[CoversClass(Shopware5FingerprintProvider::class)] +class Shopware5FingerprintProviderTest extends TestCase +{ + public function testSupportsShopware5Profiles(): void + { + static::assertTrue(Shopware5FingerprintProvider::supports(Shopware54Profile::PROFILE_NAME)); + static::assertTrue(Shopware5FingerprintProvider::supports(Shopware55Profile::PROFILE_NAME)); + static::assertTrue(Shopware5FingerprintProvider::supports(Shopware56Profile::PROFILE_NAME)); + static::assertTrue(Shopware5FingerprintProvider::supports(Shopware57Profile::PROFILE_NAME)); + } + + public function testGeneratesNoFingerprintForEmptyCredentials(): void + { + $contextFactory = $this->createMock(MigrationContextFactoryInterface::class); + $contextFactory->expects(static::never())->method('createByConnection'); + + $provider = $this->createFingerprintProvider($contextFactory); + + $fingerprint = $provider->provide([], new SwagMigrationConnectionEntity()); + + static::assertNull($fingerprint); + } + + public function testGeneratesNoFingerprintWithUnknownGateway(): void + { + $contextFactory = $this->createMock(MigrationContextFactoryInterface::class); + $contextFactory->expects(static::once())->method('createByConnection'); + + $provider = $this->createFingerprintProvider($contextFactory); + + $connection = new SwagMigrationConnectionEntity(); + $connection->setGatewayName('unknown_gateway'); + + $fingerprint = $provider->provide(['some' => 'credentials'], $connection); + + static::assertNull($fingerprint); + } + + public function testGeneratesFingerprintForLocalGateway(): void + { + $esdKey = Uuid::randomHex(); + $installationDate = (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM); + + $localEnvironmentReader = $this->createMock(LocalEnvironmentReader::class); + $localEnvironmentReader->expects(static::once())->method('read') + ->willReturn([ + 'environmentInformation' => ['config' => [ + 'esdKey' => $esdKey, + 'installationDate' => $installationDate, + ]]]); + + $provider = $this->createFingerprintProvider( + localEnvironmentReader: $localEnvironmentReader + ); + + $connection = new SwagMigrationConnectionEntity(); + $connection->setGatewayName(ShopwareLocalGateway::GATEWAY_NAME); + + $fingerprint = $provider->provide(['some' => 'credentials'], $connection); + + static::assertSame(Hasher::hash($esdKey . $installationDate), $fingerprint); + } + + public function testGeneratesFingerprintForApiGateway(): void + { + $esdKey = Uuid::randomHex(); + $installationDate = (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM); + + $localEnvironmentReader = $this->createMock(ApiEnvironmentReader::class); + $localEnvironmentReader->expects(static::once())->method('read') + ->willReturn([ + 'config' => [ + 'esdKey' => $esdKey, + 'installationDate' => $installationDate, + ]]); + + $provider = $this->createFingerprintProvider( + apiEnvironmentReader: $localEnvironmentReader + ); + + $connection = new SwagMigrationConnectionEntity(); + $connection->setGatewayName(ShopwareApiGateway::GATEWAY_NAME); + + $fingerprint = $provider->provide(['some' => 'credentials'], $connection); + + static::assertSame(Hasher::hash($esdKey . $installationDate), $fingerprint); + } + + public function testGenerateNoFingerprintWhenDataIsMissing(): void + { + $localEnvironmentReader = $this->createMock(ApiEnvironmentReader::class); + $localEnvironmentReader->expects(static::once())->method('read') + ->willReturn([]); + + $provider = $this->createFingerprintProvider( + apiEnvironmentReader: $localEnvironmentReader + ); + + $connection = new SwagMigrationConnectionEntity(); + $connection->setGatewayName(ShopwareApiGateway::GATEWAY_NAME); + + $fingerprint = $provider->provide(['some' => 'credentials'], $connection); + + static::assertNull($fingerprint); + } + + private function createFingerprintProvider( + (MockObject&MigrationContextFactoryInterface)|null $migrationContextFactory = null, + (MockObject&ApiEnvironmentReader)|null $apiEnvironmentReader = null, + (MockObject&LocalEnvironmentReader)|null $localEnvironmentReader = null, + ): Shopware5FingerprintProvider { + return new Shopware5FingerprintProvider( + $migrationContextFactory ?? $this->createMock(MigrationContextFactoryInterface::class), + $apiEnvironmentReader ?? $this->createMock(ApiEnvironmentReader::class), + $localEnvironmentReader ?? $this->createMock(LocalEnvironmentReader::class), + ); + } +} diff --git a/tests/unit/Migration/Connection/Fingerprint/Provider/Shopware6FingerprintProviderTest.php b/tests/unit/Migration/Connection/Fingerprint/Provider/Shopware6FingerprintProviderTest.php new file mode 100644 index 000000000..4af0b6857 --- /dev/null +++ b/tests/unit/Migration/Connection/Fingerprint/Provider/Shopware6FingerprintProviderTest.php @@ -0,0 +1,91 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace unit\Migration\Connection\Fingerprint\Provider; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Uuid\Uuid; +use SwagMigrationAssistant\Migration\Connection\Fingerprint\Provider\Shopware6FingerprintProvider; +use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; +use SwagMigrationAssistant\Migration\MigrationContextFactoryInterface; +use SwagMigrationAssistant\Profile\Shopware6\Gateway\Api\Reader\EnvironmentReader; +use SwagMigrationAssistant\Profile\Shopware6\Shopware6MajorProfile; + +/** + * @internal + */ +#[Package('fundamentals@after-sales')] +#[CoversClass(Shopware6FingerprintProvider::class)] +class Shopware6FingerprintProviderTest extends TestCase +{ + public function testSupportsShopwareProfile(): void + { + static::assertTrue(Shopware6FingerprintProvider::supports(Shopware6MajorProfile::PROFILE_NAME)); + } + + public function testGenerateNoFingerprintWithEmptyCredentials(): void + { + $provider = $this->createFingerprintProvider(); + + $fingerprint = $provider->provide(null, new SwagMigrationConnectionEntity()); + + static::assertNull($fingerprint); + } + + public function testGenerateFingerprint(): void + { + $shopId = Uuid::randomHex(); + + $environmentReader = $this->createMock(EnvironmentReader::class); + $environmentReader->expects(static::once()) + ->method('read') + ->willReturn([ + 'environmentInformation' => [ + 'shopIdV2' => $shopId, + ], + ]); + + $provider = $this->createFingerprintProvider(environmentReader: $environmentReader); + + $fingerprint = $provider->provide( + ['some' => 'credentials'], + new SwagMigrationConnectionEntity() + ); + + static::assertSame($shopId, $fingerprint); + } + + public function testGeneratesNoFingerprintWhenShopIdMissing(): void + { + $environmentReader = $this->createMock(EnvironmentReader::class); + $environmentReader->expects(static::once()) + ->method('read') + ->willReturn([ + 'environmentInformation' => [], + ]); + + $provider = $this->createFingerprintProvider(environmentReader: $environmentReader); + + $fingerprint = $provider->provide( + ['some' => 'credentials'], + new SwagMigrationConnectionEntity() + ); + + static::assertNull($fingerprint); + } + + private function createFingerprintProvider((MockObject&EnvironmentReader)|null $environmentReader = null): Shopware6FingerprintProvider + { + return new Shopware6FingerprintProvider( + $this->createMock(MigrationContextFactoryInterface::class), + $environmentReader ?? $this->createMock(EnvironmentReader::class), + ); + } +}