diff --git a/composer.json b/composer.json index c711a99..ce0c555 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "php": "^7.4|^8.0" }, "require-dev": { - "orchestra/testbench": "6.x" + "orchestra/testbench": "6.x", + "doctrine/dbal": "^2.12.1" }, "autoload": { "psr-4": { @@ -30,5 +31,13 @@ "A2Workspace\\DatabasePatcher\\ServiceProvider" ] } + }, + "scripts": { + "test": [ + "vendor/bin/phpunit" + ], + "test-coverage": [ + "vendor/bin/phpunit --coverage-html coverage" + ] } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b9fa7c2..23acb3a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,12 +13,14 @@ stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" > + + + src + + - - ./tests/Unit - - - ./tests/Feature + + ./tests/ diff --git a/src/Commands/DbPatchCommand.php b/src/Commands/DbPatchCommand.php index c9bd80b..c1f1af8 100644 --- a/src/Commands/DbPatchCommand.php +++ b/src/Commands/DbPatchCommand.php @@ -2,10 +2,9 @@ namespace A2Workspace\DatabasePatcher\Commands; +use Illuminate\Support\Str; use Illuminate\Console\Command; use Illuminate\Support\Collection; -use Illuminate\Support\Str; -use Illuminate\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; @@ -47,7 +46,7 @@ class DbPatchCommand extends Command * * @return int */ - public function handle() + public function handle(): int { $file = $this->determinePatchFile(); if (!$file) { @@ -55,36 +54,24 @@ public function handle() } $path = $file->getRealPath(); - $path = Str::after($path, base_path()); - - // 這邊我們判斷 - // 若為回復模式則呼叫滾回命令並傳入補丁檔案路徑 - if ($this->option('revert')) { - $this->info("Running: php artisan migrate:rollback --path={$path}"); - $this->call('migrate:rollback', [ - '--path' => $path, - ]); + if ($this->usingRevertion()) { + return $this->callMigrateCommand($path, 'migrate:rollback'); } - // 呼叫遷移命令並傳入補丁檔案路徑 - else { - $this->info("Running: php artisan migrate --path={$path}"); - - $this->call('migrate', [ - '--path' => $path, - ]); - } - - return 0; + return $this->callMigrateCommand($path); } + // ========================================================================= + // = DeterminePatchFile() + // ========================================================================= + /** * 決定要被使用的檔案 * * @return \Symfony\Component\Finder\SplFileInfo|null */ - protected function determinePatchFile() + protected function determinePatchFile(): ?SplFileInfo { // 取得 patches 目錄的檔案列表,若結果為空則提前終止 $files = $this->getFileList(); @@ -125,7 +112,7 @@ protected function getFilterInput() */ protected function getFileList(): Collection { - $paths = [database_path('patches')]; + $paths = $this->laravel['config']['database.patcher.paths'] ?? database_path('patches'); return collect($paths) ->map(fn ($path) => $this->getFileListInDirectory($path)) @@ -144,6 +131,7 @@ protected function getFileListInDirectory(string $path): array ->filter(function (SplFileInfo $file) { return !in_array($file->getRelativePathname(), $this->excludedNames); }) + ->ignoreDotFiles(true) ->files() ->in($path) ->depth(0) @@ -178,4 +166,39 @@ protected function choiceFromFileList($question, Collection $files): SplFileInfo return $value[0] === $input; })[1]; } + + // ========================================================================= + // = UsingRevertion() + // ========================================================================= + + /** + * 判定是否為 revert 模式 + * + * @return bool + */ + protected function usingRevertion(): bool + { + return $this->input->hasOption('revert') && $this->option('revert'); + } + + // ========================================================================= + // = CallMigrateCommand() + // ========================================================================= + + /** + * 呼叫運行 migrate 指令並傳入路徑 + * + * @param string $path + * @param string $command + * @return int + */ + protected function callMigrateCommand($path, string $command = 'migrate'): int + { + $path = Str::after($path, base_path()); + + $this->info("Running: php artisan {$command} --path={$path}"); + $this->call($command, ['--path' => $path]); + + return 0; + } } diff --git a/tests/CallDbPatchArtisanCommandTest.php b/tests/CallDbPatchArtisanCommandTest.php new file mode 100644 index 0000000..e489b46 --- /dev/null +++ b/tests/CallDbPatchArtisanCommandTest.php @@ -0,0 +1,158 @@ +increments('id'); + $table->string('ian')->unique(); + $table->string('name'); + }); + + if (empty(static::$published)) { + Artisan::call('vendor:publish', [ + '--tag' => '@a2workspace/laravel-database-patcher', + ]); + + static::$published = database_path('patches'); + } + } + + public static function tearDownAfterClass(): void + { + @unlink(static::$published); + @rmdir(static::$published); + } + + // ========================================================================= + // = Tests + // ========================================================================= + + public function test_call_artisan_command() + { + // ===================================================================== + // = Step 1: Install specified patch file. + // ===================================================================== + + $command = $this->artisan('db:patch'); + + $command->expectsChoice( + '選擇補丁檔案', + $this->parseLabel('2022_07_19_000000_add_priority_to_products_table.php'), + [ + $this->parseLabel('2022_07_19_000000_add_priority_to_products_table.php') + ], + ); + + $command->expectsOutput(sprintf( + 'Running: php artisan migrate --path=%s', + $this->resolvePath('/database/patches/2022_07_19_000000_add_priority_to_products_table.php') + )); + + $command->assertExitCode(0); + $command->run(); + + $this->assertDatabaseHas( + 'migrations', + ['migration' => '2022_07_19_000000_add_priority_to_products_table'] + ); + + $this->assertDatabaseTableHasColumn('products', 'priority'); + + // ===================================================================== + // = Step 2: Revert it. + // ===================================================================== + + $command2 = $this->artisan('db:patch', ['--revert' => true]); + + $command2->expectsChoice( + '選擇補丁檔案', + $this->parseLabel('2022_07_19_000000_add_priority_to_products_table.php'), + [ + $this->parseLabel('2022_07_19_000000_add_priority_to_products_table.php') + ], + ); + + $command2->expectsOutput(sprintf( + 'Running: php artisan migrate:rollback --path=%s', + $this->resolvePath('/database/patches/2022_07_19_000000_add_priority_to_products_table.php') + )); + + $command2->assertExitCode(0); + $command2->run(); + + $this->assertDatabaseTableMissingColumn('products', 'priority'); + + $this->assertDatabaseMissing( + 'migrations', + ['migration' => '2022_07_19_000000_add_priority_to_products_table'] + ); + } + + public function test_call_artisan_command_with_filter() + { + $command = $this->artisan('db:patch', [ + 'filter' => 'product', + ]); + + $command->expectsChoice( + '選擇補丁檔案', + $this->parseLabel('2022_07_19_000000_add_priority_to_products_table.php'), + [ + $this->parseLabel('2022_07_19_000000_add_priority_to_products_table.php') + ], + ); + + $command->expectsOutput(sprintf( + 'Running: php artisan migrate --path=%s', + $this->resolvePath('/database/patches/2022_07_19_000000_add_priority_to_products_table.php') + )); + + $command->assertExitCode(0); + $command->run(); + + $this->assertDatabaseHas( + 'migrations', + ['migration' => '2022_07_19_000000_add_priority_to_products_table'] + ); + + $this->assertDatabaseTableHasColumn('products', 'priority'); + } + + public function test_call_artisan_command_with_filter_then_not_found() + { + $command = $this->artisan('db:patch', [ + 'filter' => '__IMIFUMEI__', + ]); + + $command->expectsOutput('找不到符合的補丁檔案'); + $command->assertExitCode(1); + $command->run(); + } + + public function test_call_artisan_command_when_directory_is_empty() + { + $this->app->config->set('database.patcher.paths', []); + + $command = $this->artisan('db:patch'); + + $command->expectsOutput('找不到任何補丁檔案'); + $command->assertExitCode(1); + $command->run(); + } +} diff --git a/tests/TestArtisanCommandHelpers.php b/tests/TestArtisanCommandHelpers.php new file mode 100644 index 0000000..342ceec --- /dev/null +++ b/tests/TestArtisanCommandHelpers.php @@ -0,0 +1,55 @@ + {$option}"; + } + + /** + * @param string $table + * @param string $column + * @return self + */ + private function assertDatabaseTableHasColumn($table, $column) + { + $this->assertTrue( + Schema::hasColumn($table, $column), + sprintf( + 'The table [%s] doesn\'t have the column named %s', + $table, + $column + ) + ); + + return $this; + } + + /** + * @param string $table + * @param string $column + * @return self + */ + private function assertDatabaseTableMissingColumn($table, $column) + { + $this->assertFalse( + Schema::hasColumn($table, $column), + sprintf( + 'The table [%s] have the column named %s', + $table, + $column + ) + ); + + return $this; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..5b4285e --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,51 @@ +$name(...func_get_args()); + }; + + return $ref->bindTo($scope, $scope); + } + + /** + * 處理不同作業系統下的路徑 + * + * @param string $path + * @return string + */ + protected function resolvePath(string $path): string + { + return str_replace('/', DIRECTORY_SEPARATOR, $path); + } +}