clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
{
"status": "ok",
"data": [
{
"id": 1,
"title": "1 item",
"items": [
{
"id": 1,
"title": "1.1 item"
},
{
"id": 2,
"title": "1.2 item"
}
]
},
{
"id": 2,
"title": "2 item",
"items": [
{
"id": 3,
"title": "2.1 item"
}
]
}
],
"data_empty": [],
"data_false": false
}

View File

@@ -0,0 +1,21 @@
[
{
"id": "1",
"title": "Title",
"video": {
"title": "Video title",
"url": "https://localhost/"
}
},
{
"id": "2",
"video": {
"title": "Video title",
"url": "https://localhost/"
}
},
{
"id": "3",
"title": "Title 3"
}
]

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<items>
<item id="1">
<values title="Values">
<value>Value 1</value>
<value>Value 2</value>
</values>
<!-- item -->
<item id="2">
<values title="Values">
<value>Value 1 (single)</value>
</values>
</item>
</items>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- unmatched tags -->
<ietems> <!-- wrong tag -->
<item id="1">
<values title="Values">
<value>Value 1</value>
<value>Value 2</value>
</values>
</item>
<item id="2">
<values title="Values">
<value>Value 1 (single)</value>
</values>
</item>
</items>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<items>
<item id="1">
<values title="Values">
<value>Value 1</value>
<value>Value 2</value>
</values>
</item>
<item id="2">
<values title="Values">
<value>Value 1 (single)</value>
</values>
</item>
</items>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<items>
<item id="1">
<values title="Values">
<value>Value 1</value>
<value>Value 2</value>
</values>
</item>
<item id="2">
<values title="Values">
<value>Value 1 (single)</value>
</values>
</item>
</items>

View File

@@ -0,0 +1,17 @@
<? xml version="1.0" encoding="utf-8"?>
<items>
<item id="1">
<values title="Values">
<value>Value 1</value>
<value>Value 2</value>
</values>
</item>
<item id="2">
<values title="Values">
<value>Value 1 (single)</value>
</values>
</item>
</items>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<items>
<item id="1">
<values title="Values">
<value>Value 1</value>
<value>Value 2</value>
</values>
</item>
<item id="2">
<values title="Values">
<value>Value 1 (single)</value>
</values>
</item>
</items>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<items>
<item id="3">
<values title="Values">
<value>Value 1</value>
<value>Value 2</value>
</values>
</item>
</items>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<persons>
<person>
<id>1</id>
<name>Elizabeth</name>
<children>
<child age="8">
<name>Elizabeth Junior</name>
<name>Jane</name>
</child>
</children>
</person>
<person>
<id>2</id>
<name>George</name>
<children>
<child>
<name>George Junior</name>
<age>10</age>
</child>
</children>
</person>
<person>
<id>3</id>
<name>Peter</name>
<children>
<child>
<name>James</name>
</child>
<child>
<name age="6">Lucy</name>
</child>
</children>
</person>
</persons>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<items>
<item>
<Id>item_1</Id>
<Name>Item 1</Name>
<Values1>
<SubItem>
<Id>1</Id>
<Name>Name 1</Name>
</SubItem>
<SubItem>
<Id>2</Id>
<Name>Name 2</Name>
</SubItem>
</Values1>
<Values2>
<SubItem>3</SubItem>
<SubItem>4</SubItem>
</Values2>
</item>
</items>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<persons>
<person>
<id>1</id>
<name>Elizabeth</name>
<children>
<child age="8">
<name>Elizabeth Junior</name>
<name>Jane</name>
</child>
</children>
</person>
<person>
<id>2</id>
<name>George</name>
<children>
<child>
<name>George Junior</name>
<age>10</age>
</child>
</children>
</person>
<person>
<id>3</id>
<name>Peter</name>
<children>
<child>
<name age="6">Lucy</name>
</child>
</children>
</person>
</persons>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<items>
<item id="1">
<values title="Values">
<value>Value 1</value>
<value>Value 2</value>
</values>
</item>
<item id="2">
<values title="Values">
<value>Value 1 (single)</value>
</values>
</item>
</items>

View File

@@ -0,0 +1,13 @@
type: module
name: Migrate Plus Http Test
description: 'Test module to test Migrate Plus Http data_source.'
package: Testing
core_version_requirement: '>=9.1'
dependencies:
- drupal:migrate
- migrate_plus:migrate_plus
# Information added by Drupal.org packaging script on 2024-11-20
version: '6.0.5'
project: 'migrate_plus'
datestamp: 1732124626

View File

@@ -0,0 +1,40 @@
migrate_plus_http_test.json_first:
path: '/migrate-plus-http-test/json-first'
defaults:
_controller: '\Drupal\migrate_plus_http_test\Controller\HttpResponses::content'
_title: 'JSON First'
requirements:
_permission: 'access content'
migrate_plus_http_test.json_second:
path: '/migrate-plus-http-test/json-second'
defaults:
_controller: '\Drupal\migrate_plus_http_test\Controller\HttpResponses::second'
_title: 'JSON Second'
requirements:
_permission: 'access content'
migrate_plus_http_test.json_third:
path: '/migrate-plus-http-test/json-third'
defaults:
_controller: '\Drupal\migrate_plus_http_test\Controller\HttpResponses::third'
_title: 'JSON Third'
requirements:
_permission: 'access content'
migrate_plus_http_test.json_fourth:
path: '/migrate-plus-http-test/json-fourth'
defaults:
_controller: '\Drupal\migrate_plus_http_test\Controller\HttpResponses::fourth'
_title: 'JSON Fourth'
requirements:
_permission: 'access content'
migrate_plus_http_test.json_fifth:
path: '/migrate-plus-http-test/json-fifth'
defaults:
_controller: '\Drupal\migrate_plus_http_test\Controller\HttpResponses::fifth'
_title: 'JSON Fifth'
requirements:
_permission: 'access content'

View File

@@ -0,0 +1,112 @@
<?php
namespace Drupal\migrate_plus_http_test\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* An example controller.
*/
class HttpResponses extends ControllerBase {
/**
* Returns a render-able array for a test page.
*/
public function content() {
$url = Url::fromRoute('migrate_plus_http_test.json_second', [], ['absolute' => TRUE]);
$data = [
"status" => "ok",
'nextUrl' => $url->toString(),
"data" => $this->generateRowsData(3),
];
return new JsonResponse($data);
}
public function second(){
$data = [
"status" => "ok",
'nextUrl' => NULL,
"data" => $this->generateRowsData(3,4),
];
return new JsonResponse($data);
}
public function third(Request $request){
$page = $request->query->get('page') ?? 0;
$first = $page*3 +1;
$data = [
"status" => "ok",
'currentPage' => $page,
'numPages' => 2,
'nextPage' => $page+1,
"data" => $this->generateRowsData(3,$first),
];
if($page == 2){
unset($data['nextPage']);
}
return new JsonResponse($data);
}
public function fourth( Request $request ) {
$page = $request->query->get('cursor') ?? 0;
$first = $page*3 +1;
$data = [
"status" => "ok",
'nextPage' => $page+1,
"data" => $this->generateRowsData(3,$first),
];
if($page == 2){
unset($data['nextPage']);
}
return new JsonResponse($data);
}
public function fifth( Request $request ) {
$page = $request->query->get('page') ?? 0;
$first = $page*3 +1;
$data = [
"status" => "ok",
'numItems' => 3,
"data" => $this->generateRowsData(3,$first),
];
if($page == 2){
$data['numItems'] = 0;
$data["data"] = [];
}
if($page == 3){
throw new NotFoundHttpException();
}
return new JsonResponse($data);
}
protected function generateRowsData(int $length, int $first = 1){
$data = [];
$last = $first + $length;
for($i = $first; $i < $last; $i++){
$data[] = [
"id" => $i,
];
}
return $data;
}
}

View File

@@ -0,0 +1,21 @@
langcode: en
status: true
dependencies: { }
id: dummy
label: Dummy migration
migration_tags: { }
source:
plugin: embedded_data
data_rows:
-
name: Dummy
ids:
name:
type: string
process:
name: name
destination:
plugin: null
migration_dependencies:
required: { }
optional: { }

View File

@@ -0,0 +1,32 @@
langcode: en
status: true
dependencies: { }
id: fruit_terms
label: Fruit Terms
class: null
field_plugin_method: null
cck_plugin_method: null
migration_tags: { }
migration_group: default
source:
plugin: embedded_data
data_rows:
-
name: Apple
-
name: Banana
-
name: Orange
ids:
name:
type: string
constants:
vocabulary: fruit
process:
name: name
vid: constants/vocabulary
destination:
plugin: entity:taxonomy_term
migration_dependencies:
required: { }
optional: { }

View File

@@ -0,0 +1,9 @@
langcode: en
status: true
dependencies: { }
id: default
label: Default
description: ''
source_type: ''
module: null
shared_configuration: null

View File

@@ -0,0 +1,13 @@
type: module
name: Migrate Plus Test
description: 'Test module to test Migrate Plus.'
package: Testing
core_version_requirement: '>=9.1'
dependencies:
- drupal:migrate
- migrate_plus:migrate_plus
# Information added by Drupal.org packaging script on 2024-11-20
version: '6.0.5'
project: 'migrate_plus'
datestamp: 1732124626

View File

