17b6ae41da10549e949c7b77e1563e0d4a6f3643
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Command / DbDumpTest.php
1 <?php
2
3 namespace Drupal\KernelTests\Core\Command;
4
5 use Drupal\Component\Utility\SafeMarkup;
6 use Drupal\Core\Command\DbDumpApplication;
7 use Drupal\Core\Config\DatabaseStorage;
8 use Drupal\Core\Database\Database;
9 use Drupal\Core\DependencyInjection\ContainerBuilder;
10 use Drupal\KernelTests\KernelTestBase;
11 use Drupal\user\Entity\User;
12 use Symfony\Component\Console\Tester\CommandTester;
13 use Symfony\Component\DependencyInjection\Reference;
14
15 /**
16  * Tests for the database dump commands.
17  *
18  * @group Update
19  */
20 class DbDumpTest extends KernelTestBase {
21
22   /**
23    * {@inheritdoc}
24    */
25   public static $modules = ['system', 'config', 'dblog', 'menu_link_content', 'link', 'block_content', 'file', 'user'];
26
27   /**
28    * Test data to write into config.
29    *
30    * @var array
31    */
32   protected $data;
33
34   /**
35    * Flag to skip these tests, which are database-backend dependent (MySQL).
36    *
37    * @see \Drupal\Core\Command\DbDumpCommand
38    *
39    * @var bool
40    */
41   protected $skipTests = FALSE;
42
43   /**
44    * An array of original table schemas.
45    *
46    * @var array
47    */
48   protected $originalTableSchemas = [];
49
50   /**
51    * An array of original table indexes (including primary and unique keys).
52    *
53    * @var array
54    */
55   protected $originalTableIndexes = [];
56
57   /**
58    * Tables that should be part of the exported script.
59    *
60    * @var array
61    */
62   protected $tables;
63
64   /**
65    * {@inheritdoc}
66    *
67    * Register a database cache backend rather than memory-based.
68    */
69   public function register(ContainerBuilder $container) {
70     parent::register($container);
71     $container->register('cache_factory', 'Drupal\Core\Cache\DatabaseBackendFactory')
72       ->addArgument(new Reference('database'))
73       ->addArgument(new Reference('cache_tags.invalidator.checksum'))
74       ->addArgument(new Reference('settings'));
75   }
76
77   /**
78    * {@inheritdoc}
79    */
80   protected function setUp() {
81     parent::setUp();
82
83     // Determine what database backend is running, and set the skip flag.
84     $this->skipTests = Database::getConnection()->databaseType() !== 'mysql';
85
86     // Create some schemas so our export contains tables.
87     $this->installSchema('system', [
88       'key_value_expire',
89       'sessions',
90     ]);
91     $this->installSchema('dblog', ['watchdog']);
92     $this->installEntitySchema('block_content');
93     $this->installEntitySchema('user');
94     $this->installEntitySchema('file');
95     $this->installEntitySchema('menu_link_content');
96     $this->installSchema('system', 'sequences');
97
98     // Place some sample config to test for in the export.
99     $this->data = [
100       'foo' => $this->randomMachineName(),
101       'bar' => $this->randomMachineName(),
102     ];
103     $storage = new DatabaseStorage(Database::getConnection(), 'config');
104     $storage->write('test_config', $this->data);
105
106     // Create user account with some potential syntax issues.
107     $account = User::create(['mail' => 'q\'uote$dollar@example.com', 'name' => '$dollar']);
108     $account->save();
109
110     // Create url_alias (this will create 'url_alias').
111     $this->container->get('path.alias_storage')->save('/user/' . $account->id(), '/user/example');
112
113     // Create a cache table (this will create 'cache_discovery').
114     \Drupal::cache('discovery')->set('test', $this->data);
115
116     // These are all the tables that should now be in place.
117     $this->tables = [
118       'block_content',
119       'block_content_field_data',
120       'block_content_field_revision',
121       'block_content_revision',
122       'cachetags',
123       'config',
124       'cache_bootstrap',
125       'cache_config',
126       'cache_data',
127       'cache_discovery',
128       'cache_entity',
129       'file_managed',
130       'key_value_expire',
131       'menu_link_content',
132       'menu_link_content_data',
133       'sequences',
134       'sessions',
135       'url_alias',
136       'user__roles',
137       'users',
138       'users_field_data',
139       'watchdog',
140     ];
141   }
142
143   /**
144    * Test the command directly.
145    */
146   public function testDbDumpCommand() {
147     if ($this->skipTests) {
148       $this->pass("Skipping test since the DbDumpCommand is currently only compatible with MySql");
149       return;
150     }
151
152     $application = new DbDumpApplication();
153     $command = $application->find('dump-database-d8-mysql');
154     $command_tester = new CommandTester($command);
155     $command_tester->execute([]);
156
157     // Tables that are schema-only should not have data exported.
158     $pattern = preg_quote("\$connection->insert('sessions')");
159     $this->assertFalse(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Tables defined as schema-only do not have data exported to the script.');
160
161     // Table data is exported.
162     $pattern = preg_quote("\$connection->insert('config')");
163     $this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Table data is properly exported to the script.');
164
165     // The test data are in the dump (serialized).
166     $pattern = preg_quote(serialize($this->data));
167     $this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Generated data is found in the exported script.');
168
169     // Check that the user account name and email address was properly escaped.
170     $pattern = preg_quote('"q\'uote\$dollar@example.com"');
171     $this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'The user account email address was properly escaped in the exported script.');
172     $pattern = preg_quote('\'$dollar\'');
173     $this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'The user account name was properly escaped in the exported script.');
174   }
175
176   /**
177    * Test loading the script back into the database.
178    */
179   public function testScriptLoad() {
180     if ($this->skipTests) {
181       $this->pass("Skipping test since the DbDumpCommand is currently only compatible with MySql");
182       return;
183     }
184
185     // Generate the script.
186     $application = new DbDumpApplication();
187     $command = $application->find('dump-database-d8-mysql');
188     $command_tester = new CommandTester($command);
189     $command_tester->execute([]);
190     $script = $command_tester->getDisplay();
191
192     // Store original schemas and drop tables to avoid errors.
193     foreach ($this->tables as $table) {
194       $this->originalTableSchemas[$table] = $this->getTableSchema($table);
195       $this->originalTableIndexes[$table] = $this->getTableIndexes($table);
196       Database::getConnection()->schema()->dropTable($table);
197     }
198
199     // This will load the data.
200     $file = sys_get_temp_dir() . '/' . $this->randomMachineName();
201     file_put_contents($file, $script);
202     require_once $file;
203
204     // The tables should now exist and the schemas should match the originals.
205     foreach ($this->tables as $table) {
206       $this->assertTrue(Database::getConnection()
207         ->schema()
208         ->tableExists($table), SafeMarkup::format('Table @table created by the database script.', ['@table' => $table]));
209       $this->assertSame($this->originalTableSchemas[$table], $this->getTableSchema($table), SafeMarkup::format('The schema for @table was properly restored.', ['@table' => $table]));
210       $this->assertSame($this->originalTableIndexes[$table], $this->getTableIndexes($table), SafeMarkup::format('The indexes for @table were properly restored.', ['@table' => $table]));
211     }
212
213     // Ensure the test config has been replaced.
214     $config = unserialize(db_query("SELECT data FROM {config} WHERE name = 'test_config'")->fetchField());
215     $this->assertIdentical($config, $this->data, 'Script has properly restored the config table data.');
216
217     // Ensure the cache data was not exported.
218     $this->assertFalse(\Drupal::cache('discovery')
219       ->get('test'), 'Cache data was not exported to the script.');
220   }
221
222   /**
223    * Helper function to get a simplified schema for a given table.
224    *
225    * @param string $table
226    *
227    * @return array
228    *   Array keyed by field name, with the values being the field type.
229    */
230   protected function getTableSchema($table) {
231     // Verify the field type on the data column in the cache table.
232     // @todo this is MySQL specific.
233     $query = db_query("SHOW COLUMNS FROM {" . $table . "}");
234     $definition = [];
235     while ($row = $query->fetchAssoc()) {
236       $definition[$row['Field']] = $row['Type'];
237     }
238     return $definition;
239   }
240
241   /**
242    * Returns indexes for a given table.
243    *
244    * @param string $table
245    *   The table to find indexes for.
246    *
247    * @return array
248    *   The 'primary key', 'unique keys', and 'indexes' portion of the Drupal
249    *   table schema.
250    */
251   protected function getTableIndexes($table) {
252     $query = db_query("SHOW INDEX FROM {" . $table . "}");
253     $definition = [];
254     while ($row = $query->fetchAssoc()) {
255       $index_name = $row['Key_name'];
256       $column = $row['Column_name'];
257       // Key the arrays by the index sequence for proper ordering (start at 0).
258       $order = $row['Seq_in_index'] - 1;
259
260       // If specified, add length to the index.
261       if ($row['Sub_part']) {
262         $column = [$column, $row['Sub_part']];
263       }
264
265       if ($index_name === 'PRIMARY') {
266         $definition['primary key'][$order] = $column;
267       }
268       elseif ($row['Non_unique'] == 0) {
269         $definition['unique keys'][$index_name][$order] = $column;
270       }
271       else {
272         $definition['indexes'][$index_name][$order] = $column;
273       }
274     }
275     return $definition;
276   }
277
278 }