aeed8486f5b652c425562585cfbd0c6992b9a9a9
[yaffs-website] / web / core / modules / content_moderation / tests / src / Kernel / EntityStateChangeValidationTest.php
1 <?php
2
3 namespace Drupal\Tests\content_moderation\Kernel;
4
5 use Drupal\KernelTests\KernelTestBase;
6 use Drupal\language\Entity\ConfigurableLanguage;
7 use Drupal\node\Entity\Node;
8 use Drupal\node\Entity\NodeType;
9 use Drupal\workflows\Entity\Workflow;
10
11 /**
12  * @coversDefaultClass \Drupal\content_moderation\Plugin\Validation\Constraint\ModerationStateConstraintValidator
13  * @group content_moderation
14  */
15 class EntityStateChangeValidationTest extends KernelTestBase {
16
17   /**
18    * {@inheritdoc}
19    */
20   public static $modules = [
21     'node',
22     'content_moderation',
23     'user',
24     'system',
25     'language',
26     'content_translation',
27     'workflows',
28   ];
29
30   /**
31    * {@inheritdoc}
32    */
33   protected function setUp() {
34     parent::setUp();
35
36     $this->installSchema('node', 'node_access');
37     $this->installEntitySchema('node');
38     $this->installEntitySchema('user');
39     $this->installEntitySchema('content_moderation_state');
40     $this->installConfig('content_moderation');
41   }
42
43   /**
44    * Test valid transitions.
45    *
46    * @covers ::validate
47    */
48   public function testValidTransition() {
49     $node_type = NodeType::create([
50       'type' => 'example',
51     ]);
52     $node_type->save();
53     $workflow = Workflow::load('editorial');
54     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
55     $workflow->save();
56
57     $node = Node::create([
58       'type' => 'example',
59       'title' => 'Test title',
60     ]);
61     $node->moderation_state->value = 'draft';
62     $node->save();
63
64     $node->moderation_state->value = 'published';
65     $this->assertCount(0, $node->validate());
66     $node->save();
67
68     $this->assertEquals('published', $node->moderation_state->value);
69   }
70
71   /**
72    * Test invalid transitions.
73    *
74    * @covers ::validate
75    */
76   public function testInvalidTransition() {
77     $node_type = NodeType::create([
78       'type' => 'example',
79     ]);
80     $node_type->save();
81     $workflow = Workflow::load('editorial');
82     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
83     $workflow->save();
84
85     $node = Node::create([
86       'type' => 'example',
87       'title' => 'Test title',
88     ]);
89     $node->moderation_state->value = 'draft';
90     $node->save();
91
92     $node->moderation_state->value = 'archived';
93     $violations = $node->validate();
94     $this->assertCount(1, $violations);
95
96     $this->assertEquals('Invalid state transition from <em class="placeholder">Draft</em> to <em class="placeholder">Archived</em>', $violations->get(0)->getMessage());
97   }
98
99   /**
100    * Test validation with an invalid state.
101    */
102   public function testInvalidState() {
103     $node_type = NodeType::create([
104       'type' => 'example',
105     ]);
106     $node_type->save();
107     $workflow = Workflow::load('editorial');
108     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
109     $workflow->save();
110
111     $node = Node::create([
112       'type' => 'example',
113       'title' => 'Test title',
114     ]);
115     $node->moderation_state->value = 'invalid_state';
116     $violations = $node->validate();
117
118     $this->assertCount(1, $violations);
119     $this->assertEquals('State <em class="placeholder">invalid_state</em> does not exist on <em class="placeholder">Editorial</em> workflow', $violations->get(0)->getMessage());
120   }
121
122   /**
123    * Test validation with content that has no initial state or an invalid state.
124    */
125   public function testInvalidStateWithoutExisting() {
126     // Create content without moderation enabled for the content type.
127     $node_type = NodeType::create([
128       'type' => 'example',
129     ]);
130     $node_type->save();
131     $node = Node::create([
132       'type' => 'example',
133       'title' => 'Test title',
134     ]);
135     $node->save();
136
137     // Enable moderation to test validation on existing content, with no
138     // explicit state.
139     $workflow = Workflow::load('editorial');
140     $workflow->getTypePlugin()->addState('deleted_state', 'Deleted state');
141     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
142     $workflow->save();
143
144     // Validate the invalid state.
145     $node->moderation_state->value = 'invalid_state';
146     $violations = $node->validate();
147     $this->assertCount(1, $violations);
148
149     // Assign the node to a state we're going to delete.
150     $node->moderation_state->value = 'deleted_state';
151     $node->save();
152
153     // Delete the state so $node->original contains an invalid state when
154     // validating.
155     $workflow->getTypePlugin()->deleteState('deleted_state');
156     $workflow->save();
157     $node->moderation_state->value = 'draft';
158     $violations = $node->validate();
159     $this->assertCount(0, $violations);
160   }
161
162   /**
163    * Test state transition validation with multiple languages.
164    */
165   public function testInvalidStateMultilingual() {
166     ConfigurableLanguage::createFromLangcode('fr')->save();
167     $node_type = NodeType::create([
168       'type' => 'example',
169     ]);
170     $node_type->save();
171
172     $workflow = Workflow::load('editorial');
173     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
174     $workflow->save();
175
176     $node = Node::create([
177       'type' => 'example',
178       'title' => 'English Published Node',
179       'langcode' => 'en',
180       'moderation_state' => 'published',
181     ]);
182     $node->save();
183
184     $node_fr = $node->addTranslation('fr', $node->toArray());
185     $node_fr->setTitle('French Published Node');
186     $node_fr->save();
187     $this->assertEquals('published', $node_fr->moderation_state->value);
188
189     // Create a pending revision of the original node.
190     $node->moderation_state = 'draft';
191     $node->setNewRevision(TRUE);
192     $node->isDefaultRevision(FALSE);
193     $node->save();
194
195     // For the pending english revision, there should be a violation from draft
196     // to archived.
197     $node->moderation_state = 'archived';
198     $violations = $node->validate();
199     $this->assertCount(1, $violations);
200     $this->assertEquals('Invalid state transition from <em class="placeholder">Draft</em> to <em class="placeholder">Archived</em>', $violations->get(0)->getMessage());
201
202     // From the default french published revision, there should be none.
203     $node_fr = Node::load($node->id())->getTranslation('fr');
204     $this->assertEquals('published', $node_fr->moderation_state->value);
205     $node_fr->moderation_state = 'archived';
206     $violations = $node_fr->validate();
207     $this->assertCount(0, $violations);
208
209     // From the latest french revision, there should also be no violation.
210     $node_fr = Node::load($node->id())->getTranslation('fr');
211     $this->assertEquals('published', $node_fr->moderation_state->value);
212     $node_fr->moderation_state = 'archived';
213     $violations = $node_fr->validate();
214     $this->assertCount(0, $violations);
215   }
216
217   /**
218    * Tests that content without prior moderation information can be moderated.
219    */
220   public function testExistingContentWithNoModeration() {
221     $node_type = NodeType::create([
222       'type' => 'example',
223     ]);
224     $node_type->save();
225     /** @var \Drupal\node\NodeInterface $node */
226     $node = Node::create([
227       'type' => 'example',
228       'title' => 'Test title',
229     ]);
230     $node->save();
231
232     $nid = $node->id();
233
234     // Enable moderation for our node type.
235     $workflow = Workflow::load('editorial');
236     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
237     $workflow->save();
238
239     $node = Node::load($nid);
240
241     // Having no previous state should not break validation.
242     $violations = $node->validate();
243
244     $this->assertCount(0, $violations);
245
246     // Having no previous state should not break saving the node.
247     $node->setTitle('New');
248     $node->save();
249   }
250
251   /**
252    * Tests that content without prior moderation information can be translated.
253    */
254   public function testExistingMultilingualContentWithNoModeration() {
255     // Enable French.
256     ConfigurableLanguage::createFromLangcode('fr')->save();
257
258     $node_type = NodeType::create([
259       'type' => 'example',
260     ]);
261     $node_type->save();
262     /** @var \Drupal\node\NodeInterface $node */
263     $node = Node::create([
264       'type' => 'example',
265       'title' => 'Test title',
266       'langcode' => 'en',
267     ]);
268     $node->save();
269
270     $nid = $node->id();
271
272     $node = Node::load($nid);
273
274     // Creating a translation shouldn't break, even though there's no previous
275     // moderated revision for the new language.
276     $node_fr = $node->addTranslation('fr');
277     $node_fr->setTitle('Francais');
278     $node_fr->save();
279
280     // Enable moderation for our node type.
281     $workflow = Workflow::load('editorial');
282     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
283     $workflow->save();
284
285     // Reload the French version of the node.
286     $node = Node::load($nid);
287     $node_fr = $node->getTranslation('fr');
288
289     /** @var \Drupal\node\NodeInterface $node_fr */
290     $node_fr->setTitle('Nouveau');
291     $node_fr->save();
292   }
293
294 }