1<?php
2
3namespace Drupal\Tests\migrate\Kernel;
4
5/**
6 * Tests migration high water property.
7 *
8 * @group migrate
9 */
10class HighWaterTest extends MigrateTestBase {
11
12  /**
13   * {@inheritdoc}
14   */
15  protected static $modules = [
16    'system',
17    'user',
18    'node',
19    'migrate',
20    'migrate_high_water_test',
21    'field',
22  ];
23
24  /**
25   * {@inheritdoc}
26   */
27  protected function setUp(): void {
28    parent::setUp();
29    // Create source test table.
30    $this->sourceDatabase->schema()->createTable('high_water_node', [
31      'fields' => [
32        'id' => [
33          'description' => 'Serial',
34          'type' => 'serial',
35          'unsigned' => TRUE,
36          'not null' => TRUE,
37        ],
38        'changed' => [
39          'description' => 'Highwater',
40          'type' => 'int',
41          'unsigned' => TRUE,
42        ],
43        'title' => [
44          'description' => 'Title',
45          'type' => 'varchar',
46          'length' => 128,
47          'not null' => TRUE,
48          'default' => '',
49        ],
50      ],
51      'primary key' => [
52        'id',
53      ],
54      'description' => 'Contains nodes to import',
55    ]);
56
57    // Add 3 items to source table.
58    $this->sourceDatabase->insert('high_water_node')
59      ->fields([
60        'title',
61        'changed',
62      ])
63      ->values([
64        'title' => 'Item 1',
65        'changed' => 1,
66      ])
67      ->values([
68        'title' => 'Item 2',
69        'changed' => 2,
70      ])
71      ->values([
72        'title' => 'Item 3',
73        'changed' => 3,
74      ])
75      ->execute();
76
77    $this->installEntitySchema('node');
78    $this->installEntitySchema('user');
79    $this->installSchema('node', 'node_access');
80
81    $this->executeMigration('high_water_test');
82  }
83
84  /**
85   * Tests high water property of SqlBase.
86   */
87  public function testHighWater() {
88    // Assert all of the nodes have been imported.
89    $this->assertNodeExists('Item 1');
90    $this->assertNodeExists('Item 2');
91    $this->assertNodeExists('Item 3');
92
93    // Update Item 1 setting its high_water_property to value that is below
94    // current high water mark.
95    $this->sourceDatabase->update('high_water_node')
96      ->fields([
97        'title' => 'Item 1 updated',
98        'changed' => 2,
99      ])
100      ->condition('title', 'Item 1')
101      ->execute();
102
103    // Update Item 2 setting its high_water_property to value equal to
104    // current high water mark.
105    $this->sourceDatabase->update('high_water_node')
106      ->fields([
107        'title' => 'Item 2 updated',
108        'changed' => 3,
109      ])
110      ->condition('title', 'Item 2')
111      ->execute();
112
113    // Update Item 3 setting its high_water_property to value that is above
114    // current high water mark.
115    $this->sourceDatabase->update('high_water_node')
116      ->fields([
117        'title' => 'Item 3 updated',
118        'changed' => 4,
119      ])
120      ->condition('title', 'Item 3')
121      ->execute();
122
123    // Execute migration again.
124    $this->executeMigration('high_water_test');
125
126    // Item with lower highwater should not be updated.
127    $this->assertNodeExists('Item 1');
128    $this->assertNodeDoesNotExist('Item 1 updated');
129
130    // Item with equal highwater should not be updated.
131    $this->assertNodeExists('Item 2');
132    $this->assertNodeDoesNotExist('Item 2 updated');
133
134    // Item with greater highwater should be updated.
135    $this->assertNodeExists('Item 3 updated');
136    $this->assertNodeDoesNotExist('Item 3');
137  }
138
139  /**
140   * Tests that the high water value can be 0.
141   */
142  public function testZeroHighwater() {
143    // Assert all of the nodes have been imported.
144    $this->assertNodeExists('Item 1');
145    $this->assertNodeExists('Item 2');
146    $this->assertNodeExists('Item 3');
147    $migration = $this->container->get('plugin.manager.migration')->CreateInstance('high_water_test', []);
148    $source = $migration->getSourcePlugin();
149    $source->rewind();
150    $count = 0;
151    while ($source->valid()) {
152      $count++;
153      $source->next();
154    }
155
156    // Expect no rows as everything is below the high water mark.
157    $this->assertSame(0, $count);
158
159    // Test resetting the high water mark to 0.
160    $this->container->get('keyvalue')->get('migrate:high_water')->set('high_water_test', 0);
161    $migration = $this->container->get('plugin.manager.migration')->CreateInstance('high_water_test', []);
162    $source = $migration->getSourcePlugin();
163    $source->rewind();
164    $count = 0;
165    while ($source->valid()) {
166      $count++;
167      $source->next();
168    }
169    $this->assertSame(3, $count);
170  }
171
172  /**
173   * Tests that deleting the high water value causes all rows to be reimported.
174   */
175  public function testNullHighwater() {
176    // Assert all of the nodes have been imported.
177    $this->assertNodeExists('Item 1');
178    $this->assertNodeExists('Item 2');
179    $this->assertNodeExists('Item 3');
180    $migration = $this->container->get('plugin.manager.migration')->CreateInstance('high_water_test', []);
181    $source = $migration->getSourcePlugin();
182    $source->rewind();
183    $count = 0;
184    while ($source->valid()) {
185      $count++;
186      $source->next();
187    }
188
189    // Expect no rows as everything is below the high water mark.
190    $this->assertSame(0, $count);
191
192    // Test resetting the high water mark.
193    $this->container->get('keyvalue')->get('migrate:high_water')->delete('high_water_test');
194    $migration = $this->container->get('plugin.manager.migration')->CreateInstance('high_water_test', []);
195    $source = $migration->getSourcePlugin();
196    $source->rewind();
197    $count = 0;
198    while ($source->valid()) {
199      $count++;
200      $source->next();
201    }
202    $this->assertSame(3, $count);
203  }
204
205  /**
206   * Tests high water property of SqlBase when rows marked for update.
207   */
208  public function testHighWaterUpdate() {
209    // Assert all of the nodes have been imported.
210    $this->assertNodeExists('Item 1');
211    $this->assertNodeExists('Item 2');
212    $this->assertNodeExists('Item 3');
213
214    // Update Item 1 setting its high_water_property to value that is below
215    // current high water mark.
216    $this->sourceDatabase->update('high_water_node')
217      ->fields([
218        'title' => 'Item 1 updated',
219        'changed' => 2,
220      ])
221      ->condition('title', 'Item 1')
222      ->execute();
223
224    // Update Item 2 setting its high_water_property to value equal to
225    // current high water mark.
226    $this->sourceDatabase->update('high_water_node')
227      ->fields([
228        'title' => 'Item 2 updated',
229        'changed' => 3,
230      ])
231      ->condition('title', 'Item 2')
232      ->execute();
233
234    // Update Item 3 setting its high_water_property to value that is above
235    // current high water mark.
236    $this->sourceDatabase->update('high_water_node')
237      ->fields([
238        'title' => 'Item 3 updated',
239        'changed' => 4,
240      ])
241      ->condition('title', 'Item 3')
242      ->execute();
243
244    // Set all rows as needing an update.
245    $id_map = $this->getMigration('high_water_test')->getIdMap();
246    $id_map->prepareUpdate();
247
248    $this->executeMigration('high_water_test');
249
250    // Item with lower highwater should be updated.
251    $this->assertNodeExists('Item 1 updated');
252    $this->assertNodeDoesNotExist('Item 1');
253
254    // Item with equal highwater should be updated.
255    $this->assertNodeExists('Item 2 updated');
256    $this->assertNodeDoesNotExist('Item 2');
257
258    // Item with greater highwater should be updated.
259    $this->assertNodeExists('Item 3 updated');
260    $this->assertNodeDoesNotExist('Item 3');
261  }
262
263  /**
264   * Assert that node with given title exists.
265   *
266   * @param string $title
267   *   Title of the node.
268   */
269  protected function assertNodeExists($title) {
270    self::assertTrue($this->nodeExists($title));
271  }
272
273  /**
274   * Assert that node with given title does not exist.
275   *
276   * @param string $title
277   *   Title of the node.
278   */
279  protected function assertNodeDoesNotExist($title) {
280    self::assertFalse($this->nodeExists($title));
281  }
282
283  /**
284   * Checks if node with given title exists.
285   *
286   * @param string $title
287   *   Title of the node.
288   *
289   * @return bool
290   */
291  protected function nodeExists($title) {
292    $query = \Drupal::entityQuery('node')->accessCheck(FALSE);
293    $result = $query
294      ->condition('title', $title)
295      ->range(0, 1)
296      ->execute();
297
298    return !empty($result);
299  }
300
301}
302