123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- <?php
- namespace Drupal\Tests\content_moderation\Kernel;
- use Drupal\KernelTests\KernelTestBase;
- use Drupal\language\Entity\ConfigurableLanguage;
- use Drupal\node\Entity\Node;
- use Drupal\node\Entity\NodeType;
- use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
- use Drupal\Tests\user\Traits\UserCreationTrait;
- /**
- * @coversDefaultClass \Drupal\content_moderation\Plugin\Validation\Constraint\ModerationStateConstraintValidator
- * @group content_moderation
- */
- class EntityStateChangeValidationTest extends KernelTestBase {
- use ContentModerationTestTrait;
- use UserCreationTrait;
- /**
- * {@inheritdoc}
- */
- public static $modules = [
- 'node',
- 'content_moderation',
- 'user',
- 'system',
- 'language',
- 'content_translation',
- 'workflows',
- ];
- /**
- * An admin user.
- *
- * @var \Drupal\Core\Session\AccountInterface
- */
- protected $adminUser;
- /**
- * {@inheritdoc}
- */
- protected function setUp() {
- parent::setUp();
- $this->installSchema('node', 'node_access');
- $this->installEntitySchema('node');
- $this->installEntitySchema('user');
- $this->installEntitySchema('content_moderation_state');
- $this->installConfig('content_moderation');
- $this->installSchema('system', ['sequences']);
- $this->adminUser = $this->createUser(array_keys($this->container->get('user.permissions')->getPermissions()));
- }
- /**
- * Test valid transitions.
- *
- * @covers ::validate
- */
- public function testValidTransition() {
- $this->setCurrentUser($this->adminUser);
- $node_type = NodeType::create([
- 'type' => 'example',
- ]);
- $node_type->save();
- $workflow = $this->createEditorialWorkflow();
- $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
- $workflow->save();
- $node = Node::create([
- 'type' => 'example',
- 'title' => 'Test title',
- ]);
- $node->moderation_state->value = 'draft';
- $node->save();
- $node->moderation_state->value = 'published';
- $this->assertCount(0, $node->validate());
- $node->save();
- $this->assertEquals('published', $node->moderation_state->value);
- }
- /**
- * Test invalid transitions.
- *
- * @covers ::validate
- */
- public function testInvalidTransition() {
- $this->setCurrentUser($this->adminUser);
- $node_type = NodeType::create([
- 'type' => 'example',
- ]);
- $node_type->save();
- $workflow = $this->createEditorialWorkflow();
- $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
- $workflow->save();
- $node = Node::create([
- 'type' => 'example',
- 'title' => 'Test title',
- ]);
- $node->moderation_state->value = 'draft';
- $node->save();
- $node->moderation_state->value = 'archived';
- $violations = $node->validate();
- $this->assertCount(1, $violations);
- $this->assertEquals('Invalid state transition from <em class="placeholder">Draft</em> to <em class="placeholder">Archived</em>', $violations->get(0)->getMessage());
- }
- /**
- * Test validation with an invalid state.
- */
- public function testInvalidState() {
- $node_type = NodeType::create([
- 'type' => 'example',
- ]);
- $node_type->save();
- $workflow = $this->createEditorialWorkflow();
- $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
- $workflow->save();
- $node = Node::create([
- 'type' => 'example',
- 'title' => 'Test title',
- ]);
- $node->moderation_state->value = 'invalid_state';
- $violations = $node->validate();
- $this->assertCount(1, $violations);
- $this->assertEquals('State <em class="placeholder">invalid_state</em> does not exist on <em class="placeholder">Editorial</em> workflow', $violations->get(0)->getMessage());
- }
- /**
- * Test validation with content that has no initial state or an invalid state.
- */
- public function testInvalidStateWithoutExisting() {
- $this->setCurrentUser($this->adminUser);
- // Create content without moderation enabled for the content type.
- $node_type = NodeType::create([
- 'type' => 'example',
- ]);
- $node_type->save();
- $node = Node::create([
- 'type' => 'example',
- 'title' => 'Test title',
- ]);
- $node->save();
- // Enable moderation to test validation on existing content, with no
- // explicit state.
- $workflow = $this->createEditorialWorkflow();
- $workflow->getTypePlugin()->addState('deleted_state', 'Deleted state');
- $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
- $workflow->save();
- // Validate the invalid state.
- $node->moderation_state->value = 'invalid_state';
- $violations = $node->validate();
- $this->assertCount(1, $violations);
- // Assign the node to a state we're going to delete.
- $node->moderation_state->value = 'deleted_state';
- $node->save();
- // Delete the state so $node->original contains an invalid state when
- // validating.
- $workflow->getTypePlugin()->deleteState('deleted_state');
- $workflow->save();
- // When there is an invalid state, the content will revert to "draft". This
- // will allow a draft to draft transition.
- $node->moderation_state->value = 'draft';
- $violations = $node->validate();
- $this->assertCount(0, $violations);
- // This will disallow a draft to archived transition.
- $node->moderation_state->value = 'archived';
- $violations = $node->validate();
- $this->assertCount(1, $violations);
- }
- /**
- * Test state transition validation with multiple languages.
- */
- public function testInvalidStateMultilingual() {
- $this->setCurrentUser($this->adminUser);
- ConfigurableLanguage::createFromLangcode('fr')->save();
- $node_type = NodeType::create([
- 'type' => 'example',
- ]);
- $node_type->save();
- $workflow = $this->createEditorialWorkflow();
- $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
- $workflow->save();
- $node = Node::create([
- 'type' => 'example',
- 'title' => 'English Published Node',
- 'langcode' => 'en',
- 'moderation_state' => 'published',
- ]);
- $node->save();
- $node_fr = $node->addTranslation('fr', $node->toArray());
- $node_fr->setTitle('French Published Node');
- $node_fr->save();
- $this->assertEquals('published', $node_fr->moderation_state->value);
- // Create a pending revision of the original node.
- $node->moderation_state = 'draft';
- $node->setNewRevision(TRUE);
- $node->isDefaultRevision(FALSE);
- $node->save();
- // For the pending english revision, there should be a violation from draft
- // to archived.
- $node->moderation_state = 'archived';
- $violations = $node->validate();
- $this->assertCount(1, $violations);
- $this->assertEquals('Invalid state transition from <em class="placeholder">Draft</em> to <em class="placeholder">Archived</em>', $violations->get(0)->getMessage());
- // From the default french published revision, there should be none.
- $node_fr = Node::load($node->id())->getTranslation('fr');
- $this->assertEquals('published', $node_fr->moderation_state->value);
- $node_fr->moderation_state = 'archived';
- $violations = $node_fr->validate();
- $this->assertCount(0, $violations);
- // From the latest french revision, there should also be no violation.
- $node_fr = Node::load($node->id())->getTranslation('fr');
- $this->assertEquals('published', $node_fr->moderation_state->value);
- $node_fr->moderation_state = 'archived';
- $violations = $node_fr->validate();
- $this->assertCount(0, $violations);
- }
- /**
- * Tests that content without prior moderation information can be moderated.
- */
- public function testExistingContentWithNoModeration() {
- $this->setCurrentUser($this->adminUser);
- $node_type = NodeType::create([
- 'type' => 'example',
- ]);
- $node_type->save();
- /** @var \Drupal\node\NodeInterface $node */
- $node = Node::create([
- 'type' => 'example',
- 'title' => 'Test title',
- ]);
- $node->save();
- $nid = $node->id();
- // Enable moderation for our node type.
- $workflow = $this->createEditorialWorkflow();
- $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
- $workflow->save();
- $node = Node::load($nid);
- // Having no previous state should not break validation.
- $violations = $node->validate();
- $this->assertCount(0, $violations);
- // Having no previous state should not break saving the node.
- $node->setTitle('New');
- $node->save();
- }
- /**
- * Tests that content without prior moderation information can be translated.
- */
- public function testExistingMultilingualContentWithNoModeration() {
- $this->setCurrentUser($this->adminUser);
- // Enable French.
- ConfigurableLanguage::createFromLangcode('fr')->save();
- $node_type = NodeType::create([
- 'type' => 'example',
- ]);
- $node_type->save();
- /** @var \Drupal\node\NodeInterface $node */
- $node = Node::create([
- 'type' => 'example',
- 'title' => 'Test title',
- 'langcode' => 'en',
- ]);
- $node->save();
- $nid = $node->id();
- $node = Node::load($nid);
- // Creating a translation shouldn't break, even though there's no previous
- // moderated revision for the new language.
- $node_fr = $node->addTranslation('fr');
- $node_fr->setTitle('Francais');
- $node_fr->save();
- // Enable moderation for our node type.
- $workflow = $this->createEditorialWorkflow();
- $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
- $workflow->save();
- // Reload the French version of the node.
- $node = Node::load($nid);
- $node_fr = $node->getTranslation('fr');
- /** @var \Drupal\node\NodeInterface $node_fr */
- $node_fr->setTitle('Nouveau');
- $node_fr->save();
- }
- /**
- * @dataProvider transitionAccessValidationTestCases
- */
- public function testTransitionAccessValidation($permissions, $target_state, $messages) {
- $node_type = NodeType::create([
- 'type' => 'example',
- ]);
- $node_type->save();
- $workflow = $this->createEditorialWorkflow();
- $workflow->getTypePlugin()->addState('foo', 'Foo');
- $workflow->getTypePlugin()->addTransition('draft_to_foo', 'Draft to foo', ['draft'], 'foo');
- $workflow->getTypePlugin()->addTransition('foo_to_foo', 'Foo to foo', ['foo'], 'foo');
- $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
- $workflow->save();
- $this->setCurrentUser($this->createUser($permissions));
- $node = Node::create([
- 'type' => 'example',
- 'title' => 'Test content',
- 'moderation_state' => $target_state,
- ]);
- $this->assertTrue($node->isNew());
- $violations = $node->validate();
- $this->assertCount(count($messages), $violations);
- foreach ($messages as $i => $message) {
- $this->assertEquals($message, $violations->get($i)->getMessage());
- }
- }
- /**
- * Test cases for ::testTransitionAccessValidation.
- */
- public function transitionAccessValidationTestCases() {
- return [
- 'Invalid transition, no permissions validated' => [
- [],
- 'archived',
- ['Invalid state transition from <em class="placeholder">Draft</em> to <em class="placeholder">Archived</em>'],
- ],
- 'Valid transition, missing permission' => [
- [],
- 'published',
- ['You do not have access to transition from <em class="placeholder">Draft</em> to <em class="placeholder">Published</em>'],
- ],
- 'Valid transition, granted published permission' => [
- ['use editorial transition publish'],
- 'published',
- [],
- ],
- 'Valid transition, granted draft permission' => [
- ['use editorial transition create_new_draft'],
- 'draft',
- [],
- ],
- 'Valid transition, incorrect permission granted' => [
- ['use editorial transition create_new_draft'],
- 'published',
- ['You do not have access to transition from <em class="placeholder">Draft</em> to <em class="placeholder">Published</em>'],
- ],
- // Test with an additional state and set of transitions, since the
- // "published" transition can start from either "draft" or "published", it
- // does not capture bugs that fail to correctly distinguish the initial
- // workflow state from the set state of a new entity.
- 'Valid transition, granted foo permission' => [
- ['use editorial transition draft_to_foo'],
- 'foo',
- [],
- ],
- 'Valid transition, incorrect foo permission granted' => [
- ['use editorial transition foo_to_foo'],
- 'foo',
- ['You do not have access to transition from <em class="placeholder">Draft</em> to <em class="placeholder">Foo</em>'],
- ],
- ];
- }
- }
|