MigrateControllerTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. <?php
  2. namespace yiiunit\extensions\mongodb\console\controllers;
  3. use yii\console\controllers\BaseMigrateController;
  4. use yii\helpers\FileHelper;
  5. use yii\mongodb\Exception;
  6. use yii\mongodb\Migration;
  7. use yii\mongodb\Query;
  8. use Yii;
  9. use yiiunit\extensions\mongodb\TestCase;
  10. /**
  11. * Unit test for [[\yii\mongodb\console\controllers\MigrateController]].
  12. * @see MigrateController
  13. *
  14. * @group mongodb
  15. * @group console
  16. */
  17. class MigrateControllerTest extends TestCase
  18. {
  19. /**
  20. * @var string name of the migration controller class, which is under test.
  21. */
  22. protected $migrateControllerClass;
  23. /**
  24. * @var string name of the migration base class.
  25. */
  26. protected $migrationBaseClass;
  27. /**
  28. * @var string test migration path.
  29. */
  30. protected $migrationPath;
  31. /**
  32. * @var string test migration namespace
  33. */
  34. protected $migrationNamespace;
  35. public function setUp()
  36. {
  37. $this->migrateControllerClass = EchoMigrateController::className();
  38. $this->migrationBaseClass = Migration::className();
  39. parent::setUp();
  40. $this->migrationNamespace = 'yiiunit\extensions\mongodb\runtime\test_migrations';
  41. $this->setUpMigrationPath();
  42. $this->mockApplication();
  43. Yii::$app->setComponents(['mongodb' => $this->getConnection()]);
  44. }
  45. public function tearDown()
  46. {
  47. parent::tearDown();
  48. if (extension_loaded('mongodb')) {
  49. try {
  50. $this->getConnection()->getCollection('migration')->drop();
  51. } catch (Exception $e) {
  52. // shutdown exception
  53. }
  54. }
  55. $this->tearDownMigrationPath();
  56. }
  57. public function setUpMigrationPath()
  58. {
  59. $this->migrationPath = Yii::getAlias('@yiiunit/extensions/mongodb/runtime/test_migrations');
  60. FileHelper::createDirectory($this->migrationPath);
  61. if (!file_exists($this->migrationPath)) {
  62. $this->markTestIncomplete('Unit tests runtime directory should have writable permissions!');
  63. }
  64. }
  65. public function tearDownMigrationPath()
  66. {
  67. FileHelper::removeDirectory($this->migrationPath);
  68. }
  69. /**
  70. * Creates test migrate controller instance.
  71. * @param array $config controller configuration.
  72. * @return BaseMigrateController migrate command instance.
  73. */
  74. protected function createMigrateController(array $config = [])
  75. {
  76. $module = $this->getMockBuilder('yii\\base\\Module')
  77. ->setConstructorArgs(['console'])
  78. ->setMethods(['fake'])
  79. ->getMock();
  80. $class = $this->migrateControllerClass;
  81. $migrateController = new $class('migrate', $module);
  82. $migrateController->interactive = false;
  83. $migrateController->migrationPath = $this->migrationPath;
  84. if (array_key_exists('migrationNamespaces', $config) && !$migrateController->canSetProperty('migrationNamespaces')) {
  85. $this->markTestSkipped("`migrationNamespaces` not supported by this Yii framework version");
  86. }
  87. return Yii::configure($migrateController, $config);
  88. }
  89. /**
  90. * @return array applied migration entries
  91. */
  92. protected function getMigrationHistory()
  93. {
  94. $query = new Query();
  95. return $query->from('migration')->all();
  96. }
  97. /**
  98. * Emulates running of the migrate controller action.
  99. * @param string $actionID id of action to be run.
  100. * @param array $args action arguments.
  101. * @param array $config controller configuration.
  102. * @return string command output.
  103. */
  104. protected function runMigrateControllerAction($actionID, array $args = [], array $config = [])
  105. {
  106. $controller = $this->createMigrateController($config);
  107. ob_start();
  108. ob_implicit_flush(false);
  109. $controller->run($actionID, $args);
  110. return ob_get_clean();
  111. }
  112. /**
  113. * @param string $name
  114. * @param string|null $date
  115. * @return string generated class name
  116. */
  117. protected function createMigration($name, $date = null)
  118. {
  119. if ($date === null) {
  120. $date = gmdate('ymd_His');
  121. }
  122. $class = 'm' . $date . '_' . $name;
  123. $baseClass = $this->migrationBaseClass;
  124. $code = <<<CODE
  125. <?php
  126. class {$class} extends {$baseClass}
  127. {
  128. public function up()
  129. {
  130. }
  131. public function down()
  132. {
  133. }
  134. }
  135. CODE;
  136. file_put_contents($this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php', $code);
  137. return $class;
  138. }
  139. /**
  140. * @param string $name
  141. * @param string|null $date
  142. * @return string generated class name
  143. */
  144. protected function createNamespaceMigration($name, $date = null)
  145. {
  146. if ($date === null) {
  147. $date = gmdate('ymdHis');
  148. }
  149. $class = 'M' . $date . ucfirst($name);
  150. $baseClass = $this->migrationBaseClass;
  151. $namespace = $this->migrationNamespace;
  152. $code = <<<CODE
  153. <?php
  154. namespace {$namespace};
  155. class {$class} extends \\{$baseClass}
  156. {
  157. public function up()
  158. {
  159. }
  160. public function down()
  161. {
  162. }
  163. }
  164. CODE;
  165. file_put_contents($this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php', $code);
  166. return $class;
  167. }
  168. /**
  169. * Checks if applied migration history matches expected one.
  170. * @param array $expectedMigrations migration names in expected order
  171. * @param string $message failure message
  172. */
  173. protected function assertMigrationHistory(array $expectedMigrations, $message = '')
  174. {
  175. $success = true;
  176. $migrationHistory = $this->getMigrationHistory();
  177. $appliedMigrations = $migrationHistory;
  178. foreach ($expectedMigrations as $expectedMigrationName) {
  179. $appliedMigration = array_shift($appliedMigrations);
  180. if (!fnmatch(strtr($expectedMigrationName, ['\\' => DIRECTORY_SEPARATOR]), strtr($appliedMigration['version'], ['\\' => DIRECTORY_SEPARATOR]))) {
  181. $success = false;
  182. break;
  183. }
  184. }
  185. if (!$success) {
  186. $message .= "\n";
  187. $message .= "Expected: " . var_export($expectedMigrations, true) . "\n";
  188. $actualMigrations = [];
  189. foreach ($migrationHistory as $row) {
  190. $actualMigrations[] = $row['version'];
  191. }
  192. $message .= "Actual: " . var_export($actualMigrations, true) . "\n";
  193. }
  194. $this->assertTrue($success, $message);
  195. }
  196. // Tests :
  197. public function testCreate()
  198. {
  199. $migrationName = 'test_migration';
  200. $this->runMigrateControllerAction('create', [$migrationName]);
  201. $files = FileHelper::findFiles($this->migrationPath);
  202. $this->assertCount(1, $files, 'Unable to create new migration!');
  203. $this->assertContains($migrationName, basename($files[0]), 'Wrong migration name!');
  204. }
  205. public function testUp()
  206. {
  207. $this->createMigration('test1');
  208. $this->createMigration('test2');
  209. $this->runMigrateControllerAction('up');
  210. $this->assertMigrationHistory(['m*_base', 'm*_test1', 'm*_test2']);
  211. }
  212. /**
  213. * @depends testUp
  214. */
  215. public function testUpCount()
  216. {
  217. $this->createMigration('test1');
  218. $this->createMigration('test2');
  219. $this->runMigrateControllerAction('up', [1]);
  220. $this->assertMigrationHistory(['m*_base', 'm*_test1']);
  221. }
  222. /**
  223. * @depends testUp
  224. */
  225. public function testDownCount()
  226. {
  227. $this->createMigration('test1');
  228. $this->createMigration('test2');
  229. $this->runMigrateControllerAction('up');
  230. $this->runMigrateControllerAction('down', [1]);
  231. $this->assertMigrationHistory(['m*_base', 'm*_test1']);
  232. }
  233. /**
  234. * @depends testDownCount
  235. */
  236. public function testDownAll()
  237. {
  238. $this->createMigration('test1');
  239. $this->createMigration('test2');
  240. $this->runMigrateControllerAction('up');
  241. $this->runMigrateControllerAction('down', ['all']);
  242. $this->assertMigrationHistory(['m*_base']);
  243. }
  244. /**
  245. * @depends testUp
  246. */
  247. public function testHistory()
  248. {
  249. $output = $this->runMigrateControllerAction('history');
  250. $this->assertContains('No migration', $output);
  251. $this->createMigration('test1');
  252. $this->createMigration('test2');
  253. $this->runMigrateControllerAction('up');
  254. $output = $this->runMigrateControllerAction('history');
  255. $this->assertContains('_test1', $output);
  256. $this->assertContains('_test2', $output);
  257. }
  258. /**
  259. * @depends testUp
  260. */
  261. public function testNew()
  262. {
  263. $this->createMigration('test1');
  264. $output = $this->runMigrateControllerAction('new');
  265. $this->assertContains('_test1', $output);
  266. $this->runMigrateControllerAction('up');
  267. $output = $this->runMigrateControllerAction('new');
  268. $this->assertNotContains('_test1', $output);
  269. }
  270. public function testMark()
  271. {
  272. $version = '010101_000001';
  273. $this->createMigration('mark1', $version);
  274. $this->runMigrateControllerAction('mark', [$version]);
  275. $this->assertMigrationHistory(['m*_base', 'm*_mark1']);
  276. }
  277. public function testTo()
  278. {
  279. $version = '020202_000001';
  280. $this->createMigration('to1', $version);
  281. $this->runMigrateControllerAction('to', [$version]);
  282. $this->assertMigrationHistory(['m*_base', 'm*_to1']);
  283. }
  284. /**
  285. * @depends testUp
  286. */
  287. public function testRedo()
  288. {
  289. $this->createMigration('redo');
  290. $this->runMigrateControllerAction('up');
  291. $this->runMigrateControllerAction('redo');
  292. $this->assertMigrationHistory(['m*_base', 'm*_redo']);
  293. }
  294. // namespace :
  295. /**
  296. * @depends testCreate
  297. */
  298. public function testNamespaceCreate()
  299. {
  300. // default namespace apply :
  301. $migrationName = 'testDefaultNamespace';
  302. $this->runMigrateControllerAction('create', [$migrationName], [
  303. 'migrationPath' => null,
  304. 'migrationNamespaces' => [$this->migrationNamespace]
  305. ]);
  306. $files = FileHelper::findFiles($this->migrationPath);
  307. $fileContent = file_get_contents($files[0]);
  308. $this->assertContains("namespace {$this->migrationNamespace};", $fileContent);
  309. $this->assertRegExp('/class M[0-9]{12}' . ucfirst($migrationName) . '/s', $fileContent);
  310. unlink($files[0]);
  311. // namespace specify :
  312. $migrationName = 'test_namespace_specify';
  313. $this->runMigrateControllerAction('create', [$this->migrationNamespace . '\\' . $migrationName], [
  314. 'migrationPath' => $this->migrationPath,
  315. 'migrationNamespaces' => [$this->migrationNamespace]
  316. ]);
  317. $files = FileHelper::findFiles($this->migrationPath);
  318. $fileContent = file_get_contents($files[0]);
  319. $this->assertContains("namespace {$this->migrationNamespace};", $fileContent);
  320. unlink($files[0]);
  321. // no namespace:
  322. $migrationName = 'test_no_namespace';
  323. $this->runMigrateControllerAction('create', [$migrationName], [
  324. 'migrationPath' => $this->migrationPath,
  325. 'migrationNamespaces' => [$this->migrationNamespace]
  326. ]);
  327. $files = FileHelper::findFiles($this->migrationPath);
  328. $fileContent = file_get_contents($files[0]);
  329. $this->assertNotContains("namespace {$this->migrationNamespace};", $fileContent);
  330. }
  331. /**
  332. * @depends testUp
  333. */
  334. public function testNamespaceUp()
  335. {
  336. $this->createNamespaceMigration('nsTest1');
  337. $this->createNamespaceMigration('nsTest2');
  338. $this->runMigrateControllerAction('up', [], [
  339. 'migrationPath' => null,
  340. 'migrationNamespaces' => [$this->migrationNamespace]
  341. ]);
  342. $this->assertMigrationHistory([
  343. 'm*_*_base',
  344. $this->migrationNamespace . '\\M*NsTest1',
  345. $this->migrationNamespace . '\\M*NsTest2',
  346. ]);
  347. }
  348. /**
  349. * @depends testNamespaceUp
  350. * @depends testDownCount
  351. */
  352. public function testNamespaceDownCount()
  353. {
  354. $this->createNamespaceMigration('down1');
  355. $this->createNamespaceMigration('down2');
  356. $controllerConfig = [
  357. 'migrationPath' => null,
  358. 'migrationNamespaces' => [$this->migrationNamespace]
  359. ];
  360. $this->runMigrateControllerAction('up', [], $controllerConfig);
  361. $this->runMigrateControllerAction('down', [1], $controllerConfig);
  362. $this->assertMigrationHistory([
  363. 'm*_*_base',
  364. $this->migrationNamespace . '\\M*Down1',
  365. ]);
  366. }
  367. /**
  368. * @depends testNamespaceUp
  369. * @depends testHistory
  370. */
  371. public function testNamespaceHistory()
  372. {
  373. $controllerConfig = [
  374. 'migrationPath' => null,
  375. 'migrationNamespaces' => [$this->migrationNamespace]
  376. ];
  377. $output = $this->runMigrateControllerAction('history', [], $controllerConfig);
  378. $this->assertContains('No migration', $output);
  379. $this->createNamespaceMigration('history1');
  380. $this->createNamespaceMigration('history2');
  381. $this->runMigrateControllerAction('up', [], $controllerConfig);
  382. $output = $this->runMigrateControllerAction('history', [], $controllerConfig);
  383. $this->assertRegExp('/' . preg_quote($this->migrationNamespace) . '.*History1/s', $output);
  384. $this->assertRegExp('/' . preg_quote($this->migrationNamespace) . '.*History2/s', $output);
  385. }
  386. /**
  387. * @depends testMark
  388. */
  389. public function testNamespaceMark()
  390. {
  391. $controllerConfig = [
  392. 'migrationPath' => null,
  393. 'migrationNamespaces' => [$this->migrationNamespace]
  394. ];
  395. $version = '010101000001';
  396. $this->createNamespaceMigration('mark1', $version);
  397. $this->runMigrateControllerAction('mark', [$this->migrationNamespace . '\\M' . $version], $controllerConfig);
  398. $this->assertMigrationHistory(['m*_base', $this->migrationNamespace . '\\M*Mark1']);
  399. }
  400. /**
  401. * @depends testTo
  402. */
  403. public function testNamespaceTo()
  404. {
  405. $controllerConfig = [
  406. 'migrationPath' => null,
  407. 'migrationNamespaces' => [$this->migrationNamespace]
  408. ];
  409. $version = '020202000020';
  410. $this->createNamespaceMigration('to1', $version);
  411. $this->runMigrateControllerAction('to', [$this->migrationNamespace . '\\M' . $version], $controllerConfig);
  412. $this->assertMigrationHistory(['m*_base', $this->migrationNamespace . '\\M*To1']);
  413. }
  414. /**
  415. * @depends testNamespaceHistory
  416. *
  417. * @see https://github.com/yiisoft/yii2-mongodb/issues/170
  418. */
  419. public function testGetMigrationHistory()
  420. {
  421. $connection = $this->getConnection();
  422. $controllerConfig = [
  423. 'migrationPath' => null,
  424. 'migrationNamespaces' => [$this->migrationNamespace]
  425. ];
  426. $controller = $this->createMigrateController($controllerConfig);
  427. $controller->db = $this->getConnection();
  428. $connection->createCommand()->batchInsert('migration', [
  429. [
  430. 'version' => 'app\migrations\M140506102106One',
  431. 'apply_time' => 10,
  432. ],
  433. [
  434. 'version' => 'app\migrations\M160909083544Two',
  435. 'apply_time' => 10,
  436. ],
  437. [
  438. 'version' => 'app\modules\foo\migrations\M161018124749Three',
  439. 'apply_time' => 10,
  440. ],
  441. [
  442. 'version' => 'app\migrations\M160930135248Four',
  443. 'apply_time' => 20,
  444. ],
  445. [
  446. 'version' => 'app\modules\foo\migrations\M161025123028Five',
  447. 'apply_time' => 20,
  448. ],
  449. [
  450. 'version' => 'app\migrations\M161110133341Six',
  451. 'apply_time' => 20,
  452. ],
  453. ]);
  454. $rows = $this->invokeMethod($controller, 'getMigrationHistory', [10]);
  455. $this->assertSame(
  456. [
  457. 'app\migrations\M161110133341Six',
  458. 'app\modules\foo\migrations\M161025123028Five',
  459. 'app\migrations\M160930135248Four',
  460. 'app\modules\foo\migrations\M161018124749Three',
  461. 'app\migrations\M160909083544Two',
  462. 'app\migrations\M140506102106One',
  463. ],
  464. array_keys($rows)
  465. );
  466. $rows = $this->invokeMethod($controller, 'getMigrationHistory', [4]);
  467. $this->assertSame(
  468. [
  469. 'app\migrations\M161110133341Six',
  470. 'app\modules\foo\migrations\M161025123028Five',
  471. 'app\migrations\M160930135248Four',
  472. 'app\modules\foo\migrations\M161018124749Three',
  473. ],
  474. array_keys($rows)
  475. );
  476. }
  477. /**
  478. * @depends testUp
  479. */
  480. public function testRefreshMigration()
  481. {
  482. if (!is_callable(['yii\console\controllers\BaseMigrateController', 'actionFresh'])) {
  483. $this->markTestSkipped('Method "yii\console\controllers\BaseMigrateController::actionFresh()" does not exist in this Yii framework version.');
  484. }
  485. $connection = $this->getConnection();
  486. $collection = $connection->getCollection('hall_of_fame');
  487. $collection->insert(['name' => 'Qiang Xue']);
  488. $collection->insert(['name' => 'Alexander Makarov']);
  489. $result = $this->runMigrateControllerAction('fresh');
  490. $this->assertContains('Collection hall_of_fame dropped.', $result);
  491. $this->assertContains('No new migrations found. Your system is up-to-date.', $result);
  492. $this->assertEmpty($connection->getDatabase()->listCollections(['name' => $collection->name]));
  493. }
  494. }