@@ -0,0 +1,388 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
use Drupal\migrate_plus\DataParserPluginManager;
/**
* Tests the http data_fetcher from the url plugin.
*
* @group migrate_plus
*/
final class HttpTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'migrate_plus',
'migrate_plus_http_test',
];
/**
* A user with permission to administer site configuration.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The plugin manager.
*/
protected ?DataParserPluginManager $pluginManager = NULL;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->pluginManager = $this->container->get('plugin.manager.migrate_plus.data_parser');
$this->user = $this->drupalCreateUser(['administer site configuration', 'access content']);
$this->drupalLogin($this->user);
}
public function testUrlPagerMissingSelector(): void {
$url = Url::fromRoute('migrate_plus_http_test.json_first', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'urls'
];
$result = $this->getPluginResults($conf);
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
];
$this->assertEquals($expected, $result);
}
public function testUrlsPagerMissingSelectorAttribute(){
$url = Url::fromRoute('migrate_plus_http_test.json_first', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'urls',
'selector' => 'thisIsMissing'
];
$result = $this->getPluginResults($conf);
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
];
$this->assertEquals($expected, $result);
}
public function testUrlsPager(): void {
$url = Url::fromRoute('migrate_plus_http_test.json_first', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'urls',
'selector' => 'nextUrl'
];
$result = $this->getPluginResults($conf);
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
[ "Id" => 4 ],
[ "Id" => 5 ],
[ "Id" => 6 ],
];
$this->assertEquals($expected, $result);
}
public function testCursorPager(){
$url = Url::fromRoute('migrate_plus_http_test.json_third', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'cursor',
'selector' => 'nextPage',
'key' => 'page'
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
[ "Id" => 4 ],
[ "Id" => 5 ],
[ "Id" => 6 ],
[ "Id" => 7 ],
[ "Id" => 8 ],
[ "Id" => 9 ],
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
public function testCursorPagerMissingKey(){
$url = Url::fromRoute('migrate_plus_http_test.json_fourth', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'cursor',
'selector' => 'nextPage'
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
[ "Id" => 4 ],
[ "Id" => 5 ],
[ "Id" => 6 ],
[ "Id" => 7 ],
[ "Id" => 8 ],
[ "Id" => 9 ],
];
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'cursor',
'selector' => 'nextPage'
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
public function testCursorPagerMissingSelector(){
$url = Url::fromRoute('migrate_plus_http_test.json_third', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'cursor',
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
public function testCursorPagerMissingSelectorAttribute(){
$url = Url::fromRoute('migrate_plus_http_test.json_third', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'cursor',
'selector' => 'missingAttribute'
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
public function testPagePager(){
$url = Url::fromRoute('migrate_plus_http_test.json_third', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'page',
'selector' => 'currentPage',
'key' => 'page'
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
public function testPagePagerSelectorMax(){
$url = Url::fromRoute('migrate_plus_http_test.json_third', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'page',
'selector' => 'currentPage',
'selector_max' => 'numPages',
'key' => 'page'
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
[ "Id" => 4 ],
[ "Id" => 5 ],
[ "Id" => 6 ],
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
public function testPaginationPagerNumItemsSelector(){
$url = Url::fromRoute('migrate_plus_http_test.json_fifth', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'paginator',
'selector' => 'numItems',
'default_num_items' => 3,
'page_key' => 'page'
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
[ "Id" => 4 ],
[ "Id" => 5 ],
[ "Id" => 6 ],
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
public function testPaginationPagerRowArraySelector(){
$url = Url::fromRoute('migrate_plus_http_test.json_fifth', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'paginator',
'selector' => 'data',
'default_num_items' => 3,
'page_key' => 'page'
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
[ "Id" => 4 ],
[ "Id" => 5 ],
[ "Id" => 6 ],
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
public function testPaginationPagerNoSelector(){
$url = Url::fromRoute('migrate_plus_http_test.json_fifth', [], ['absolute' => TRUE]);
$conf = $this->getBaseConfiguration();
$conf['urls'][] = $url->toString();
$conf['pager'] = [
'type' => 'paginator',
'default_num_items' => 3,
'page_key' => 'page'
];
$expected = [
[ "Id" => 1 ],
[ "Id" => 2 ],
[ "Id" => 3 ],
[ "Id" => 4 ],
[ "Id" => 5 ],
[ "Id" => 6 ],
];
$result = $this->getPluginResults($conf);
$this->assertEquals( $expected, $result);
}
protected function getBaseConfiguration( ): array{
return [
'plugin' => 'url',
'data_fetcher_plugin' => 'http',
'data_parser_plugin' => 'json',
'pager' => [],
'urls' => [],
'ids' => [
'Id' => [
'type' => 'integer'
]
],
'fields' => [
[
'name' => 'Id',
'label' => 'ID',
'selector' => '/id'
],
],
'item_selector' => 'data',
];
}
protected function getPluginResults( array $configuration ): array{
$json_parser = $this->pluginManager->createInstance('json', $configuration);
$data = [];
foreach ($json_parser as $item) {
$data[] = $item;
}
return $data;
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Simple test to ensure that main page loads with module enabled.
*
* @group migrate_plus
*/
final class LoadTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate_plus',
'migrate_example',
'migrate_example_setup',
'migrate_example_advanced',
'migrate_example_advanced_setup',
'migrate_json_example',
];
/**
* A user with permission to administer site configuration.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->user = $this->drupalCreateUser(['administer site configuration']);
$this->drupalLogin($this->user);
}
/**
* Tests that the home page loads with a 200 response.
*/
public function testLoad(): void {
$this->drupalGet(Url::fromRoute('<front>'));
$this->assertSession()->statusCodeEquals(200);
}
}

View File

@@ -0,0 +1,120 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\Row;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\user\Entity\User;
/**
* Tests entity lookup access check.
*
* @group migrate_plus
*/
final class EntityLookupAccessTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'migrate',
'migrate_plus',
'system',
'user',
];
/**
* A user.
*
* @var bool|\Drupal\user\Entity\User
*/
protected $user;
/**
* A test entity.
*/
protected ?EntityTest $entity;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$this->installConfig('user');
$this->user = $this->createUser(['view all entity_test_query_access entities']);
$this->entity = EntityTest::create(['name' => $this->randomMachineName(8)]);
}
/**
* Tests that access is honored for entity lookups.
*/
public function testEntityLookupAccessCheck(): void {
$definition = [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['id' => 1],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [],
'destination' => [
'plugin' => 'entity:entity_test',
],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration);
$row = new Row();
$configuration_base = [
'entity_type' => 'entity_test',
'value_key' => 'id',
];
// Set access_check true.
$configuration = $configuration_base + ['access_check' => TRUE];
// Test as anonymous.
$anonymous = User::getAnonymousUser();
$this->setCurrentUser($anonymous);
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
// Check the entity is not found.
$value = $plugin->transform($this->entity->id(), $executable, $row, 'id');
$this->assertNull($value);
// Test as authenticated user.
$this->setCurrentUser($this->user);
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
// Check the entity is found.
$value = $plugin->transform($this->entity->id(), $executable, $row, 'id');
$this->assertSame($this->entity->id(), $value);
// Retest with access check false.
$configuration = $configuration_base + ['access_check' => FALSE];
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
// Check the entity is found.
$value = $plugin->transform($this->entity->id(), $executable, $row, 'id');
$this->assertSame($this->entity->id(), $value);
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
/**
* Verifies all tests pass with batching enabled, uneven batches.
*
* @group migrate
*/
final class MigrateTableBatchTest extends MigrateTableTest {
/**
* The batch size to configure (a size of 1 disables batching).
*
* @var int
*/
protected $batchSize = 2;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
/**
* Verifies all tests pass with batching enabled, even batches.
*
* @group migrate
*/
final class MigrateTableEvenBatchTest extends MigrateTableTest {
/**
* The batch size to configure (a size of 1 disables batching).
*
* @var int
*/
protected $batchSize = 3;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
/**
* Verifies all tests pass with batching enabled, uneven batches.
*
* @group migrate
*/
final class MigrateTableIncrementBatchTest extends MigrateTableIncrementTest {
/**
* The batch size to configure.
*
* @var int
*/
protected static int $batchSize = 2;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
/**
* Verifies all tests pass with batching enabled, even batches.
*
* @group migrate
*/
final class MigrateTableIncrementEvenBatchTest extends MigrateTableIncrementTest {
/**
* The batch size to configure.
*
* @var int
*/
protected static int $batchSize = 3;
}

View File

@@ -0,0 +1,189 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
use Drupal\Core\Database\Connection;
use Drupal\migrate\MigrateExecutable;
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
/**
* Tests migration destination table with auto-increment keys.
*
* @group migrate
*/
class MigrateTableIncrementTest extends MigrateTestBase {
public const TABLE_NAME = 'migrate_test_destination_table';
/**
* The database connection.
*/
protected ?Connection $connection = NULL;
/**
* {@inheritdoc}
*/
protected static $modules = ['migrate_plus'];
/**
* The batch size to configure.
*
* @var int
*/
protected static int $batchSize = 1;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->connection = $this->container->get('database');
$this->connection->schema()->createTable(static::TABLE_NAME, [
'description' => 'Test table',
'fields' => [
'id' => [
'type' => 'serial',
'not null' => TRUE,
],
'data1' => [
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
],
'data2' => [
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
],
],
'primary key' => ['id'],
]);
}
/**
* {@inheritdoc}
*/
protected function tearDown(): void {
$this->connection->schema()->dropTable(static::TABLE_NAME);
parent::tearDown();
}
/**
* Create a minimally valid migration with some source data.
*
* @return array
* The migration definition.
*/
public static function tableDestinationMigration(): array {
return [
'dummy table' => [
[
'id' => 'migration_table_test',
'migration_tags' => ['Testing'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'data1' => 'dummy1 value1',
'data2' => 'dummy2 value1',
],
[
'data1' => 'dummy1 value2',
'data2' => 'dummy2 value2',
],
[
'data1' => 'dummy1 value3',
'data2' => 'dummy2 value3',
],
],
'ids' => [
'data1' => ['type' => 'string'],
],
],
'destination' => [
'plugin' => 'table',
'table_name' => static::TABLE_NAME,
'batch_size' => static::$batchSize,
'id_fields' => [
'id' => [
'type' => 'integer',
'use_auto_increment' => TRUE,
],
],
],
'process' => [
'data1' => 'data1',
'data2' => 'data2',
],
],
],
];
}
/**
* Tests table destination.
*
* @param array $definition
* The migration definition.
*
* @dataProvider tableDestinationMigration
*
* @throws \Drupal\migrate\MigrateException
*/
public function testTableDestination(array $definition) {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, $this);
$executable->import();
$values = $this->connection->select(static::TABLE_NAME)
->fields(static::TABLE_NAME)
->execute()
->fetchAllAssoc('data1');
$this->assertEquals(1, $values['dummy1 value1']->id);
$this->assertEquals(2, $values['dummy1 value2']->id);
$this->assertEquals(3, $values['dummy1 value3']->id);
$this->assertEquals('dummy2 value3', $values['dummy1 value3']->data2);
$this->assertCount(3, $values);
}
/**
* Tests table destination with data already in the table.
*
* @param array $definition
* The migration definition.
*
* @dataProvider tableDestinationMigration
*
* @throws \Drupal\migrate\MigrateException
*/
public function testTableDestinationWithExistingData(array $definition) {
$this->connection->insert(static::TABLE_NAME)
->fields([
'id' => 5,
'data1' => 'Dummy initial value',
'data2' => 'Dummy initial value2',
])
->execute();
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, $this);
$executable->import();
$values = $this->connection->select(static::TABLE_NAME)
->fields(static::TABLE_NAME)
->execute()
->fetchAllAssoc('data1');
$this->assertEquals(5, $values['Dummy initial value']->id);
$this->assertEquals(6, $values['dummy1 value1']->id);
$this->assertEquals(7, $values['dummy1 value2']->id);
$this->assertEquals(8, $values['dummy1 value3']->id);
$this->assertEquals('dummy2 value3', $values['dummy1 value3']->data2);
$this->assertCount(4, $values);
}
}

View File

@@ -0,0 +1,237 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
use Drupal\Core\Database\Connection;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Row;
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
/**
* Tests migration destination table.
*
* @group migrate
*/
class MigrateTableTest extends MigrateTestBase {
public const SOURCE_TABLE_NAME = 'migrate_test_source_table';
public const DEST_TABLE_NAME = 'migrate_test_destination_table';
/**
* The database connection.
*/
protected ?Connection $connection = NULL;
/**
* The batch size to configure (a size of 1 disables batching).
*
* @var int
*/
protected $batchSize = 1;
/**
* {@inheritdoc}
*/
protected static $modules = ['migrate_plus'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->connection = $this->container->get('database');
$connections = [
static::SOURCE_TABLE_NAME => $this->sourceDatabase,
static::DEST_TABLE_NAME => $this->connection,
];
foreach ($connections as $table => $connection) {
$connection->schema()->createTable($table, [
'description' => 'Test table',
'fields' => [
'data' => [
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
],
'data2' => [
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
],
'data3' => [
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
],
],
'primary key' => ['data'],
]);
}
$query = $this->sourceDatabase->insert(static::SOURCE_TABLE_NAME)
->fields(['data', 'data2', 'data3']);
$values = [
[
'data' => 'dummy value',
'data2' => 'dummy2 value',
'data3' => 'dummy3 value',
],
[
'data' => 'dummy value2',
'data2' => 'dummy2 value2',
'data3' => 'dummy3 value2',
],
[
'data' => 'dummy value3',
'data2' => 'dummy2 value3',
'data3' => 'dummy3 value3',
],
];
foreach ($values as $record) {
$query->values($record);
}
$query->execute();
}
/**
* {@inheritdoc}
*/
protected function tearDown(): void {
$this->sourceDatabase->schema()->dropTable(static::SOURCE_TABLE_NAME);
$this->connection->schema()->dropTable(static::DEST_TABLE_NAME);
parent::tearDown();
}
/**
* Create a minimally valid migration with some source data.
*
* @return array
* The migration definition.
*/
public static function tableDestinationMigration(): array {
return [
'dummy table' => [
[
'id' => 'migration_table_test',
'migration_tags' => ['Testing'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'data' => 'dummy1 value1',
'data2' => 'dummy2 value1',
],
[
'data' => 'dummy1 value2',
'data2' => 'dummy2 value2',
],
[
'data' => 'dummy1 value3',
'data2' => 'dummy2 value3',
],
],
'ids' => [
'data' => ['type' => 'string'],
],
],
'destination' => [
'plugin' => 'table',
'table_name' => static::DEST_TABLE_NAME,
'id_fields' => [
'data' => [
'type' => 'string',
],
],
],
'process' => [
'data' => 'data',
'data2' => 'data2',
],
],
],
];
}
/**
* Tests table migration.
*/
public function testTableMigration(): void {
$definition = [
'id' => 'migration_table_test',
'migration_tags' => ['Testing'],
'source' => [
'plugin' => 'table',
'table_name' => static::SOURCE_TABLE_NAME,
'id_fields' => [
'data' => ['type' => 'string'],
],
'ignore_map' => TRUE,
],
'destination' => [
'plugin' => 'table',
'table_name' => static::DEST_TABLE_NAME,
'id_fields' => [
'data' => ['type' => 'string'],
],
'batch_size' => $this->batchSize,
],
'process' => [
'data' => 'data',
'data2' => 'data2',
'data3' => 'data3',
],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, $this);
$executable->import();
$values = $this->connection->select(static::DEST_TABLE_NAME)
->fields(static::DEST_TABLE_NAME)
->execute()
->fetchAllAssoc('data');
$this->assertEquals('dummy value', $values['dummy value']->data);
$this->assertEquals('dummy2 value', $values['dummy value']->data2);
$this->assertEquals('dummy2 value2', $values['dummy value2']->data2);
$this->assertEquals('dummy3 value3', $values['dummy value3']->data3);
$this->assertEquals(3, count($values));
// Now rollback.
$executable->rollback();
$values = $this->connection->select(static::DEST_TABLE_NAME)
->fields(static::DEST_TABLE_NAME)
->execute()
->fetchAllAssoc('data');
$this->assertEquals(0, count($values));
}
/**
* Tests table update.
*
* @dataProvider tableDestinationMigration
*/
public function testTableUpdate(array $definition): void {
// Make sure migration overwrites the original data for the first row.
$original_values = [
'data' => 'dummy value',
'data2' => 'original value 2',
'data3' => 'original value 3',
];
$this->connection->insert(static::DEST_TABLE_NAME)
->fields($original_values)
->execute();
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = \Drupal::service('plugin.manager.migration')
->createStubMigration($definition);
$migration->getIdMap()->saveIdMapping(new Row($original_values,
['data' => 'dummy value']), ['data' => 'dummy value'], MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
$this->testTableMigration();
}
}

View File

@@ -0,0 +1,124 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate_plus\Entity\Migration;
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
/**
* Test migration config entity discovery.
*
* @group migrate_plus
*/
final class MigrationConfigEntityTest extends MigrateTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate',
'migrate_plus',
'migrate_plus_test',
'taxonomy',
'text',
'system',
'user',
];
/**
* The plugin manager.
*
* @var \Drupal\migrate\Plugin\MigrationPluginManager
*/
protected $pluginManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->pluginManager = \Drupal::service('plugin.manager.migration');
$this->installConfig('migrate_plus');
$this->installEntitySchema('taxonomy_term');
}
/**
* Tests cache invalidation.
*/
public function testCacheInvalidation(): void {
$config = Migration::create([
'id' => 'test',
'status' => TRUE,
'label' => 'Label A',
'migration_tags' => [],
'source' => [],
'destination' => [],
'migration_dependencies' => [],
]);
$config->save();
$this->assertNotEmpty($this->pluginManager->getDefinition('test'));
$this->assertSame('Label A', $this->pluginManager->getDefinition('test')['label']);
$config->set('label', 'Label B');
$config->save();
$this->assertSame('Label B', $this->pluginManager->getDefinition('test')['label']);
}
/**
* Tests migration status.
*/
public function testMigrationStatus(): void {
$configs = [
[
'id' => 'test_active',
'status' => TRUE,
'label' => 'Label Active',
'migration_tags' => [],
'source' => [],
'destination' => [],
'migration_dependencies' => [],
],
[
'id' => 'test_inactive',
'status' => FALSE,
'label' => 'Label Inactive',
'migration_tags' => [],
'source' => [],
'destination' => [],
'migration_dependencies' => [],
],
];
foreach ($configs as $config) {
Migration::create($config)->save();
}
$definitions = $this->pluginManager->getDefinitions();
$this->assertCount(1, $definitions);
$this->assertArrayHasKey('test_active', $definitions);
$this->expectException(PluginNotFoundException::class);
$this->expectExceptionMessage('The "test_inactive" plugin does not exist.');
$this->pluginManager->getDefinition('test_inactive');
}
/**
* Tests migration from configuration.
*/
public function testImport(): void {
$this->installConfig('migrate_plus_test');
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->pluginManager->createInstance('fruit_terms');
$id_map = $migration->getIdMap();
$executable = new MigrateExecutable($migration, $this);
$executable->import();
$this->assertSame(3, $id_map->importedCount());
}
}

View File

@@ -0,0 +1,146 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate_plus\Entity\Migration;
use Drupal\migrate_plus\Entity\MigrationGroup;
/**
* Test migration groups.
*
* @group migrate_plus
*/
final class MigrationGroupTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['migrate', 'migrate_plus', 'migrate_plus_test'];
/**
* Test that group configuration is properly merged into specific migrations.
*/
public function testConfigurationMerge(): void {
$group_id = 'test_group';
/** @var \Drupal\migrate_plus\Entity\MigrationGroupInterface $migration_group */
$group_configuration = [
'id' => $group_id,
'shared_configuration' => [
// In migration, so will be overridden.
'migration_tags' => ['Drupal 6'],
'audit' => TRUE,
'source' => [
'constants' => [
// Not in migration, so will be added.
'type' => 'image',
// In migration, so will be overridden.
'cardinality' => '1',
],
],
// Not in migration, so will be added.
'destination' => ['plugin' => 'field_storage_config'],
],
];
$this->container->get('entity_type.manager')->getStorage('migration_group')
->create($group_configuration)->save();
/** @var \Drupal\migrate_plus\Entity\MigrationInterface $migration */
$migration = $this->container->get('entity_type.manager')
->getStorage('migration')->create([
'id' => 'specific_migration',
'load' => [],
'migration_group' => $group_id,
'label' => 'Unaffected by the group',
// Overrides group.
'migration_tags' => ['Drupal 7'],
'destination' => [],
'source' => [],
'process' => [],
'migration_dependencies' => [],
]);
$migration->set('source', [
// Not in group, persists.
'plugin' => 'empty',
'constants' => [
// Not in group, persists.
'entity_type' => 'user',
// Overrides group.
'cardinality' => '3',
],
]);
$migration->save();
$expected_config = [
'label' => 'Unaffected by the group',
'getMigrationTags' => ['Drupal 7'],
'isAuditable' => TRUE,
'getSourceConfiguration' => [
'plugin' => 'empty',
'constants' => [
'entity_type' => 'user',
'type' => 'image',
'cardinality' => '3',
],
],
'getDestinationConfiguration' => ['plugin' => 'field_storage_config'],
];
/** @var \Drupal\migrate_plus\Plugin\MigrationInterface $loaded_migration */
$loaded_migration = $this->container->get('plugin.manager.migration')
->createInstance('specific_migration');
foreach ($expected_config as $method => $expected_value) {
$actual_value = $loaded_migration->$method();
$this->assertEquals($expected_value, $actual_value);
}
}
/**
* Test that deleting a group deletes its migrations.
*/
public function testDelete(): void {
/** @var \Drupal\migrate_plus\Entity\MigrationGroupInterface $migration_group */
$group_configuration = [
'id' => 'test_group',
];
$migration_group = $this->container->get('entity_type.manager')
->getStorage('migration_group')->create($group_configuration);
$migration_group->save();
/** @var \Drupal\migrate_plus\Entity\MigrationInterface $migration */
$migration = $this->container->get('entity_type.manager')
->getStorage('migration')->create([
'id' => 'specific_migration',
'migration_group' => 'test_group',
'migration_tags' => [],
'load' => [],
'destination' => [],
'source' => [],
'migration_dependencies' => [],
]);
$migration->save();
/** @var \Drupal\migrate_plus\Entity\MigrationGroupInterface $loaded_migration_group */
$loaded_migration_group = MigrationGroup::load('test_group');
$loaded_migration_group->delete();
/** @var \Drupal\migrate_plus\Entity\MigrationInterface $loaded_migration */
$loaded_migration = Migration::load('specific_migration');
$this->assertNull($loaded_migration);
}
/**
* Test that migrations without a group are assigned to the default group.
*/
public function testDefaultGroup(): void {
$this->installConfig('migrate_plus_test');
/** @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface $pluginManager */
$pluginManager = \Drupal::service('plugin.manager.migration');
$migration = $pluginManager->getDefinition('dummy');
$this->assertEquals($migration['migration_group'], 'default', 'Migrations without an explicit group are assigned the default group.');
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrateDestinationInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests the default_entity_value plugin.
*
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\DefaultEntityValue
* @group migrate_plus
*/
final class DefaultEntityValueTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate_plus',
'migrate',
'user',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
}
/**
* Tests the lookup when the value is empty.
*
* @covers ::transform
*/
public function testDefaultEntityValue(): void {
// Create a user.
$editorial_user = $this->createUser([], 'editorial');
$journalist_user = $this->createUser([], 'journalist');
// Setup test migration objects.
$migration_prophecy = $this->prophesize(MigrationInterface::class);
$migrate_destination_prophecy = $this->prophesize(MigrateDestinationInterface::class);
$migrate_destination_prophecy->getPluginId()->willReturn('user');
$migrate_destination = $migrate_destination_prophecy->reveal();
$migration_prophecy->getDestinationPlugin()->willReturn($migrate_destination);
$migration_prophecy->getProcess()->willReturn([]);
$migration = $migration_prophecy->reveal();
$configuration = [
'entity_type' => 'user',
'value_key' => 'name',
'ignore_case' => TRUE,
'default_value' => 'editorial',
];
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('default_entity_value', $configuration, $migration);
$executable = $this->prophesize(MigrateExecutableInterface::class)->reveal();
$row = new Row();
// Check the case default value is not used.
$value = $plugin->transform($journalist_user->id(), $executable, $row, 'name');
$this->assertSame($journalist_user->id(), $value);
// Check the default value is found.
$value = $plugin->transform('', $executable, $row, 'name');
$this->assertSame($editorial_user->id(), $value);
}
}

View File

@@ -0,0 +1,972 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessageInterface;
use Drupal\migrate\Plugin\MigrationPluginManager;
use Drupal\node\Entity\NodeType;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
/**
* Tests the migration plugin.
*
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\EntityGenerate
* @group migrate_plus
*/
final class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterface {
use EntityReferenceFieldCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate_plus',
'migrate',
'user',
'system',
'node',
'taxonomy',
'field',
'text',
'filter',
];
private static ?string $bundle = 'page';
private static ?string $fieldName = 'field_entity_reference';
private static ?string $vocabulary = 'fruit';
protected ?MigrationPluginManager $migrationPluginManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create article content type.
$values = [
'type' => self::$bundle,
'name' => 'Page',
];
$node_type = NodeType::create($values);
$node_type->save();
$this->installEntitySchema('node');
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('taxonomy_vocabulary');
$this->installEntitySchema('user');
$this->installSchema('user', 'users_data');
$this->installConfig(self::$modules);
// Create a vocabulary.
$vocabulary = Vocabulary::create([
'name' => self::$vocabulary,
'description' => self::$vocabulary,
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$vocabulary->save();
// Create a field.
$this->createEntityReferenceField(
'node',
self::$bundle,
self::$fieldName,
'Term reference',
'taxonomy_term',
'default',
['target_bundles' => [self::$vocabulary]],
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
);
// Create a non-reference field.
FieldStorageConfig::create([
'field_name' => 'field_integer',
'type' => 'integer',
'entity_type' => 'node',
])->save();
FieldConfig::create([
'field_name' => 'field_integer',
'entity_type' => 'node',
'bundle' => self::$bundle,
])->save();
$this->migrationPluginManager = \Drupal::service('plugin.manager.migration');
}
/**
* Tests generating an entity.
*
* @dataProvider transformDataProvider
*
* @covers ::transform
*/
public function testTransform(array $definition, array $expected, array $preSeed = []): void {
// Pre seed some test data.
foreach ($preSeed as $storageName => $values) {
// If the first element of $values is a non-empty array, create multiple
// entities. Otherwise, create just one entity.
if (isset($values[0])) {
foreach ($values as $itemValues) {
$this->createTestData($storageName, $itemValues);
}
}
else {
$this->createTestData($storageName, $values);
}
}
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->migrationPluginManager->createStubMigration($definition);
$reflector = new \ReflectionObject($migration->getDestinationPlugin());
$attribute = $reflector->getProperty('storage');
$attribute->setAccessible(TRUE);
/** @var \Drupal\Core\Entity\EntityStorageBase $storage */
$storage = $attribute->getValue($migration->getDestinationPlugin());
$migrationExecutable = (new MigrateExecutable($migration, $this));
$migrationExecutable->import();
foreach ($expected as $row) {
$entity = $storage->load($row['id']);
$properties = array_diff_key($row, array_flip(['id']));
foreach ($properties as $property => $value) {
if (is_array($value)) {
if (empty($value)) {
$this->assertEmpty($entity->{$property}->getValue(), "Expected value is 'unset' but field $property is set.");
}
else {
// Check if we're testing multiple values in one field. If so, loop
// through them one-by-one and check that they're present in the
// $entity.
if (isset($value[0])) {
foreach ($value as $valueID => $valueToCheck) {
foreach ($valueToCheck as $key => $expectedValue) {
if (empty($expectedValue)) {
if (!$entity->{$property}->isEmpty()) {
$this->assertTrue($entity->{$property}[0]->entity->{$key}->isEmpty(), "Expected value is empty but field $property.$key is not empty.");
}
else {
$this->assertTrue($entity->{$property}->isEmpty(), "Expected value is empty but field $property is not empty.");
}
}
elseif ($entity->{$property}->getValue()) {
$this->assertEquals($expectedValue, $entity->get($property)->offsetGet($valueID)->entity->{$key}->value);
}
else {
$this->fail("Expected value: $expectedValue does not exist in $property.");
}
}
}
}
// If we get to this point, we're only checking a
// single field value.
else {
foreach ($value as $key => $expectedValue) {
if (empty($expectedValue)) {
if (!$entity->{$property}->isEmpty()) {
$this->assertTrue($entity->{$property}[0]->entity->{$key}->isEmpty(), "Expected value is empty but field $property.$key is not empty.");
}
else {
$this->assertTrue($entity->{$property}->isEmpty(), "BINBAZ Expected value is empty but field $property is not empty.");
}
}
elseif ($entity->{$property}->getValue()) {
$referenced_entity = $entity->{$property}[0]->entity;
$result_value = $referenced_entity instanceof ConfigEntityInterface ? $referenced_entity->get($key) : $referenced_entity->get($key)->value;
$this->assertEquals($expectedValue, $result_value);
}
else {
$this->fail("Expected value: $expectedValue does not exist in $property.");
}
}
}
}
}
else {
$this->assertNotEmpty($entity, 'Entity with label ' . $row[$property] . ' is empty');
$this->assertEquals($row[$property], $entity->label());
}
}
}
}
/**
* Test lookup without a reference field.
*/
public function testNonReferenceField(): void {
$values = [
'name' => 'Apples',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
];
$this->createTestData('taxonomy_term', $values);
// Not enough context is provided for a non reference field, so error out.
$definition = [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apples',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
'title' => 'title',
'field_integer' => [
'plugin' => 'entity_generate',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
];
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->migrationPluginManager->createStubMigration($definition);
$migrationExecutable = (new MigrateExecutable($migration, $this));
$migrationExecutable->import();
$this->assertStringEndsWith('Destination field type integer is not a recognized reference type.', $migration->getIdMap()->getMessages()->fetch()->message);
$this->assertSame(1, $migration->getIdMap()->messageCount());
// Enough context is provided so this should work.
$definition = [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apples',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
'title' => 'title',
'field_integer' => [
'plugin' => 'entity_generate',
'source' => 'term',
'value_key' => 'name',
'bundle_key' => 'vid',
'bundle' => self::$vocabulary,
'entity_type' => 'taxonomy_term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
];
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->migrationPluginManager->createStubMigration($definition);
$migrationExecutable = (new MigrateExecutable($migration, $this));
$migrationExecutable->import();
$this->assertEmpty($migration->getIdMap()->messageCount());
$term = Term::load(1);
$this->assertEquals('Apples', $term->label());
}
/**
* Provides multiple migration definitions for "transform" test.
*/
public static function transformDataProvider(): array {
return [
'no arguments' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apples',
],
[
'id' => 2,
'title' => 'content item 2',
'term' => 'Bananas',
],
[
'id' => 3,
'title' => 'content item 3',
'term' => 'Grapes',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
'title' => 'title',
self::$fieldName => [
'plugin' => 'entity_generate',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'title' => 'content item 1',
self::$fieldName => [
'tid' => 2,
'name' => 'Apples',
],
],
'row 2' => [
'id' => 2,
'title' => 'content item 2',
self::$fieldName => [
'tid' => 3,
'name' => 'Bananas',
],
],
'row 3' => [
'id' => 3,
'title' => 'content item 3',
self::$fieldName => [
'tid' => 1,
'name' => 'Grapes',
],
],
],
'preSeed' => [
'taxonomy_term' => [
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
'no arguments_lookup_only' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apples',
],
[
'id' => 2,
'title' => 'content item 2',
'term' => 'Bananas',
],
[
'id' => 3,
'title' => 'content item 3',
'term' => 'Grapes',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
'title' => 'title',
self::$fieldName => [
'plugin' => 'entity_lookup',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'title' => 'content item 1',
self::$fieldName => [
'tid' => NULL,
'name' => NULL,
],
],
'row 2' => [
'id' => 2,
'title' => 'content item 2',
self::$fieldName => [
'tid' => NULL,
'name' => NULL,
],
],
'row 3' => [
'id' => 3,
'title' => 'content item 3',
self::$fieldName => [
'tid' => 1,
'name' => 'Grapes',
],
],
],
'preSeed' => [
'taxonomy_term' => [
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
'provide values' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apples',
],
[
'id' => 2,
'title' => 'content item 2',
'term' => 'Bananas',
],
[
'id' => 3,
'title' => 'content item 3',
'term' => 'Grapes',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
'title' => 'title',
'term_upper' => [
'plugin' => 'callback',
'source' => 'term',
'callable' => 'strtoupper',
],
self::$fieldName => [
'plugin' => 'entity_generate',
'source' => 'term',
'values' => [
'description' => '@term_upper',
],
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'title' => 'content item 1',
self::$fieldName => [
'tid' => 2,
'name' => 'Apples',
'description' => 'APPLES',
],
],
'row 2' => [
'id' => 2,
'title' => 'content item 2',
self::$fieldName => [
'tid' => 3,
'name' => 'Bananas',
'description' => 'BANANAS',
],
],
'row 3' => [
'id' => 3,
'title' => 'content item 3',
self::$fieldName => [
'tid' => 1,
'name' => 'Grapes',
'description' => NULL,
],
],
],
'preSeed' => [
'taxonomy_term' => [
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
'provide multiple values' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apples',
],
[
'id' => 2,
'title' => 'content item 2',
'term' => 'Bananas',
],
[
'id' => 3,
'title' => 'content item 3',
'term' => 'Grapes',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
'constants' => [
'foo' => 'bar',
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
'title' => 'title',
'term_upper' => [
'plugin' => 'callback',
'source' => 'term',
'callable' => 'strtoupper',
],
self::$fieldName => [
'plugin' => 'entity_generate',
'source' => 'term',
'values' => [
'name' => '@term_upper',
'description' => 'constants/foo',
],
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'title' => 'content item 1',
self::$fieldName => [
'tid' => 2,
'name' => 'APPLES',
'description' => 'bar',
],
],
'row 2' => [
'id' => 2,
'title' => 'content item 2',
self::$fieldName => [
'tid' => 3,
'name' => 'BANANAS',
'description' => 'bar',
],
],
'row 3' => [
'id' => 3,
'title' => 'content item 3',
self::$fieldName => [
'tid' => 1,
'name' => 'Grapes',
'description' => NULL,
],
],
],
'preSeed' => [
'taxonomy_term' => [
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
'lookup single existing term returns correct term' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Grapes',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
'title' => 'title',
self::$fieldName => [
'plugin' => 'entity_lookup',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'title' => 'content item 1',
self::$fieldName => [
'tid' => 1,
'name' => 'Grapes',
],
],
],
'preSeed' => [
'taxonomy_term' => [
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
'lookup single missing term returns null value' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apple',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
'title' => 'title',
self::$fieldName => [
'plugin' => 'entity_lookup',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'title' => 'content item 1',
self::$fieldName => [],
],
],
'preSeed' => [
'taxonomy_term' => [
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
'lookup multiple existing terms returns correct terms' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => [
'Grapes',
'Apples',
],
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'title' => 'title',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
self::$fieldName => [
'plugin' => 'entity_lookup',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'title' => 'content item 1',
self::$fieldName => [
[
'tid' => 1,
'name' => 'Grapes',
],
[
'tid' => 2,
'name' => 'Apples',
],
],
],
],
'preSeed' => [
'taxonomy_term' => [
[
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
[
'name' => 'Apples',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
],
'lookup multiple mixed terms returns correct terms' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => [
'Grapes',
'Pears',
],
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'title' => 'title',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
self::$fieldName => [
'plugin' => 'entity_lookup',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => '1',
'title' => 'content item 1',
self::$fieldName => [
[
'tid' => 1,
'name' => 'Grapes',
],
],
],
],
'preSeed' => [
'taxonomy_term' => [
[
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
[
'name' => 'Apples',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
],
'lookup with empty term value returns no terms' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => [],
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'title' => 'title',
'type' => [
'plugin' => 'default_value',
'default_value' => self::$bundle,
],
self::$fieldName => [
'plugin' => 'entity_lookup',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'title' => 'content item 1',
self::$fieldName => [],
],
],
'preSeed' => [
'taxonomy_term' => [
'name' => 'Grapes',
'vid' => self::$vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
],
],
'lookup config entity' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'name' => 'user 1',
'mail' => 'user1@user1.com',
'roles' => ['role_1'],
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'name' => 'name',
'roles' => [
'plugin' => 'entity_lookup',
'entity_type' => 'user_role',
'value_key' => 'id',
'source' => 'roles',
],
],
'destination' => [
'plugin' => 'entity:user',
],
],
'expected' => [
'row 1' => [
'id' => 1,
'name' => 'user 1',
'roles' => [
'id' => 'role_1',
'label' => 'Role 1',
],
],
],
'preSeed' => [
'user_role' => [
'id' => 'role_1',
'label' => 'Role 1',
],
],
],
];
}
/**
* {@inheritdoc}
*/
public function display($message, $type = 'status'): void {
$this->assertTrue($type == 'status', $message);
}
/**
* Create pre-seed test data.
*
* @param string $storageName
* The storage manager to create.
* @param array $values
* The values to use when creating the entity.
*
* @return string|int
* The entity identifier.
*/
private function createTestData($storageName, array $values) {
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = $this->container
->get('entity_type.manager')
->getStorage($storageName);
$entity = $storage->create($values);
$entity->save();
return $entity->id();
}
}

View File

@@ -0,0 +1,271 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\Row;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests the entity_lookup plugin.
*
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\EntityLookup
* @group migrate_plus
*/
final class EntityLookupTest extends KernelTestBase {
use UserCreationTrait;
use NodeCreationTrait;
/**
* The migrate executable mock object.
*
* @var \Drupal\migrate\MigrateExecutable|\PHPUnit\Framework\MockObject\MockObject
*/
protected $migrateExecutable;
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate_plus',
'migrate',
'user',
'system',
'node',
'filter',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installConfig(['filter']);
$this->migrateExecutable = $this->getMockBuilder('Drupal\migrate\MigrateExecutable')
->disableOriginalConstructor()
->getMock();
$test_nodes = [
['title' => 'foo 1'],
['title' => 'foo 2'],
['title' => 'bar 1'],
];
foreach ($test_nodes as $test_node) {
$this->createNode($test_node);
}
}
/**
* Lookup an entity without bundles on destination key.
*
* Using user entity as destination entity without bundles as example for
* testing.
*
* @covers ::transform
*/
public function testLookupEntityWithoutBundles(): void {
$migration = \Drupal::service('plugin.manager.migration')
->createStubMigration([
'id' => 'test',
'source' => [],
'process' => [],
'destination' => [
'plugin' => 'entity:user',
],
]);
// Create a user.
$known_user = $this->createUser([], 'lucuma');
$configuration = [
'entity_type' => 'user',
'value_key' => 'name',
];
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
$row = new Row();
// Check the known user is found.
$value = $plugin->transform('lucuma', $this->migrateExecutable, $row, 'name');
$this->assertSame($known_user->id(), $value);
// Check an unknown user is not found.
$value = $plugin->transform('orange', $this->migrateExecutable, $row, 'name');
$this->assertNull($value);
}
/**
* Lookup an entity on an entity_reference field.
*
* @covers ::transform
*/
public function testLookupEntityOnEntityReferenceField(): void {
$migration = \Drupal::service('plugin.manager.migration')
->createStubMigration([
'id' => 'test',
'source' => [],
'process' => [],
'destination' => [
'plugin' => 'entity:node',
],
]);
// Create a user.
$known_user = $this->createUser([], 'lucuma');
// Create a node owned by this user.
$known_node = $this->createNode([
'title' => 'Node test',
'uid' => $known_user->id(),
]);
$configuration = [
'entity_type' => 'node',
'value_key' => 'uid',
];
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
$row = new Row();
// Check the known node is found.
$value = $plugin->transform($known_user->id(), $this->migrateExecutable, $row, 'nid');
$this->assertSame($known_node->id(), $value);
// Check an unknown node is not found.
$value = $plugin->transform('not-an-id', $this->migrateExecutable, $row, 'nid');
$this->assertNull($value);
}
/**
* Tests a lookup of config entity.
*/
public function testConfigEntityLookup(): void {
$migration = \Drupal::service('plugin.manager.migration')
->createStubMigration([
'id' => 'test',
'source' => [],
'process' => [],
'destination' => [
'plugin' => 'entity:node',
],
]);
$configuration = [
'entity_type' => 'filter_format',
'value_key' => 'name',
];
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
$value = $plugin->transform('Plain text', $this->migrateExecutable, new Row(), 'destination_property');
$this->assertEquals('plain_text', $value);
}
/**
* Tests lookup with different operators.
*
* @covers ::transform
* @dataProvider providerTestLookupOperators
*/
public function testLookupOperators(array $configuration, mixed $lookup_value, mixed $expected_value): void {
$migration = \Drupal::service('plugin.manager.migration')
->createStubMigration([
'id' => 'test',
'source' => [],
'process' => [],
'destination' => [
'plugin' => 'entity:node',
],
]);
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
$value = $plugin->transform($lookup_value, $this->migrateExecutable, new Row(), 'destination_property');
$this->assertEquals($expected_value, $value);
}
/**
* Data provider for testLookupOperators test.
*
* @return array[]
* The test cases.
*/
public static function providerTestLookupOperators(): array {
return [
'Default operator' => [
[
'entity_type' => 'node',
'value_key' => 'title',
],
'foo 1',
'1',
],
'Multiple values' => [
[
'entity_type' => 'node',
'value_key' => 'title',
],
['foo 1', 'foo 2'],
['2', '1'],
],
'Starts with' => [
[
'entity_type' => 'node',
'value_key' => 'title',
'operator' => 'STARTS_WITH',
],
'bar',
'3',
],
];
}
/**
* Tests a lookup with bundle conditions.
*/
public function testEntityLookupWithBundles(): void {
$migration = \Drupal::service('plugin.manager.migration')
->createStubMigration([
'id' => 'test',
'source' => [],
'process' => [],
'destination' => [
'plugin' => 'entity:node',
],
]);
// Create a node of article content type.
$this->createNode([
'title' => 'article 1',
'type' => 'article',
]);
$configuration = [
'entity_type' => 'node',
'value_key' => 'title',
'bundle_key' => 'type',
'bundle' => 'page',
];
// The search is performed by one bundle - node is not found.
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
$value = $plugin->transform('article 1', $this->migrateExecutable, new Row(), 'destination_property');
$this->assertEquals(NULL, $value);
// Now include both bundles in the search - node is found.
$configuration['bundle'] = ['page', 'article'];
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
$value = $plugin->transform('article 1', $this->migrateExecutable, new Row(), 'destination_property');
$this->assertEquals('4', $value);
}
}

View File

@@ -0,0 +1,235 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\migrate_plus\Plugin\migrate\process\EntityValue;
use Drupal\node\Entity\Node;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\NodeType;
/**
* Tests the entity_value plugin.
*
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\EntityValue
* @group migrate_drupal
*/
final class EntityValueTest extends KernelTestBase {
/**
* The generated title.
*/
protected ?string $title;
/**
* The generated Spanish title.
*/
protected ?string $titleSpanish;
/**
* The generated node ID.
*/
protected ?string $uid;
/**
* The plugin to test.
*/
protected ?EntityValue $plugin;
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate',
'migrate_plus',
'system',
'node',
'user',
'language',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
ConfigurableLanguage::createFromLangcode('es')->save();
$this->installSchema('node', 'node_access');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->title = $this->randomString();
$this->titleSpanish = $this->randomString();
$node = Node::create([
'type' => 'page',
'title' => $this->title,
'langcode' => 'en',
]);
$node->save();
$node_es = $node->addTranslation('es');
$node_es->setTitle($this->titleSpanish);
$node_es->save();
$this->uid = $node->id();
}
/**
* Test the EntityLoad plugin succeeding.
*
* @covers ::transform
*/
public function testEntityValueSuccess(): void {
$this->plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_value', [
'entity_type' => 'node',
'field_name' => 'title',
]);
$executable = $this->prophesize(MigrateExecutableInterface::class)
->reveal();
$row = new Row();
// Ensure that the entity is returned if it really exists.
$value = $this->plugin->transform($this->uid, $executable, $row, 'dummmy');
$this->assertSame($this->title, $value[0]['value']);
$this->assertFalse($this->plugin->multiple());
// Ensure that an array of entities is returned.
$value = $this->plugin->transform([$this->uid], $executable, $row,
'dummmy');
$this->assertSame($this->title, $value[0][0]['value']);
$this->assertTrue($this->plugin->multiple());
// Ensure that the plugin returns [] if the entity doesn't exist.
$value = $this->plugin->transform(9999999, $executable, $row, 'dummmy');
$this->assertSame([], $value);
$this->assertFalse($this->plugin->multiple());
// Ensure that the plugin returns [] if NULL is passed.
$value = $this->plugin->transform(NULL, $executable, $row, 'dummmy');
$this->assertSame([], $value);
$this->assertFalse($this->plugin->multiple());
}
/**
* Test the EntityLoad plugin succeeding.
*
* @covers ::transform
*/
public function testEntityValueLangSuccess(): void {
$this->plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_value', [
'entity_type' => 'node',
'langcode' => 'es',
'field_name' => 'title',
]);
$executable = $this->prophesize(MigrateExecutableInterface::class)
->reveal();
$row = new Row();
// Ensure that the entity is returned if it really exists.
$value = $this->plugin->transform($this->uid, $executable, $row, 'dummmy');
$this->assertSame($this->titleSpanish, $value[0]['value']);
$this->assertFalse($this->plugin->multiple());
// Ensure that an array of entities is returned.
$value = $this->plugin->transform([$this->uid], $executable, $row,
'dummmy');
$this->assertSame($this->titleSpanish, $value[0][0]['value']);
$this->assertTrue($this->plugin->multiple());
}
/**
* Test the EntityLoad plugin failure.
*
* @covers ::transform
*/
public function testEntityValueLangException(): void {
$config_entity = NodeType::create(['type' => 'page', 'name' => 'page']);
$config_entity->save();
$this->plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_value', [
'entity_type' => 'node_type',
'langcode' => 'es',
'field_name' => 'type',
]);
$executable = $this->prophesize(MigrateExecutableInterface::class)
->reveal();
$row = new Row();
// Ensure that the entity is returned if it really exists.
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('Langcode can only be used with content entities currently.');
$this->plugin->transform([$config_entity->id()], $executable, $row, 'dummmy');
}
/**
* Test the EntityLoad plugin throwing.
*
* @param mixed $config
* the Plugin Config.
*
* @covers ::__construct
* @dataProvider entityValueFailureConfigData
*/
public function testEntityValueConfig(array $config): void {
$this->expectException(\InvalidArgumentException::class);
\Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_value', $config);
}
/**
* Provides data for entityLoadFailureConfigData.
*
* The data.
*/
public static function entityValueFailureConfigData(): array {
return [
[
[
'entity_type' => '',
],
],
[
[
'entity_type' => NULL,
],
],
[
[
'entity_type' => 'node',
'source' => '',
],
],
[
[
'entity_type' => 'node',
'source' => NULL,
],
],
[
[
'entity_type' => 'node',
'source' => 'test',
'field_name' => '',
],
],
[
[
'entity_type' => 'node',
'source' => 'test',
'field_name' => NULL,
],
],
];
}
}

View File

@@ -0,0 +1,235 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
use Drupal\Core\File\FileSystemInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Row;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests the file_blob plugin.
*
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\FileBlob
* @group migrate_plus
*/
final class FileBlobTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate',
'migrate_plus',
'system',
];
/**
* The process plugin manager.
*/
protected ?MigratePluginManagerInterface $pluginManager;
/**
* The blob representation of a cat image.
*/
protected ?string $blob;
/**
* The destination filename.
*/
protected ?string $filename;
/**
* The sha1sum of the blob.
*/
protected ?string $sha1sum;
/**
* The filesystem interface.
*/
protected ?FileSystemInterface $filesystem = NULL;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->pluginManager = $this->container->get('plugin.manager.migrate.process');
$this->filesystem = $this->container->get('file_system');
$this->filename = 'public://subdir/cat.jpeg';
$this->sha1sum = 'a8eb2b9a987cfda507356d884f0498289bd0f620';
$this->blob = <<<EOT
/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg
SlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkk
KyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVF
RUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgBLADI
AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMF
BQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkq
NDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqi
o6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/E
AB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR
BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVG
R0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKz
tLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A
jA4pwFCqRUgHNcZ1iAcVIKQU8CmAoFOFA6UUCFpcUmaWgBRS0lKAaBC0UlGaAHZoBqMnilDcUASi
jFIDTqAExSGnU00AMYCmECnnrSEUhkeKYVqU1GxpDGEU04FDnFNGKB3ExminZzRQFyQJTvLqUKKf
5Z28VRNyALSqM5qTbgc0BMHPrQK5HjBxT9ppzJnFOAxQFxm00u2pNooI4oC5GBTsUxnxxmhHycZo
AcaaalxUbjFMCMkmnAetN46U4ZFAEg6/Sl3UzPNAPrQBJnimg45NNJ4psjYAFIBVGcn1odto96QE
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
2Q==
EOT;
}
/**
* Tests file creation.
*
* @covers ::transform
*/
public function testFileCreation(): void {
/** @var \Drupal\migrate\MigrateExecutableInterface $executable */
$executable = $this->prophesize(MigrateExecutableInterface::class)->reveal();
// Delete the target directory in case it already exists
// to make sure target directories are automatically created
if (is_dir(dirname($this->filename))) {
rmdir(dirname($this->filename));
}
// Run the plugin
$row = new Row([], []);
$value = [$this->filename, base64_decode($this->blob, TRUE)];
/** @var \Drupal\migrate_plus\Plugin\migrate\process\FileBlob $file_blob */
$file_blob = $this->pluginManager->createInstance('file_blob');
$file = $file_blob->transform($value, $executable, $row, 'destination_property');
$this->assertEquals($this->filename, $file);
$this->assertEquals($this->sha1sum, sha1_file($file));
// Run the plugin again, but error if the file already exists
$configuration = [
'reuse' => FileSystemInterface::EXISTS_ERROR,
];
/** @var \Drupal\migrate_plus\Plugin\migrate\process\FileBlob $file_blob */
$file_blob = $this->pluginManager->createInstance('file_blob', $configuration);
/** @var \Drupal\migrate\MigrateExecutableInterface $executable */
$file = $file_blob->transform($value, $executable, $row, 'destination_property');
$this->assertEquals($this->filename, $file);
$this->assertEquals($this->sha1sum, sha1_file($file));
}
}

View File

@@ -0,0 +1,119 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Row;
/**
* Tests the service plugin.
*
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\Service
* @group migrate_plus
*/
final class ServiceTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate',
'migrate_plus',
'system',
];
/**
* The process plugin manager.
*/
protected ?MigratePluginManagerInterface $pluginManager = NULL;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->pluginManager = $this->container->get('plugin.manager.migrate.process');
}
/**
* Tests using a service.
*
* @covers ::create
*/
public function testValidConfig(): void {
/** @var \Drupal\migrate\MigrateExecutableInterface $executable */
$executable = $this->prophesize(MigrateExecutableInterface::class)->reveal();
$row = new Row([], []);
$configuration = [
'service' => 'email.validator',
'method' => 'isValid',
];
/** @var \Drupal\migrate_plus\Plugin\migrate\process\Service $service */
$service = $this->pluginManager->createInstance('service', $configuration);
// Test a valid email address.
$value = 'drupal@example.com';
$bool = $service->transform($value, $executable, $row, 'destination_property');
$this->assertEquals(TRUE, $bool);
// Test an invalid email address.
$value = 'drupal_example.com';
$bool = $service->transform($value, $executable, $row, 'destination_property');
$this->assertEquals(FALSE, $bool);
}
/**
* Tests configuration validation.
*
* @param string[] $configuration
* The configuration for the service plugin. The expected keys are 'service'
* and 'method'.
* @param string $message
* The expected exception message.
*
* @covers ::create
*
* @dataProvider providerConfig
*/
public function testInvalidConfig(array $configuration, string $message): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage($message);
$this->pluginManager->createInstance('service', $configuration);
}
/**
* Data provider for testInvalidConfig.
*/
public static function providerConfig(): array {
return [
'missing service name' => [
'configuration' => [],
'message' => 'The "service" must be set.',
],
'missing method name' => [
'configuration' => ['service' => 'email.validator'],
'message' => 'The "method" must be set.',
],
'invalid service name' => [
'configuration' => [
'service' => 'no shirt no shoes no service',
'method' => 'isValid',
],
'message' => 'You have requested the non-existent service "no shirt no shoes no service".',
],
'invalid method name' => [
'configuration' => [
'service' => 'email.validator',
'method' => 'noSuchMethod',
],
'message' => 'The "email.validator" service has no method "noSuchMethod".',
],
];
}
}

