ContentExportTest.php
Namespace
Drupal\FunctionalTests\DefaultContentFile
-
core/
tests/ Drupal/ FunctionalTests/ DefaultContent/ ContentExportTest.php
View source
<?php
declare (strict_types=1);
namespace Drupal\FunctionalTests\DefaultContent;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\DefaultContent\ContentExportCommand;
use Drupal\Core\DefaultContent\Exporter;
use Drupal\Core\DefaultContent\Finder;
use Drupal\Core\DefaultContent\Importer;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\FunctionalTests\Core\Recipe\RecipeTestTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\UserInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
/**
* Tests exporting content in YAML format.
*/
class ContentExportTest extends BrowserTestBase {
use RecipeTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Scans for content in the fixture.
*/
private readonly Finder $finder;
/**
* The directory where the default content is located.
*/
private readonly string $contentDir;
/**
* The user account which is doing the content import and export.
*/
private readonly UserInterface $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() : void {
parent::setUp();
// Apply the recipe that sets up the fields and configuration for our
// default content.
$fixtures_dir = $this->getDrupalRoot() . '/core/tests/fixtures';
$this->applyRecipe($fixtures_dir . '/recipes/default_content_base');
// We need an administrative user to import and export content.
$this->adminUser = $this->setUpCurrentUser(admin: TRUE);
// Import all of the default content from the fixture.
$this->contentDir = $fixtures_dir . '/default_content';
$this->finder = new Finder($this->contentDir);
$this->assertNotEmpty($this->finder->data);
$this->container
->get(Importer::class)
->importContent($this->finder);
}
/**
* Ensures that all imported content can be exported properly.
*/
public function testExportContent() : void {
// We should get an error if we try to export a non-existent entity type.
$process = $this->runDrupalCommand([
'content:export',
'camels',
42,
'--no-ansi',
]);
$this->assertSame(1, $process->wait());
$this->assertStringContainsString('The entity type "camels" does not exist.', $process->getOutput());
// We should get an error if we try to export a non-existent entity.
$process = $this->runDrupalCommand([
'content:export',
'taxonomy_term',
42,
'--no-ansi',
]);
$this->assertSame(1, $process->wait());
$this->assertStringContainsString('taxonomy_term 42 does not exist.', $process->getOutput());
// We should get an error if we try to export a config entity.
$process = $this->runDrupalCommand([
'content:export',
'taxonomy_vocabulary',
'tags',
'--no-ansi',
]);
$this->assertSame(1, $process->wait());
$this->assertStringContainsString('taxonomy_vocabulary is not a content entity type.', $process->getOutput());
$entity_repository = $this->container
->get(EntityRepositoryInterface::class);
foreach ($this->finder->data as $uuid => $imported_data) {
$entity_type_id = $imported_data['_meta']['entity_type'];
$entity = $entity_repository->loadEntityByUuid($entity_type_id, $uuid);
$this->assertInstanceOf(ContentEntityInterface::class, $entity);
$process = $this->runDrupalCommand([
'content:export',
$entity->getEntityTypeId(),
$entity->id(),
]);
// The export should succeed without error.
$this->assertSame(0, $process->wait(), $process->getErrorOutput());
// The path is added by the importer and is never exported.
unset($imported_data['_meta']['path']);
// The output should be identical to the imported data. Sort recursively
// by key to prevent false negatives.
$exported_data = Yaml::decode($process->getOutput());
// If the entity is a file, the file URI might vary slightly -- i.e., if
// the file already existed, the imported one would have been renamed. We
// need to account for that.
if ($entity->getEntityTypeId() === 'file') {
$imported_uri = $entity->getFileUri();
$extension = strlen('.' . pathinfo($imported_uri, PATHINFO_EXTENSION));
$imported_uri = substr($imported_uri, 0, -$extension);
$exported_uri = substr($exported_data['default']['uri'][0]['value'], 0, -$extension);
$this->assertStringStartsWith($imported_uri, $exported_uri);
// We know they match; no need to consider them further.
unset($exported_data['default']['uri'][0]['value'], $imported_data['default']['uri'][0]['value']);
}
// This specific node is special -- it is always reassigned to the current
// user during import, because its owner does not exist. Therefore, the
// current user is who it should be referring to when exported.
if ($uuid === '7f1dd75a-0be2-4d3b-be5d-9d1a868b9267') {
$new_owner = $this->adminUser
->uuid();
$exported_data['_meta']['depends'] = $imported_data['_meta']['depends'] = [
$new_owner => 'user',
];
$exported_data['default']['uid'][0]['entity'] = $imported_data['default']['uid'][0]['entity'] = $new_owner;
}
self::recursiveSortByKey($exported_data);
self::recursiveSortByKey($imported_data);
$this->assertSame($imported_data, $exported_data);
}
}
/**
* Tests that an exported user account can be logged in with after import.
*/
public function testExportedPasswordIsPreserved() : void {
$account = $this->createUser();
$this->assertNotEmpty($account->passRaw);
// Export the account to temporary file.
$process = $this->runDrupalCommand([
'content:export',
'user',
$account->id(),
]);
$this->assertSame(0, $process->wait());
$dir = 'public://content';
mkdir($dir);
file_put_contents($dir . '/user.yml', $process->getOutput());
// Delete the account and re-import it.
$account->delete();
$this->container
->get(Importer::class)
->importContent(new Finder($dir));
// Ensure the import succeeded, and that we can log in with the imported
// account. We want to use the standard login form, rather than a one-time
// login link, to ensure the password is preserved.
$this->assertIsObject(user_load_by_name($account->getAccountName()));
$this->useOneTimeLoginLinks = FALSE;
$this->drupalLogin($account);
$this->assertSession()
->addressMatches('/\\/user\\/[0-9]+$/');
}
/**
* Recursively sorts an array by key.
*
* @param array $data
* The array to sort.
*/
private static function recursiveSortByKey(array &$data) : void {
// If the array is a list, it is by definition already sorted.
if (!array_is_list($data)) {
ksort($data);
}
foreach ($data as &$value) {
if (is_array($value)) {
self::recursiveSortByKey($value);
}
}
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
ContentExportTest | Tests exporting content in YAML format. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.