Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -30,5 +31,13 @@
"A2Workspace\\DatabasePatcher\\ServiceProvider"
]
}
},
"scripts": {
"test": [
"vendor/bin/phpunit"
],
"test-coverage": [
"vendor/bin/phpunit --coverage-html coverage"
]
}
}
12 changes: 7 additions & 5 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
>
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
<testsuite name="Package Test">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<php>
Expand Down
71 changes: 47 additions & 24 deletions src/Commands/DbPatchCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -47,44 +46,32 @@ class DbPatchCommand extends Command
*
* @return int
*/
public function handle()
public function handle(): int
{
$file = $this->determinePatchFile();
if (!$file) {
return 1;
}

$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();
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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;
}
}
158 changes: 158 additions & 0 deletions tests/CallDbPatchArtisanCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

namespace Tests;

use Illuminate\Testing\PendingCommand;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Database\Schema\Blueprint;
use Tests\TestCase;

class CallDbPatchArtisanCommandTest extends TestCase
{
use TestArtisanCommandHelpers;

protected static $published;

protected function setUp(): void
{
parent::setUp();

Schema::create('products', function (Blueprint $table) {
$table->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();
}
}
55 changes: 55 additions & 0 deletions tests/TestArtisanCommandHelpers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Tests;

use Illuminate\Support\Facades\Schema;

trait TestArtisanCommandHelpers
{
/**
* @param string $option
* @return string
*/
private function parseLabel(string $option): string
{
return "-> {$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;
}
}
Loading