View File

@@ -0,0 +1,123 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\source;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests Table source plugin.
*
* @covers \Drupal\migrate_plus\Plugin\migrate\source\Table
*
* @group migrate_plus
*/
final class TableTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['migrate_plus'];
/**
* The migration plugin manager.
*
* @var \Drupal\migrate\Plugin\MigrationPluginManager
*/
protected $migrationPluginManager;
/**
* Definition of a test migration.
*/
protected ?array $migrationDefinition;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->migrationPluginManager = \Drupal::service('plugin.manager.migration');
$this->migrationDefinition = [
'id' => 'test',
'source' => [
'plugin' => 'table',
'table_name' => 'foo',
'fields' => [],
'id_fields' => [],
],
'process' => [],
'destination' => [
'plugin' => 'null',
],
];
}
/**
* Tests 'Table' source plugin requirements.
*/
public function testCheckRequirements(): void {
$this->expectException(RequirementsException::class);
$this->expectExceptionMessage("Source database table 'foo' does not exist");
$this->migrationPluginManager->createStubMigration($this->migrationDefinition)
->getSourcePlugin()
->checkRequirements();
}
/**
* Test 'Table' source plugin constructor with invalid configuration.
*
* @dataProvider badConfigurationProvider
*/
public function testTableBadConfiguration(array $configuration, string $message): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage($message);
$configuration['plugin'] = 'table';
$this->migrationPluginManager->createStubMigration([
'source' => $configuration,
])->getSourcePlugin();
}
/**
* Data provider with invalid plugin configurations.
*
* Plugin configurations and messages.
*/
public static function badConfigurationProvider(): array {
return [
'Missing table_name' => [
[],
'Table plugin is missing table_name property configuration.',
],
'Missing id_fields' => [
[
'table_name' => 'foo',
],
'Table plugin is missing id_fields property configuration.',
],
'Configuration id_fields is not an array' => [
[
'table_name' => 'foo',
'id_fields' => 56,
],
'Table plugin configuration property id_fields must be an array.',
],
'Configuration fields is not an array' => [
[
'table_name' => 'foo',
'id_fields' => [
'color_name' => [
'type' => 'string',
],
],
'fields' => 56,
],
'Table plugin configuration property fields must be an array.',
],
];
}
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate_plus\data_fetcher;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\Http;
/**
* Class HttpTest.
*
* @group migrate_plus
* @package Drupal\Tests\migrate_plus\Unit\migrate_plus\data_fetcher
*/
final class HttpTest extends KernelTestBase {
/**
* Test http headers option.
*
* @dataProvider headerDataProvider
*/
public function testHttpHeaders(array $definition, array $expected, array $preSeed = []): void {
$http = new Http($definition, 'http', []);
$this->assertEquals($expected, $http->getRequestHeaders());
}
/**
* Provides multiple test cases for the testHttpHeaders method.
*
* @return array
* The test cases
*/
public static function headerDataProvider(): array {
return [
'dummy headers specified' => [
'definition' => [
'headers' => [
'Accept' => 'application/json',
'User-Agent' => 'Internet Explorer 6',
'Authorization-Key' => 'secret',
'Arbitrary-Header' => 'foobarbaz',
],
],
'expected' => [
'Accept' => 'application/json',
'User-Agent' => 'Internet Explorer 6',
'Authorization-Key' => 'secret',
'Arbitrary-Header' => 'foobarbaz',
],
],
'no headers specified' => [
'definition' => [
'no_headers_here' => 'foo',
],
'expected' => [],
],
];
}
}

View File

@@ -0,0 +1,209 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate_plus\data_parser;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate_plus\DataParserPluginInterface;
use Drupal\migrate_plus\DataParserPluginManager;
/**
* Test of the data_parser SimpleXml migrate_plus plugin.
*
* @group migrate_plus
*/
abstract class BaseXml extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['migrate', 'migrate_plus'];
/**
* Path for the xml file.
*/
protected ?string $path;
/**
* The plugin manager.
*/
protected ?DataParserPluginManager $pluginManager = NULL;
/**
* The plugin configuration.
*/
protected ?array $configuration;
/**
* The expected result.
*/
protected ?array $expected;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->path = $this->container->get('module_handler')
->getModule('migrate_plus')->getPath();
$this->pluginManager = $this->container
->get('plugin.manager.migrate_plus.data_parser');
$this->configuration = [
'plugin' => 'url',
'data_fetcher_plugin' => 'file',
'data_parser_plugin' => 'simple_xml',
'destination' => 'node',
'urls' => [],
'ids' => ['id' => ['type' => 'integer']],
'fields' => [
[
'name' => 'id',
'label' => 'Id',
'selector' => '@id',
],
[
'name' => 'values',
'label' => 'Values',
'selector' => 'values',
],
],
'item_selector' => '/items/item',
];
$this->expected = [
[
'Value 1',
'Value 2',
],
[
'Value 1 (single)',
],
];
}
/**
* Tests current URL of parsed XML item.
*/
public function testCurrentUrl(): void {
$urls = [
$this->path . '/tests/data/xml_current_url1.xml',
$this->path . '/tests/data/xml_current_url2.xml',
];
$this->configuration['urls'] = $urls;
$parser = $this->getParser();
// First 2 items available in the first URL.
$parser->rewind();
$this->assertEquals($urls[0], $parser->currentUrl());
$parser->next();
$this->assertEquals($urls[0], $parser->currentUrl());
// Third item available in the second URL.
$parser->next();
$this->assertEquals($urls[1], $parser->currentUrl());
}
/**
* Tests reducing single values.
*/
public function testReduceSingleValue(): void {
$url = $this->path . '/tests/data/xml_reduce_single_value.xml';
$this->configuration['urls'][0] = $url;
$this->assertResults($this->expected, $this->getParser());
}
/**
* Tests retrieving single value from element with attributes.
*/
public function testSingleValueWithAttributes() {
$urls = [
$this->path . '/tests/data/xml_persons.xml',
];
$this->configuration['urls'] = $urls;
$this->configuration['item_selector'] = '/persons/person';
$this->configuration['fields'] = [
[
'name' => 'id',
'label' => 'Id',
'selector' => 'id',
],
[
'name' => 'child',
'label' => 'child',
'selector' => 'children/child',
],
];
$names = [];
foreach ($this->getParser() as $item) {
$names[] = (string) $item['child']->name;
}
$expected_names = ['Elizabeth Junior', 'George Junior', 'Lucy'];
$this->assertEquals($expected_names, $names);
}
/**
* Tests retrieval a value with multiple items.
*/
public function testMultipleItems(): void {
$this->configuration['urls'] = [
$this->path . '/tests/data/xml_multiple_items.xml',
];
$this->configuration['fields'] = [
[
'name' => 'id',
'label' => 'Id',
'selector' => 'Id',
],
[
'name' => 'sub_items1',
'label' => 'Sub items 1',
'selector' => 'Values1/SubItem',
],
[
'name' => 'sub_items2',
'label' => 'Sub items 2',
'selector' => 'Values2/SubItem',
],
];
$parser = $this->getParser();
$parser->next();
// Transform SimpleXMLELements to arrays.
$item = json_decode(json_encode($parser->current()), TRUE);
$sub_items1 = array_column($item['sub_items1'], 'Id');
$this->assertEquals(['1', '2'], $sub_items1);
$this->assertEquals(['3', '4'], $item['sub_items2']);
}
/**
* Parses and asserts the results match expectations.
*
* @param array|string $expected
* The expected results.
* @param \Traversable $parser
* An iterable data result to parse.
*/
protected function assertResults($expected, \Traversable $parser): void {
$data = [];
foreach ($parser as $item) {
$values = [];
foreach ($item['values'] as $value) {
$values[] = (string) $value;
}
$data[] = $values;
}
$this->assertEquals($expected, $data);
}
/**
* Returns a parse object with active configuration.
*
* @return \Drupal\migrate_plus\DataParserPluginInterface
* Data parser object.
*/
abstract protected function getParser(): DataParserPluginInterface;
}

View File

@@ -0,0 +1,242 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate_plus\data_parser;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate_plus\DataParserPluginManager;
/**
* Test of the data_parser Json migrate_plus plugin.
*
* @group migrate_plus
*/
final class JsonTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['migrate', 'migrate_plus'];
/**
* Path for the module.
*/
protected ?string $path;
/**
* The plugin manager.
*/
protected ?DataParserPluginManager $pluginManager = NULL;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->path = $this->container->get('module_handler')
->getModule('migrate_plus')->getPath();
$this->pluginManager = $this->container
->get('plugin.manager.migrate_plus.data_parser');
}
/**
* Tests missing properties in json file.
*
* @param string $file
* File name in tests/data/ directory of this module.
* @param array $ids
* Array of ids to pass to the plugin.
* @param array $fields
* Array of fields to pass to the plugin.
* @param array $expected
* Expected array from json decoded file.
*
* @dataProvider providerTestMissingProperties
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* @throws \Exception
*/
public function testMissingProperties(string $file, array $ids, array $fields, array $expected): void {
$url = $this->path . '/tests/data/' . $file;
$conf = [
'plugin' => 'url',
'data_fetcher_plugin' => 'file',
'data_parser_plugin' => 'json',
'destination' => 'node',
'urls' => [$url],
'ids' => $ids,
'fields' => $fields,
'item_selector' => NULL,
];
$json_parser = $this->pluginManager->createInstance('json', $conf);
$data = [];
foreach ($json_parser as $item) {
$data[] = $item;
}
$this->assertEquals($expected, $data);
}
/**
* Provides multiple test cases for the testMissingProperty method.
*
* @return array
* The test cases.
*/
public static function providerTestMissingProperties(): array {
return [
'missing properties' => [
'file' => 'missing_properties.json',
'ids' => ['id' => ['type' => 'integer']],
'fields' => [
[
'name' => 'id',
'label' => 'Id',
'selector' => '/id',
],
[
'name' => 'title',
'label' => 'Title',
'selector' => '/title',
],
[
'name' => 'video_url',
'label' => 'Video url',
'selector' => '/video/url',
],
],
'expected' => [
[
'id' => '1',
'title' => 'Title',
'video_url' => 'https://localhost/',
],
[
'id' => '2',
'title' => '',
'video_url' => 'https://localhost/',
],
[
'id' => '3',
'title' => 'Title 3',
'video_url' => '',
],
],
],
];
}
/**
* Tests item_selector parser property.
*
* @dataProvider providerItemSelector
*/
public function testItemSelector(mixed $item_selector, array $fields, array $expected): void {
$url = $this->path . '/tests/data/item_selector.json';
$conf = [
'plugin' => 'url',
'data_fetcher_plugin' => 'file',
'data_parser_plugin' => 'json',
'destination' => 'node',
'urls' => [$url],
'ids' => ['id' => ['type' => 'integer']],
'item_selector' => $item_selector,
'fields' => $fields,
];
$json_parser = $this->pluginManager->createInstance('json', $conf);
$data = [];
foreach ($json_parser as $item) {
$data[] = $item;
}
$this->assertEquals($expected, $data);
}
/**
* Provides multiple test cases for the testItemSelector method.
*
* @return array
* The test cases.
*/
public static function providerItemSelector(): array {
$fields = [
[
'name' => 'id',
'label' => 'Id',
'selector' => '/id',
],
[
'name' => 'title',
'label' => 'Title',
'selector' => '/title',
],
];
return [
'item_selector 1st level' => [
'item_selector' => '/data',
'fields' => $fields,
'expected' => [
[
'id' => '1',
'title' => '1 item',
],
[
'id' => '2',
'title' => '2 item',
],
],
],
'item_selector is available, data is empty' => [
'item_selector' => '/data_empty',
'fields' => $fields,
'expected' => [],
],
'item_selector not available' => [
'item_selector' => '/data_unavailable',
'fields' => $fields,
'expected' => [],
],
'item_selector 2nd level' => [
'item_selector' => '/data/0/items',
'fields' => $fields,
'expected' => [
[
'id' => '1',
'title' => '1.1 item',
],
[
'id' => '2',
'title' => '1.2 item',
],
],
],
'item_selector 2nd level, depth selector' => [
'item_selector' => 3,
'fields' => $fields,
'expected' => [
[
'id' => '1',
'title' => '1.1 item',
],
[
'id' => '2',
'title' => '1.2 item',
],
[
'id' => '3',
'title' => '2.1 item',
],
],
],
];
}
}

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate_plus\data_parser;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\DataParserPluginInterface;
/**
* Test of the data_parser SimpleXml migrate_plus plugin.
*
* @group migrate_plus
*/
class SimpleXmlTest extends BaseXml {
/**
* Test reading non-standard conforming XML.
*
* XML file with lots of different white spaces before the starting tag.
*/
public function testReadNonStandardXmlWhitespace(): void {
$url = $this->path . '/tests/data/simple_xml_invalid_multi_whitespace.xml';
$this->configuration['urls'][0] = $url;
$this->assertResults($this->expected, $this->getParser());
}
/**
* Test reading non-standard conforming XML .
*
* XML file with one empty line before the starting tag.
*/
public function testReadNonStandardXml2(): void {
$url = $this->path . '/tests/data/simple_xml_invalid_single_line.xml';
$this->configuration['urls'][0] = $url;
$this->assertResults($this->expected, $this->getParser());
}
/**
* Test reading broken XML (missing closing tag).
*/
public function testReadBrokenXmlMissingTag(): void {
$url = $this->path . '/tests/data/simple_xml_broken_missing_tag.xml';
$this->configuration['urls'][0] = $url;
$this->expectException(MigrateException::class);
// Newer versions of libxml mark it as an error 76, older ones as 73.
$this->expectExceptionMessageMatches('/^Fatal Error 7[0-9]/');
$this->getParser()->next();
}
/**
* Test reading broken XML (tag mismatch).
*/
public function testReadBrokenXmlTagMismatch(): void {
$url = $this->path . '/tests/data/simple_xml_broken_tag_mismatch.xml';
$this->configuration['urls'][0] = $url;
$this->expectException(MigrateException::class);
$this->expectExceptionMessageMatches('/^Fatal Error 76/');
$this->getParser()->next();
}
/**
* Test reading non XML.
*/
public function testReadNonXml(): void {
$url = $this->path . '/tests/data/simple_xml_non_xml.xml';
$this->configuration['urls'][0] = $url;
$this->expectException(MigrateException::class);
$this->expectExceptionMessageMatches('/^Fatal Error 46/');
$this->getParser()->next();
}
/**
* Tests reading non-existing XML.
*/
public function testReadNonExistingXml(): void {
$url = $this->path . '/tests/data/xml_non_existing.xml';
$this->configuration['urls'][0] = $url;
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('file parser plugin: could not retrieve data from');
$this->getParser()->next();
}
/**
* {@inheritdoc}
*/
protected function getParser(): DataParserPluginInterface {
return $this->pluginManager->createInstance('simple_xml', $this->configuration);
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate_plus\data_parser;
use Drupal\migrate_plus\DataParserPluginInterface;
/**
* Test of the data_parser Xml migrate_plus plugin.
*
* @group migrate_plus
*/
final class XmlTest extends BaseXml {
/**
* {@inheritdoc}
*/
protected function getParser(): DataParserPluginInterface {
return $this->pluginManager->createInstance('xml', $this->configuration);
}
}

View File

@@ -0,0 +1,189 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\data_fetcher;
use org\bovigo\vfs\vfsStreamDirectory;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\File;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
use org\bovigo\vfs\vfsStream;
/**
* @file
* PHPUnit tests for the Migrate Plus File 'data fetcher' plugin.
*/
/**
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\File
*
* @group migrate_plus
*/
final class FileTest extends MigrateTestCase {
/**
* Directory where test data will be created.
*
* @var string
*/
public const BASE_DIRECTORY = 'migration_data';
/**
* Minimal migration configuration data.
*/
private array $specificMigrationConfig = [
'source' => 'url',
'data_fetcher_plugin' => 'file',
'data_parser_plugin' => 'json',
'item_selector' => 0,
'fields' => [],
'ids' => [
'id' => [
'type' => 'integer',
],
],
];
/**
* The data fetcher plugin ID being tested.
*/
private string $dataFetcherPluginId = 'file';
/**
* The data fetcher plugin definition.
*/
private array $pluginDefinition = [
'id' => 'file',
'title' => 'File',
];
/**
* Test data to populate a file with.
*/
private string $testData = '[
{
"id": 1,
"name": "Joe Bloggs"
}
]';
/**
* Define virtual dir where we'll be creating files in/fetching files from.
*/
private vfsStreamDirectory $baseDir;
/**
* Set up test environment.
*/
public function setUp(): void {
parent::setUp();
$this->baseDir = vfsStream::setup(self::BASE_DIRECTORY);
}
/**
* Test fetching a valid file.
*/
public function testFetchFile(): void {
$file_name = 'file.json';
$file_path = vfsStream::url(implode(DIRECTORY_SEPARATOR, [
self::BASE_DIRECTORY,
$file_name,
]));
$migration_config = $this->specificMigrationConfig + [
'urls' => [$file_path],
];
$plugin = new File(
$migration_config,
$this->dataFetcherPluginId,
$this->pluginDefinition
);
$tree = [
$file_name => $this->testData,
];
vfsStream::create($tree, $this->baseDir);
$expected = json_decode($this->testData, TRUE, 512, JSON_THROW_ON_ERROR);
$retrieved = json_decode($plugin->getResponseContent($file_path), TRUE, 512, JSON_THROW_ON_ERROR);
$this->assertEquals($expected, $retrieved);
}
/**
* Test fetching multiple valid files.
*/
public function testFetchMultipleFiles(): void {
$number_of_files = 3;
$file_paths = [];
$file_names = [];
for ($i = 0; $i < $number_of_files; $i++) {
$file_name = 'file_' . $i . '.json';
$file_names[] = $file_name;
$file_paths[] = vfsStream::url(implode(DIRECTORY_SEPARATOR, [
self::BASE_DIRECTORY,
$file_name,
]));
}
$migration_config = $this->specificMigrationConfig + [
'urls' => $file_paths,
];
$plugin = new File(
$migration_config,
$this->dataFetcherPluginId,
$this->pluginDefinition
);
for ($i = 0; $i < $number_of_files; $i++) {
$file_name = $file_names[$i];
$file_path = $file_paths[$i];
$tree = [
$file_name => $this->testData,
];
vfsStream::create($tree, $this->baseDir);
$expected = json_decode($this->testData, NULL, 512, JSON_THROW_ON_ERROR);
$retrieved = json_decode($plugin->getResponseContent($file_path), NULL, 512, JSON_THROW_ON_ERROR);
$this->assertEquals($expected, $retrieved);
}
}
/**
* Test trying to fetch an unreadable file results in exception.
*/
public function testFetchUnreadableFile(): void {
$file_name = 'file.json';
$file_path = vfsStream::url(implode(DIRECTORY_SEPARATOR, [
self::BASE_DIRECTORY,
$file_name,
]));
$migration_config = $this->specificMigrationConfig + [
'urls' => [$file_path],
];
$plugin = new File(
$migration_config,
$this->dataFetcherPluginId,
$this->pluginDefinition
);
// Create an unreadable file.
vfsStream::newFile($file_name, 0300)
->withContent($this->testData)
->at($this->baseDir);
// Trigger exception trying to read the non-readable file.
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('file parser plugin: could not retrieve data from vfs://migration_data/file.json');
$plugin->getResponseContent($file_path);
}
}

View File

@@ -0,0 +1,236 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\data_fetcher;
use PHPUnit\Framework\MockObject\MockObject;
use Drupal\migrate_plus\AuthenticationPluginInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\DataFetcherPluginBase;
use Drupal\migrate_plus\Plugin\migrate_plus\authentication\Basic;
use Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\Http;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
/**
* @file
* PHPUnit tests for the Migrate Plus Http 'data fetcher' plugin.
*/
/**
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\Http
*
* @group migrate_plus
*/
final class HttpTest extends MigrateTestCase {
/**
* Minimal migration configuration data.
*/
private array $specificMigrationConfig = [
'source' => 'url',
'urls' => ['http://example.org/http_fetcher_test'],
'data_fetcher_plugin' => 'http',
'data_parser_plugin' => 'json',
'item_selector' => 0,
'authentication' => [
'plugin' => 'basic',
'username' => 'testing',
'password' => 'password',
],
'fields' => [],
'ids' => [
'id' => [
'type' => 'integer',
],
],
];
/**
* The data fetcher plugin ID being tested.
*/
private string $dataFetcherPluginId = 'http';
/**
* The data fetcher plugin definition.
*/
private array $pluginDefinition = [
'id' => 'http',
'title' => 'HTTP',
];
/**
* Test data to validate an HTTP response against.
*/
private string $testData = '
{
"id": 1,
"name": "Joe Bloggs"
}
';
/**
* Mocked up Basic authentication plugin.
*/
private ?MockObject $basicAuthenticator = NULL;
/**
* Set up test environment.
*/
public function setUp(): void {
parent::setUp();
// Mock up a Basic authentication plugin that will be used in requests.
$basic_authenticator = $this->createMock(Basic::class);
$basic_authenticator->method('getAuthenticationOptions')
->will($this->returnValue([
'auth' => [
'username',
'password',
],
]));
$this->basicAuthenticator = $basic_authenticator;
}
/**
* Test 'http' data fetcher (with auth) returns an expected response.
*/
public function testFetchHttpWithAuth(): void {
$migration_config = $this->migrationConfiguration + $this->specificMigrationConfig;
$plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition);
$plugin->mockHttpClient([[200, 'application/json', $this->testData]], $this->basicAuthenticator);
// The Guzzle mock returns an instance of StreamInterface.
// http://docs.guzzlephp.org/en/latest/psr7.html
$stream = $plugin->getResponseContent($migration_config['urls'][0]);
$body = json_decode((string) $stream, TRUE, 512, JSON_THROW_ON_ERROR);
// Compare what we got back from the parser to what we expected to get.
$expected = json_decode($this->testData, TRUE, 512, JSON_THROW_ON_ERROR);
$this->assertSame($expected, $body);
}
/**
* Test 'http' data fetcher (without auth) returns an expected response.
*/
public function testFetchHttpNoAuth(): void {
$migration_config = $this->migrationConfiguration + $this->specificMigrationConfig;
unset($migration_config['authentication']);
$plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition);
$plugin->mockHttpClient([[200, 'application/json', $this->testData]], NULL);
$stream = $plugin->getResponseContent($migration_config['urls'][0]);
$body = json_decode((string) $stream, TRUE, 512, JSON_THROW_ON_ERROR);
$expected = json_decode($this->testData, TRUE, 512, JSON_THROW_ON_ERROR);
$this->assertSame($expected, $body);
}
/**
* Test 'http' data fetcher (with auth) dies as expected when auth fails.
*/
public function testFetchHttpAuthFailure(): void {
$migration_config = $this->migrationConfiguration + $this->specificMigrationConfig;
$plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition);
$plugin->mockHttpClient([[403, 'text/html', 'Forbidden']], $this->basicAuthenticator);
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('Error message: Client error: `GET http://example.org/http_fetcher_test` resulted in a `403 Forbidden');
$plugin->getResponseContent($migration_config['urls'][0]);
}
/**
* Test 'http' data fetcher (with auth) dies as expected when server down.
*/
public function testFetchHttp500Error(): void {
$migration_config = $this->migrationConfiguration + $this->specificMigrationConfig;
$plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition);
$plugin->mockHttpClient([[500, 'text/html', 'Internal Server Error']], $this->basicAuthenticator);
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('GET http://example.org/http_fetcher_test` resulted in a `500 Internal Server Error');
$plugin->getResponseContent($migration_config['urls'][0]);
}
}
/**
* Test class to mock an HTTP request.
*/
final class TestHttp extends Http {
/**
* Mocked authenticator plugin.
*
* @var \PHPUnit_Framework_MockObject_MockObject
*/
public $authenticator = NULL;
/**
* Mock the HttpClient, so we can control the request/response(s) etc.
*
* @param array $responses
* An array of responses (arrays), with each consisting of properties,
* ordered: response code, content-type and response body.
* @param object $authenticator
* Mocked authenticator plugin.
*/
public function mockHttpClient(array $responses, object $authenticator = NULL): void {
// Set mocked authentication plugin to be used for the request auth plugin.
$this->authenticator = $authenticator;
$handler_responses = [];
foreach ($responses as $response) {
$handler_responses[] = new Response(
$response[0],
['Content-Type' => $response[1]],
$response[2]
);
}
$mock = new MockHandler($handler_responses);
$handler = HandlerStack::create($mock);
$this->httpClient = new Client(['handler' => $handler]);
}
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
// Skip calling the Http() constructor (that sets the httpClient instance
// variable via \Drupal which we don't want to do), but keep the call to its
// parent class constructor. @see https://bugs.php.net/bug.php?id=42016
DataFetcherPluginBase::__construct($configuration, $plugin_id, $plugin_definition);
// This is what the parent class is doing, that we need to override.
$this->httpClient = NULL;
}
/**
* Override the parent::getAuthenticationPlugin()
*
* So we can mock the authentication plugin.
*
* A mocked authentication plugin.
*/
public function getAuthenticationPlugin(): AuthenticationPluginInterface {
if (!isset($this->authenticationPlugin)) {
$this->authenticationPlugin = $this->authenticator;
}
return $this->authenticationPlugin;
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\Plugin\migrate\process\ArrayPop;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the array pop process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\ArrayPop
*/
final class ArrayPopTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->plugin = new ArrayPop([], 'array_pop', []);
parent::setUp();
}
/**
* Data provider for testArrayPop().
*
* @return array
* An array containing input values and expected output values.
*/
public static function arrayPopDataProvider(): array {
return [
'indexed array' => [
'input' => ['v1', 'v2', 'v3'],
'expected_output' => 'v3',
],
'associative array' => [
'input' => ['i1' => 'v1', 'i2' => 'v2', 'i3' => 'v3'],
'expected_output' => 'v3',
],
'empty array' => [
'input' => [],
'expected_output' => NULL,
],
];
}
/**
* Test array pop plugin.
*
* @param array $input
* The input values.
* @param mixed $expected_output
* The expected output.
*
* @dataProvider arrayPopDataProvider
*/
public function testArrayPop(array $input, mixed $expected_output): void {
$output = $this->plugin->transform($input, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($output, $expected_output);
}
/**
* Test invalid input.
*/
public function testArrayPopFromString(): void {
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('Input should be an array.');
$this->plugin->transform('foo', $this->migrateExecutable, $this->row, 'destinationproperty');
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\Plugin\migrate\process\ArrayShift;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the array shift process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\ArrayShift
*/
final class ArrayShiftTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->plugin = new ArrayShift([], 'array_shift', []);
parent::setUp();
}
/**
* Data provider for testArrayShift().
*
* @return array
* An array containing input values and expected output values.
*/
public static function arrayShiftDataProvider(): array {
return [
'indexed array' => [
'input' => ['v1', 'v2', 'v3'],
'expected_output' => 'v1',
],
'associative array' => [
'input' => ['i1' => 'v1', 'i2' => 'v2', 'i3' => 'v3'],
'expected_output' => 'v1',
],
'empty array' => [
'input' => [],
'expected_output' => NULL,
],
];
}
/**
* Test array shift plugin.
*
* @param array $input
* The input values.
* @param mixed $expected_output
* The expected output.
*
* @dataProvider arrayShiftDataProvider
*/
public function testArrayShift(array $input, mixed $expected_output): void {
$output = $this->plugin->transform($input, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($output, $expected_output);
}
/**
* Test invalid input.
*/
public function testArrayShiftFromString(): void {
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('Input should be an array.');
$this->plugin->transform('foo', $this->migrateExecutable, $this->row, 'destinationproperty');
}
}

View File

@@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Row;
use Drupal\migrate_plus\Plugin\migrate\process\ArrayTemplate;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the array_template process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\ArrayTemplate
*/
final class ArrayTemplateTest extends MigrateProcessTestCase {
/**
* Test array_template plugin.
*
* @param mixed $input
* The input values.
* @param mixed $expected
* The expected output.
* @param array $configuration
* The configuration.
*
* @dataProvider providerTestArrayTemplate
*/
public function testArrayTemplate($input, $expected, array $configuration): void {
$plugin = new ArrayTemplate($configuration, 'array_template', []);
$output = $plugin->transform($input, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($expected, $output);
}
/**
* Data provider for testArrayTemplate().
*
* @return array
* An array containing input values and expected output values.
*/
public static function providerTestArrayTemplate(): array {
return [
'string wrap' => [
'input' => 'my_string',
'expected' => ['my_string'],
'configuration' => [
'template' => ['pipeline:'],
],
],
'array wrap' => [
'input' => ['v1', 'v2', 'v3'],
'expected' => [['v1', 'v2', 'v3']],
'configuration' => [
'template' => ['pipeline:'],
],
],
'string wrap with key' => [
'input' => 'my_string',
'expected' => ['string' => 'my_string'],
'configuration' => [
'template' => ['string' => 'pipeline:'],
],
],
'unrecognized prefix' => [
'input' => 'my_string',
'expected' => ['prefix:', 'another:prefix'],
'configuration' => [
'template' => ['prefix:', 'another:prefix'],
],
],
];
}
/**
* Test source and destination properties with the array_template plugin.
*
* Also test that complex template structure is preserved and that
* sub-properties can be accessed.
*/
public function testArrayTemplateSourceAndDestination(): void {
// Use a real Row object, not a mock.
$row = new Row();
$pipeline = [
'zero',
'one',
'two' => ['nested value'],
];
$row->setSourceProperty('some_field', [
'source value',
['value' => 'nested source value'],
]);
$row->setDestinationProperty('some_field', [
'destination value',
['value' => 'nested destination value'],
]);
$configuration = [
'template' => [
'literals' => [
'pipeline' => 'pipeline',
'source' => 'source',
'dest' => 'dest',
],
'full values' => [
'pipeline' => 'pipeline:',
'source' => 'source:some_field',
'dest' => 'dest:some_field',
],
'scalar values' => [
'zero' => 'pipeline:0',
'one' => 'pipeline:1',
'nested' => 'pipeline:two/0',
'source' => 'source:some_field/0',
'nested_source' => 'source:some_field/1/value',
'destination' => 'dest:some_field/0',
'nested_destination' => 'dest:some_field/1/value',
],
],
];
$expected = [
'literals' => [
'pipeline' => 'pipeline',
'source' => 'source',
'dest' => 'dest',
],
'full values' => [
'pipeline' => [
'zero',
'one',
'two' => ['nested value'],
],
'source' => [
'source value',
['value' => 'nested source value'],
],
'dest' => [
'destination value',
['value' => 'nested destination value'],
],
],
'scalar values' => [
'zero' => 'zero',
'one' => 'one',
'nested' => 'nested value',
'source' => 'source value',
'nested_source' => 'nested source value',
'destination' => 'destination value',
'nested_destination' => 'nested destination value',
],
];
$plugin = new ArrayTemplate($configuration, 'array_template', []);
$output = $plugin->transform($pipeline, $this->migrateExecutable, $row, 'destinationproperty');
$this->assertSame($expected, $output);
}
/**
* Test invalid configuration.
*
* @param array $configuration
* The configuration.
* @param string $message
* The exception message we expect.
*
* @dataProvider providerArrayTemplateConstructorExceptions
*/
public function testArrayTemplateConstructorExceptions(array $configuration, string $message): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage($message);
$plugin = new ArrayTemplate($configuration, 'array_template', []);
$plugin->transform(123, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Data provider for ::testArrayTemplateConstructorExceptions().
*/
public static function providerArrayTemplateConstructorExceptions(): array {
return [
[
'configuration' => [
'template' => 'not an array',
],
'message' => 'The "template" must be set to an array.',
],
];
}
}

View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Utility\Html;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate_plus\Plugin\migrate\process\DomApplyStyles;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the dom_apply_styles process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\DomApplyStyles
*/
final class DomApplyStylesTest extends MigrateProcessTestCase {
/**
* Example configuration for the dom_apply_styles process plugin.
*
* @var array
*/
protected $exampleConfiguration = [
'format' => 'test_format',
'rules' => [
[
'xpath' => '//b',
'style' => 'Bold',
],
[
'xpath' => '//span/i',
'style' => 'Italic',
'depth' => 1,
],
],
];
/**
* Mock a config factory object.
*/
protected ?object $configFactory = NULL;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
// Mock a config object.
$prophecy = $this->prophesize(ImmutableConfig::class);
$prophecy
->get('editor')
->willReturn('ckeditor');
$prophecy
->get('settings.plugins.stylescombo.styles')
->willReturn("strong.foo|Bold\r\nem.foo.bar|Italic\r\n");
$style_config = $prophecy->reveal();
// Mock the config factory.
$prophecy = $this->prophesize(ConfigFactory::class);
$prophecy
->get('editor.editor.test_format')
->willReturn($style_config);
$this->configFactory = $prophecy->reveal();
parent::setUp();
}
/**
* @covers ::__construct
*
* @dataProvider providerTestConfig
*/
public function testValidateRules(array $config_overrides, string $message): void {
$configuration = $config_overrides + $this->exampleConfiguration;
$value = '<p>A simple paragraph.</p>';
$this->expectException(InvalidPluginDefinitionException::class);
$this->expectExceptionMessage($message);
(new DomApplyStyles($configuration, 'dom_apply_styles', [], $this->configFactory))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Dataprovider for testValidateRules().
*/
public static function providerTestConfig(): array {
$cases = [
'format-empty' => [
['format' => ''],
'The "format" option must be a non-empty string.',
],
'format-not-string' => [
['format' => [1, 2, 3]],
'The "format" option must be a non-empty string.',
],
'rules-not-array' => [
['rules' => 'invalid'],
'The "rules" option must be an array.',
],
'xpath-null' => [
[
'rules' => [['xpath' => NULL, 'style' => 'Bold']],
],
'The "xpath" and "style" options are required for each rule.',
],
'style-invalid' => [
[
'rules' => [['xpath' => '//b', 'style' => 'invalid-style']],
],
'The style "invalid-style" is not defined.',
],
];
return $cases;
}
/**
* @covers ::transform
*/
public function testTransformInvalidInput(): void {
$value = 'string';
$this->expectException(MigrateSkipRowException::class);
$this->expectExceptionMessage('The dom_apply_styles plugin in the destinationproperty process pipeline requires a \DOMDocument object. You can use the dom plugin to convert a string to \DOMDocument.');
(new DomApplyStyles($this->exampleConfiguration, 'dom_apply_styles', [], $this->configFactory))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::transform
*/
public function testTransform(): void {
$input_string = '<div><span><b>Bold text</b></span><span><i>Italic text</i></span></div>';
$output_string = '<div><span><strong class="foo">Bold text</strong></span><em class="foo bar">Italic text</em></div>';
$value = Html::load($input_string);
$document = (new DomApplyStyles($this->exampleConfiguration, 'dom_apply_styles', [], $this->configFactory))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertTrue($document instanceof \DOMDocument);
$this->assertEquals($output_string, Html::serialize($document));
}
}

View File

@@ -0,0 +1,193 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Utility\Html;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Plugin\MigrateProcessInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_plus\Plugin\migrate\process\DomMigrationLookup;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the dom_migration_lookup process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\DomMigrationLookup
*/
final class DomMigrationLookupTest extends MigrateProcessTestCase {
/**
* Example configuration for the dom_migration_lookup process plugin.
*
* @var array
*/
protected $exampleConfiguration = [
'plugin' => 'dom_migration_lookup',
'mode' => 'attribute',
'xpath' => '//a',
'attribute_options' => [
'name' => 'href',
],
'search' => '@/user/(\d+)@',
'replace' => '/user/[mapped-id]',
'migrations' => [
'users' => [],
'people' => [
'replace' => '/people/[mapped-id]',
],
],
];
/**
* Mock a migration.
*
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected object $migration;
/**
* Mock a process plugin manager.
*
* @var \Drupal\migrate\Plugin\MigratePluginManagerInterface
*/
protected object $processPluginManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Mock a migration.
$prophecy = $this->prophesize(MigrationInterface::class);
$this->migration = $prophecy->reveal();
// Mock two migration lookup plugins.
$prophecy = $this->prophesize(MigrateProcessInterface::class);
$prophecy
->transform('123', $this->migrateExecutable, $this->row, 'destinationproperty')
->willReturn('321');
$prophecy
->transform('456', $this->migrateExecutable, $this->row, 'destinationproperty')
->willReturn(NULL);
$users_lookup_plugin = $prophecy->reveal();
$prophecy = $this->prophesize(MigrateProcessInterface::class);
$prophecy
->transform('123', $this->migrateExecutable, $this->row, 'destinationproperty')
->willReturn('ignored');
$prophecy
->transform('456', $this->migrateExecutable, $this->row, 'destinationproperty')
->willReturn('654');
$people_lookup_plugin = $prophecy->reveal();
// Mock a process plugin manager.
$prophecy = $this->prophesize(MigratePluginManager::class);
$users_configuration = [
'migration' => 'users',
'no_stub' => TRUE,
];
$people_configuration = [
'migration' => 'people',
'no_stub' => TRUE,
];
$prophecy
->createInstance('migration_lookup', $users_configuration, $this->migration)
->willReturn($users_lookup_plugin);
$prophecy
->createInstance('migration_lookup', $people_configuration, $this->migration)
->willReturn($people_lookup_plugin);
$this->processPluginManager = $prophecy->reveal();
}
/**
* @covers ::__construct
*
* @dataProvider providerTestConfigValidation
*/
public function testConfigValidation(array $config_overrides, string $message): void {
$configuration = $config_overrides + $this->exampleConfiguration;
$value = '<p>A simple paragraph.</p>';
$this->expectException(InvalidPluginDefinitionException::class);
$this->expectExceptionMessage($message);
(new DomMigrationLookup($configuration, 'dom_migration_lookup', [], $this->migration, $this->processPluginManager))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Dataprovider for testConfigValidation().
*/
public static function providerTestConfigValidation(): array {
$cases = [
'migrations-empty' => [
['migrations' => []],
"Configuration option 'migration' is required.",
],
'migrations-invalid' => [
['migrations' => 42],
"Configuration option 'migration' should be a keyed array.",
],
'replace-null' => [
['replace' => NULL],
"Please define either a global replace for all migrations, or a specific one for 'migrations.users'.",
],
];
return $cases;
}
/**
* @covers ::transform
*/
public function testTransformInvalidInput(): void {
$value = 'string';
$this->expectException(MigrateSkipRowException::class);
$this->expectExceptionMessage('The dom_migration_lookup plugin in the destinationproperty process pipeline requires a \DOMDocument object. You can use the dom plugin to convert a string to \DOMDocument.');
(new DomMigrationLookup($this->exampleConfiguration, 'dom_migration_lookup', [], $this->migration, $this->processPluginManager))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::transform
*
* @dataProvider providerTestTransform
*/
public function testTransform($config_overrides, $input_string, $output_string): void {
$configuration = $config_overrides + $this->exampleConfiguration;
$value = Html::load($input_string);
$document = (new DomMigrationLookup($configuration, 'dom_migration_lookup', [], $this->migration, $this->processPluginManager))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertTrue($document instanceof \DOMDocument);
$this->assertEquals($output_string, Html::serialize($document));
}
/**
* Dataprovider for testTransform().
*/
public static function providerTestTransform(): array {
$cases = [
'users-migration' => [
[],
'<a href="/user/123">text</a>',
'<a href="/user/321">text</a>',
],
'people-migration' => [
[],
'<a href="https://www.example.com/user/456">text</a>',
'<a href="https://www.example.com/people/654">text</a>',
],
'no-match' => [
['search' => '@www\.mysite\.com/user/(\d+)@'],
'<a href="https://www.example.com/user/456">text</a>',
'<a href="https://www.example.com/user/456">text</a>',
],
];
return $cases;
}
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\Component\Utility\Html;
use Drupal\migrate_plus\Plugin\migrate\process\DomRemove;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the dom_remove process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\DomRemove
*/
final class DomRemoveTest extends MigrateProcessTestCase {
/**
* @covers ::transform
*
* @dataProvider providerTestTransform
*/
public function testTransform($input_string, $configuration, $output_string): void {
$value = Html::load($input_string);
$document = (new DomRemove($configuration, 'dom_remove', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertTrue($document instanceof \DOMDocument);
$this->assertEquals($output_string, Html::serialize($document));
}
/**
* Dataprovider for testTransform().
*/
public static function providerTestTransform(): array {
$input_string = '<ul><li>Item 1</li><li>Item 2</li><li><ul><li>Item 3.1</li><li>Item 3.2</li></ul></li><li>Item 4</li><li>Item 5</li></ul>';
$attribute_input_string = '<div style="font-size:15px;"><a class="btn-lg" href="#" style="padding: 10px;">Button</a><p class="lead-paragraph">Testing</p></div>';
$cases = [
'any li, no limit' => [
$input_string,
['selector' => '//li'],
'<ul></ul>',
],
'any li, limit 3' => [
$input_string,
['selector' => '//li', 'limit' => 3],
'<ul><li>Item 4</li><li>Item 5</li></ul>',
],
'any li, limit 4' => [
$input_string,
['selector' => '//li', 'limit' => 4],
// The fourth match is Item 3.1.
'<ul><li>Item 4</li><li>Item 5</li></ul>',
],
'top-level li, limit 4' => [
$input_string,
// Both Html::load() and the dom process plugin wrap HTML snippets in
// <html> and <body> tags.
['selector' => '/html/body/ul/li', 'limit' => 4],
'<ul><li>Item 5</li></ul>',
],
'nested li, no limit' => [
$input_string,
['selector' => '//li//li'],
'<ul><li>Item 1</li><li>Item 2</li><li><ul></ul></li><li>Item 4</li><li>Item 5</li></ul>',
],
'nested li, limit 1' => [
$input_string,
['selector' => '//li//li', 'limit' => 1],
'<ul><li>Item 1</li><li>Item 2</li><li><ul><li>Item 3.2</li></ul></li><li>Item 4</li><li>Item 5</li></ul>',
],
'specific item' => [
$input_string,
['selector' => '//li[text() = "Item 3.1"]'],
'<ul><li>Item 1</li><li>Item 2</li><li><ul><li>Item 3.2</li></ul></li><li>Item 4</li><li>Item 5</li></ul>',
],
'all items with sub-lists' => [
$input_string,
['selector' => '//li[./ul]'],
'<ul><li>Item 1</li><li>Item 2</li><li>Item 4</li><li>Item 5</li></ul>',
],
'attribute, no limit' => [
$attribute_input_string,
['selector' => '//*[@style]', 'mode' => 'attribute', 'attribute' => 'style'],
'<div><a class="btn-lg" href="#">Button</a><p class="lead-paragraph">Testing</p></div>',
],
'attribute, limit 1' => [
$attribute_input_string,
['selector' => '//*[@style]', 'mode' => 'attribute', 'attribute' => 'style', 'limit' => 1],
'<div><a class="btn-lg" href="#" style="padding: 10px;">Button</a><p class="lead-paragraph">Testing</p></div>',
],
'attribute in specific tag' => [
$attribute_input_string,
['selector' => '//p[@class]', 'mode' => 'attribute', 'attribute' => 'class'],
'<div style="font-size:15px;"><a class="btn-lg" href="#" style="padding: 10px;">Button</a><p>Testing</p></div>',
],
'attribute not found' => [
$attribute_input_string,
['selector' => 'p[@class]', 'mode' => 'attribute', 'attribute' => 'data-test'],
$attribute_input_string,
],
];
return $cases;
}
/**
* Tests running remove attribute without specifying an attribute to remove.
*/
public function testMissingConfiguration(): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The "attribute" must be set if "mode" is set to "attribute".');
(new DomRemove(['selector' => 'p', 'mode' => 'attribute'], 'dom_remove', []));
}
}

View File

@@ -0,0 +1,118 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\Component\Utility\Html;
use Drupal\migrate_plus\Plugin\migrate\process\DomSelect;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the dom_select process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\DomSelect
*/
final class DomSelectTest extends MigrateProcessTestCase {
/**
* @covers ::transform
*
* @dataProvider providerTestTransform
*/
public function testTransform(string $input_string, array $configuration, array $output_array): void {
$value = Html::load($input_string);
$elements = (new DomSelect($configuration, 'dom_select', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($output_array, $elements);
}
/**
* Dataprovider for testTransform().
*/
public static function providerTestTransform(): array {
$lists = '<ul><li>Item 1</li><li>Item 2</li><li><ul><li>Item 3.1</li><li>Item 3.2</li></ul></li><li>Item 4</li><li>Item 5</li></ul>';
$image = '<p>A broken image: <img src="https://www.example.com/img/foo.jpg" alt="metasyntactic image" /></p>';
$cases = [
'any li, no limit' => [
$lists,
['selector' => '//li'],
[
'Item 1',
'Item 2',
'Item 3.1Item 3.2',
'Item 3.1',
'Item 3.2',
'Item 4',
'Item 5',
],
],
'any li, limit 3' => [
$lists,
['selector' => '//li', 'limit' => 3],
[
'Item 1',
'Item 2',
'Item 3.1Item 3.2',
],
],
'any li, limit 4' => [
$lists,
['selector' => '//li', 'limit' => 4],
// The fourth match is Item 3.1.
[
'Item 1',
'Item 2',
'Item 3.1Item 3.2',
'Item 3.1',
],
],
'top-level li, limit 4' => [
$lists,
// Both Html::load() and the dom process plugin wrap HTML snippets in
// <html> and <body> tags.
['selector' => '/html/body/ul/li', 'limit' => 4],
[
'Item 1',
'Item 2',
'Item 3.1Item 3.2',
'Item 4',
],
],
'nested li, no limit' => [
$lists,
['selector' => '//li//li'],
[
'Item 3.1',
'Item 3.2',
],
],
'nested li, limit 1' => [
$lists,
['selector' => '//li//li', 'limit' => 1],
[
'Item 3.1',
],
],
'image src attribute' => [
$image,
['selector' => '//img/@src', 'limit' => 1],
[
'https://www.example.com/img/foo.jpg',
],
],
'image src or alt attribute' => [
$image,
['selector' => '//p/img/@src|/html/body/p/img/@alt'],
[
'https://www.example.com/img/foo.jpg',
'metasyntactic image',
],
],
];
return $cases;
}
}

View File

@@ -0,0 +1,147 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Utility\Html;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate_plus\Plugin\migrate\process\DomStrReplace;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the dom_str_replace process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\DomStrReplace
*/
final class DomStrReplaceTest extends MigrateProcessTestCase {
/**
* Example configuration for the dom_str_replace process plugin.
*
* @var array
*/
private static $exampleConfiguration = [
'mode' => 'attribute',
'xpath' => '//a',
'attribute_options' => [
'name' => 'href',
],
'search' => 'foo',
'replace' => 'bar',
];
/**
* @covers ::__construct
*
* @dataProvider providerTestConfigEmpty
*/
public function testConfigValidation(array $config_overrides, string $message): void {
$configuration = $config_overrides + self::$exampleConfiguration;
$value = '<p>A simple paragraph.</p>';
$this->expectException(InvalidPluginDefinitionException::class);
$this->expectExceptionMessage($message);
(new DomStrReplace($configuration, 'dom_str_replace', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Dataprovider for testConfigValidation().
*/
public static function providerTestConfigEmpty(): array {
$cases = [
'xpath-null' => [
['xpath' => NULL],
"Configuration option 'xpath' is required.",
],
'mode-null' => [
['mode' => NULL],
"Configuration option 'mode' is required.",
],
'mode-invalid' => [
['mode' => 'invalid'],
'Configuration option "mode" only accepts the following values: attribute, element.',
],
'attribute_options-null' => [
['attribute_options' => NULL],
"Configuration option 'attribute_options' is required for mode 'attribute'.",
],
'search-null' => [
['search' => NULL],
"Configuration option 'search' is required.",
],
'replace-null' => [
['replace' => NULL],
"Configuration option 'replace' is required.",
],
];
return $cases;
}
/**
* @covers ::transform
*/
public function testTransformInvalidInput(): void {
$configuration = [
'xpath' => '//a',
'mode' => 'attribute',
'attribute_options' => [
'name' => 'href',
],
'search' => 'foo',
'replace' => 'bar',
];
$value = 'string';
$this->expectException(MigrateSkipRowException::class);
$this->expectExceptionMessage('The dom_str_replace plugin in the destinationproperty process pipeline requires a \DOMDocument object. You can use the dom plugin to convert a string to \DOMDocument.');
(new DomStrReplace($configuration, 'dom_str_replace', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::transform
*
* @dataProvider providerTestTransform
*/
public function testTransform(string $input_string, array $configuration, string $output_string): void {
$value = Html::load($input_string);
$document = (new DomStrReplace($configuration, 'dom_str_replace', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertTrue($document instanceof \DOMDocument);
$this->assertEquals($output_string, Html::serialize($document));
}
/**
* Dataprovider for testTransform().
*/
public static function providerTestTransform(): array {
$cases = [
'string:case_sensitive' => [
'<a href="/foo/Foo/foo">text</a>',
self::$exampleConfiguration,
'<a href="/bar/Foo/bar">text</a>',
],
'string:case_insensitive' => [
'<a href="/foo/Foo/foo">text</a>',
[
'case_insensitive' => TRUE,
] + self::$exampleConfiguration,
'<a href="/bar/bar/bar">text</a>',
],
'regex' => [
'<a href="/foo/Foo/foo">text</a>',
[
'search' => '/(.)\1/',
'regex' => TRUE,
] + self::$exampleConfiguration,
'<a href="/fbar/Fbar/fbar">text</a>',
],
];
return $cases;
}
}

View File

@@ -0,0 +1,130 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\Component\Utility\Html;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\Plugin\migrate\process\Dom;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the dom process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\Dom
*/
final class DomTest extends MigrateProcessTestCase {
/**
* @covers ::__construct
*/
public function testConfigMethodEmpty(): void {
$configuration = [];
$value = '<p>A simple paragraph.</p>';
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The "method" must be set.');
(new Dom($configuration, 'dom', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::__construct
*/
public function testConfigMethodInvalid(): void {
$configuration = [];
$configuration['method'] = 'invalid';
$value = '<p>A simple paragraph.</p>';
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The "method" must be "import" or "export".');
(new Dom($configuration, 'dom', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::__construct
*/
public function testInvalidImportMethod(): void {
$configuration['method'] = 'import';
$configuration['import_method'] = 'invalid';
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The "import_method" must be "html", "html5", or "xml".');
(new Dom($configuration, 'dom', []));
}
/**
* @covers ::import
*/
public function testImportNonRoot(): void {
$configuration = [];
$configuration['method'] = 'import';
$value = '<p>A simple paragraph.</p>';
$document = (new Dom($configuration, 'dom', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertTrue($document instanceof \DOMDocument);
}
/**
* @covers ::import
*/
public function testImportMethodHtml5(): void {
$configuration['method'] = 'import';
$configuration['import_method'] = 'html5';
$value = '<p>A simple paragraph.</p>';
$document = (new Dom($configuration, 'dom', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertTrue($document instanceof \DOMDocument);
}
/**
* @covers ::import
*/
public function testImportMethodXml(): void {
$configuration['method'] = 'import';
$configuration['import_method'] = 'xml';
$value = '<item><value>A simple paragraph.</value></item>';
$document = (new Dom($configuration, 'dom', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertTrue($document instanceof \DOMDocument);
}
/**
* @covers ::import
*/
public function testImportNonRootInvalidInput(): void {
$configuration = [];
$configuration['method'] = 'import';
$value = [1, 1];
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('Cannot import a non-string value.');
(new Dom($configuration, 'dom', []))
->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::export
*/
public function testExportNonRoot(): void {
$configuration = [];
$configuration['method'] = 'export';
$partial = '<p>A simple paragraph.</p>';
$document = Html::load($partial);
$value = (new Dom($configuration, 'dom', []))
->transform($document, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, $partial);
}
/**
* @covers ::export
*/
public function testExportNonRootInvalidInput(): void {
$configuration = [];
$configuration['method'] = 'export';
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('Cannot export a "string".');
(new Dom($configuration, 'dom', []))
->transform('string is not DOMDocument', $this->migrateExecutable, $this->row, 'destinationproperty');
}
}

View File

@@ -0,0 +1,204 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate\MigrateSkipProcessException;
use Drupal\migrate\Row;
use Drupal\migrate_plus\Plugin\migrate\process\Gate;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the gate process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\Gate
*/
final class GateTest extends MigrateProcessTestCase {
/**
* Test Gate plugin.
*
* @dataProvider gateProvider
*/
public function testGate($row_data, $destination_data, $configuration, $message): void {
$row = new Row($row_data);
if (!empty($destination_data)) {
foreach ($destination_data as $key => $val) {
$row->setDestinationProperty($key, $val);
}
}
if (!empty($message)) {
$this->expectException(MigrateSkipProcessException::class);
$this->expectExceptionMessage($message);
}
$plugin = new Gate($configuration, 'gate', []);
$value = $row_data[$configuration['source']];
$transformed = $plugin->transform($value, $this->migrateExecutable, $row, 'destinationproperty');
if (empty($message)) {
$this->assertSame($value, $transformed);
}
}
/**
* Row and plugin configuration for tests.
*/
public static function gateProvider(): array {
return [
'Gate does not unlock' => [
[
'state_abbr' => 'MO',
'source_data' => 'Let me through!',
],
NULL,
[
'source' => 'source_data',
'use_as_key' => 'state_abbr',
'valid_keys' => 'CO',
'key_direction' => 'unlock',
],
'Processing of destination property destinationproperty was skipped: Gate was not unlocked by property state_abbr with value MO.',
],
'Gate unlocks (with valid_keys array)' => [
[
'state_abbr' => 'Colorado',
'source_data' => 'Let me through!',
],
NULL,
[
'source' => 'source_data',
'use_as_key' => 'state_abbr',
'valid_keys' => ['CO', 'Colorado'],
'key_direction' => 'unlock',
],
NULL,
],
'Gate locks' => [
[
'state_abbr' => 'CO',
'source_data' => 'Let me through!',
],
NULL,
[
'source' => 'source_data',
'use_as_key' => 'state_abbr',
'valid_keys' => 'CO',
'key_direction' => 'lock',
],
'Processing of destination property destinationproperty was skipped: Gate was locked by property state_abbr with value CO.',
],
'Gate stays unlocked' => [
[
'state_abbr' => 'MO',
'source_data' => 'Let me through!',
],
NULL,
[
'source' => 'source_data',
'use_as_key' => 'state_abbr',
'valid_keys' => 'CO',
'key_direction' => 'lock',
],
NULL,
],
'Destination prop does not unlock gate' => [
['source_data' => 'Let me through!'],
['state_abbr' => 'MO'],
[
'source' => 'source_data',
'use_as_key' => '@state_abbr',
'valid_keys' => 'CO',
'key_direction' => 'unlock',
],
'Processing of destination property destinationproperty was skipped: Gate was not unlocked by property @state_abbr with value MO.',
],
'Destination prop unlocks gate' => [
['source_data' => 'Let me through!'],
['state_abbr' => 'CO'],
[
'source' => 'source_data',
'use_as_key' => '@state_abbr',
'valid_keys' => 'CO',
'key_direction' => 'unlock',
],
NULL,
],
'Destination prop locks gate' => [
['source_data' => 'Let me through!'],
['state_abbr' => 'CO'],
[
'source' => 'source_data',
'use_as_key' => '@state_abbr',
'valid_keys' => 'CO',
'key_direction' => 'lock',
],
'Processing of destination property destinationproperty was skipped: Gate was locked by property @state_abbr with value CO.',
],
'Gate stays unlocked with destination prop' => [
['source_data' => 'Let me through!'],
['state_abbr' => 'MO'],
[
'source' => 'source_data',
'use_as_key' => '@state_abbr',
'valid_keys' => 'CO',
'key_direction' => 'lock',
],
NULL,
],
];
}
/**
* Test Gate plugin with bad configuration.
*
* @dataProvider badConfigurationProvider
*/
public function testGateBadConfiguration($configuration, string $message): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage($message);
new Gate($configuration, 'gate', []);
}
/**
* Provider for bad configuration.
*/
public static function badConfigurationProvider(): array {
return [
'Missing use_as_key' => [
[
'source' => 'source_data',
'valid_keys' => 'CO',
'key_direction' => 'unlock',
],
'Gate plugin is missing use_as_key configuration.',
],
'Missing valid_keys' => [
[
'source' => 'source_data',
'use_as_key' => 'state_abbr',
'key_direction' => 'unlock',
],
'Gate plugin is missing valid_keys configuration.',
],
'Missing key_direction' => [
[
'source' => 'source_data',
'use_as_key' => 'state_abbr',
'valid_keys' => 'CO',
],
'Gate plugin is missing key_direction configuration.',
],
'Invalid key_direction' => [
[
'source' => 'source_data',
'use_as_key' => 'state_abbr',
'valid_keys' => 'CO',
'key_direction' => 'open',
],
'Gate plugin only accepts the following values for key_direction: lock and unlock.',
],
];
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate_plus\Plugin\migrate\process\MultipleValues;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\MultipleValues
* @group migrate
*/
final class MultipleValuesTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->plugin = new MultipleValues([], 'multiple_values', []);
parent::setUp();
}
/**
* Test input treated as multiple value output.
*/
public function testTreatAsMultiple(): void {
$value = ['v1', 'v2', 'v3'];
$output = $this->plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($output, $value);
$this->assertTrue($this->plugin->multiple());
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate_plus\Plugin\migrate\process\SingleValue;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\SingleValue
* @group migrate
*/
final class SingleValueTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->plugin = new SingleValue([], 'single_value', []);
parent::setUp();
}
/**
* Test input treated as single value output.
*/
public function testTreatAsSingle(): void {
$value = ['v1', 'v2', 'v3'];
$output = $this->plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($output, $value);
$this->assertFalse($this->plugin->multiple());
}
}

View File

@@ -0,0 +1,163 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate\MigrateSkipProcessException;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate_plus\Plugin\migrate\process\SkipOnValue;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the skip on value process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\SkipOnValue
*/
final class SkipOnValueTest extends MigrateProcessTestCase {
/**
* @covers ::process
*/
public function testProcessSkipsOnValue(): void {
$configuration = [];
$configuration['method'] = 'process';
$configuration['value'] = 86;
$this->expectException(MigrateSkipProcessException::class);
(new SkipOnValue($configuration, 'skip_on_value', []))
->transform('86', $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::process
*/
public function testProcessSkipsOnMultipleValue(): void {
$configuration = [];
$configuration['method'] = 'process';
$configuration['value'] = [1, 1, 2, 3, 5, 8];
$this->expectException(MigrateSkipProcessException::class);
(new SkipOnValue($configuration, 'skip_on_value', []))
->transform('5', $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::process
*/
public function testProcessBypassesOnNonValue(): void {
$configuration = [];
$configuration['method'] = 'process';
$configuration['value'] = 'sourcevalue';
$configuration['not_equals'] = TRUE;
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform('sourcevalue', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, 'sourcevalue');
$configuration['value'] = 86;
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform('86', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, '86');
}
/**
* @covers ::process
*/
public function testProcessSkipsOnMultipleNonValue(): void {
$configuration = [];
$configuration['method'] = 'process';
$configuration['value'] = [1, 1, 2, 3, 5, 8];
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform(4, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, '4');
}
/**
* @covers ::process
*/
public function testProcessBypassesOnMultipleNonValue(): void {
$configuration = [];
$configuration['method'] = 'process';
$configuration['value'] = [1, 1, 2, 3, 5, 8];
$configuration['not_equals'] = TRUE;
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform(5, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, '5');
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform(1, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, '1');
}
/**
* @covers ::row
*/
public function testRowBypassesOnMultipleNonValue(): void {
$configuration = [];
$configuration['method'] = 'row';
$configuration['value'] = [1, 1, 2, 3, 5, 8];
$configuration['not_equals'] = TRUE;
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform(5, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, '5');
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform(1, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, '1');
}
/**
* @covers ::row
*/
public function testRowSkipsOnValue(): void {
$configuration = [];
$configuration['method'] = 'row';
$configuration['value'] = 86;
$this->expectException(MigrateSkipRowException::class);
(new SkipOnValue($configuration, 'skip_on_value', []))
->transform('86', $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Tests that a skip row exception with a message is raised.
*
* @covers ::row
*/
public function testRowSkipWithMessage(): void {
$configuration = [
'method' => 'row',
'value' => 86,
'message' => 'The value is 86',
];
$process = new SkipOnValue($configuration, 'skip_on_value', []);
$this->expectException(MigrateSkipRowException::class);
$this->expectExceptionMessage('The value is 86');
$process->transform(86, $this->migrateExecutable, $this->row, 'destination_property');
}
/**
* @covers ::row
*/
public function testRowBypassesOnNonValue(): void {
$configuration = [];
$configuration['method'] = 'row';
$configuration['value'] = 'sourcevalue';
$configuration['not_equals'] = TRUE;
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform('sourcevalue', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, 'sourcevalue');
$configuration['value'] = 86;
$value = (new SkipOnValue($configuration, 'skip_on_value', []))
->transform('86', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($value, 86);
}
/**
* @covers ::__construct
*/
public function testRequiredConfiguration(): void {
$configuration = [];
// It doesn't meter which method we will put here, because it should throw
// error on contraction of Plugin.
$configuration['method'] = 'row';
$this->expectException(\InvalidArgumentException::class);
(new SkipOnValue($configuration, 'skip_on_value', []));
}
}

View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate_plus\Plugin\migrate\process\StrReplace;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the str replace process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\StrReplace
*/
final class StrReplaceTest extends MigrateProcessTestCase {
/**
* Test for a simple str_replace string.
*/
public function testStrReplace(): void {
$configuration = [];
$value = 'vero eos et accusam et justo vero';
$configuration['search'] = 'et';
$configuration['replace'] = 'that';
$plugin = new StrReplace($configuration, 'str_replace', []);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame('vero eos that accusam that justo vero', $actual);
}
/**
* Test for a simple str_replace given NULL.
*/
public function testStrReplaceNull(): void {
$configuration = [];
$value = NULL;
$configuration['search'] = '';
$configuration['replace'] = 'that';
$plugin = new StrReplace($configuration, 'str_replace', []);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame('', $actual);
}
/**
* Test for a simple str_replace given int 1.
*/
public function testStrReplaceInt(): void {
$configuration = [];
$value = 1;
$configuration['search'] = '1';
$configuration['replace'] = 'that';
$plugin = new StrReplace($configuration, 'str_replace', []);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame('that', $actual);
}
/**
* Test for a simple str_replace given TRUE.
*/
public function testStrReplaceTrue(): void {
$configuration = [];
$value = TRUE;
$configuration['search'] = '1';
$configuration['replace'] = 'that';
$plugin = new StrReplace($configuration, 'str_replace', []);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame('that', $actual);
}
/**
* Test for a simple str_replace given FALSE.
*/
public function testStrReplaceFalse(): void {
$configuration = [];
$value = FALSE;
$configuration['search'] = '';
$configuration['replace'] = 'that';
$plugin = new StrReplace($configuration, 'str_replace', []);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame('', $actual);
}
/**
* Test for case-insensitive searches.
*/
public function testStrIreplace(): void {
$configuration = [];
$value = 'VERO eos et accusam et justo vero';
$configuration['search'] = 'vero';
$configuration['replace'] = 'that';
$configuration['case_insensitive'] = TRUE;
$plugin = new StrReplace($configuration, 'str_replace', []);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame('that eos et accusam et justo that', $actual);
}
/**
* Test for regular expressions.
*/
public function testPregReplace(): void {
$configuration = [];
$value = 'vero eos et 123 accusam et justo 123 duo';
$configuration['search'] = '/[0-9]{3}/';
$configuration['replace'] = 'the';
$configuration['regex'] = TRUE;
$plugin = new StrReplace($configuration, 'str_replace', []);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame('vero eos et the accusam et justo the duo', $actual);
}
/**
* Test for InvalidArgumentException for "search" configuration.
*/
public function testSearchInvalidArgumentException(): void {
$configuration = [];
$configuration['replace'] = 'that';
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The "search" must be set.');
new StrReplace($configuration, 'str_replace', []);
}
/**
* Test for InvalidArgumentException for "replace" configuration.
*/
public function testReplaceInvalidArgumentException(): void {
$configuration = [];
$configuration['search'] = 'et';
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The "replace" must be set.');
new StrReplace($configuration, 'str_replace', []);
}
/**
* Test for multiple.
*/
public function testIsMultiple(): void {
$configuration = [];
$value = [
'vero eos et accusam et justo vero',
'et eos vero accusam vero justo et',
];
$expected = [
'vero eos that accusam that justo vero',
'that eos vero accusam vero justo that',
];
$configuration['search'] = 'et';
$configuration['replace'] = 'that';
$plugin = new StrReplace($configuration, 'str_replace', []);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($expected, $actual);
$this->assertTrue($plugin->multiple());
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_plus\Unit\process;
use Drupal\migrate_plus\Plugin\migrate\process\Transpose;
use Drupal\Tests\migrate\Unit\process\MigrateProcessTestCase;
/**
* Tests the transpose process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\Transpose
*/
final class TransposeTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->plugin = new Transpose([], 'array_pop', []);
parent::setUp();
}
/**
* Test transpose plugin.
*
* @param array $input
* The input values.
* @param mixed $expected_output
* The expected output.
*
* @dataProvider transposeDataProvider
*/
public function testTranspose(array $input, array $expected_output): void {
$output = $this->plugin->transform($input, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($output, $expected_output);
}
/**
* Data provider for testTranspose().
*
* @return array
* An array containing input values and expected output values.
*/
public static function transposeDataProvider(): array {
return [
'empty array' => [
'input' => [],
'expected_output' => [],
],
'simple array' => [
'input' => [1, 2, 3],
'expected_output' => [[1, 2, 3]],
],
'image files and alt text' => [
'input' => [
['2.png', '3.png', '5.png', '7.png'],
['two', 'three', 'five', 'seven'],
],
'expected_output' => [
['2.png', 'two'],
['3.png', 'three'],
['5.png', 'five'],
['7.png', 'seven'],
],
],
'indexed arrays' => [
'input' => [
['a' => 1, 'b' => 2],
['c' => 3, 'd' => 4],
['e' => 5, 'f' => 6],
['g' => 7, 'h' => 8],
],
'expected_output' => [[1, 3, 5, 7], [2, 4, 6, 8]],
],
];
}
}