241 lines
10 KiB
PHP
241 lines
10 KiB
PHP
<?php
|
||
|
||
namespace Tests\Unit\Utilities;
|
||
|
||
use App\Exceptions\SystemDatabaseVerificationException;
|
||
use App\Utilities\SystemDatabaseConfiguration;
|
||
use RuntimeException;
|
||
use Tests\TestCase;
|
||
|
||
class SystemDatabasePersistenceTest extends TestCase
|
||
{
|
||
public function test_switch_replaces_all_legacy_connection_routes_and_duplicate_keys(): void
|
||
{
|
||
$environmentPath = storage_path('framework/testing-legacy-routes.env');
|
||
$original = "DB_HOST=mysql\nDB_HOST_READ=old-read\nDB_HOST_WRITE=old-write\n"
|
||
. "DATABASE_URL=mysql://fixture:fixture@old-url/old_database\nDB_SOCKET=/tmp/old.sock\n"
|
||
. "DB_HOST=old-duplicate\nSYSTEM_DATABASE_MODE=bootstrap\nUNRELATED_OPTION=keep\n"
|
||
. "BUSINESS_MIRROR_DB_HOST=old-mirror\nBUSINESS_MIRROR_DB_HOST_READ=old-mirror-read\nBUSINESS_MIRROR_DB_HOST_WRITE=old-mirror-write\nBUSINESS_MIRROR_DB_PORT=13306\nBUSINESS_MIRROR_DB_SOCKET=/tmp/mirror.sock\n";
|
||
file_put_contents($environmentPath, $original);
|
||
$configuration = new class($environmentPath) extends SystemDatabaseConfiguration {
|
||
public array $verified = [];
|
||
protected function runFreshVerification(string $command): void
|
||
{
|
||
$this->verified = $this->freshVerificationEnvironment();
|
||
}
|
||
protected function clearConfigurationCache(): void {}
|
||
};
|
||
try {
|
||
$backup = $configuration->persist([
|
||
'host' => 'shared.example.test', 'port' => 8443, 'database' => 'shared_system',
|
||
'username' => 'fixture_user', 'password' => 'fixture_password', 'prefix' => '',
|
||
'tls_required' => true, 'tls_verify_server_cert' => false,
|
||
'mirror_database' => 'caiwu_business_mirror',
|
||
]);
|
||
$saved = \Dotenv\Dotenv::parse(file_get_contents($environmentPath));
|
||
$this->assertSame('shared.example.test', $saved['BUSINESS_MIRROR_DB_HOST']);
|
||
$this->assertSame('shared.example.test', $saved['BUSINESS_MIRROR_DB_HOST_READ']);
|
||
$this->assertSame('shared.example.test', $saved['BUSINESS_MIRROR_DB_HOST_WRITE']);
|
||
$this->assertSame('8443', $saved['BUSINESS_MIRROR_DB_PORT']);
|
||
$this->assertSame('', $saved['BUSINESS_MIRROR_DB_SOCKET']);
|
||
$this->assertSame('caiwu_business_mirror', $saved['BUSINESS_MIRROR_DB_DATABASE']);
|
||
$this->assertSame('fixture_password', $configuration->verified['BUSINESS_MIRROR_DB_PASSWORD']);
|
||
foreach (['DB_HOST', 'DB_HOST_READ', 'DB_HOST_WRITE'] as $key) {
|
||
$this->assertSame('shared.example.test', $saved[$key]);
|
||
$this->assertSame('shared.example.test', $configuration->verified[$key]);
|
||
}
|
||
$this->assertSame('', $saved['DATABASE_URL']);
|
||
$this->assertSame('', $saved['DB_SOCKET']);
|
||
$this->assertSame('keep', $saved['UNRELATED_OPTION']);
|
||
$this->assertSame($original, file_get_contents($backup));
|
||
} finally {
|
||
@unlink($environmentPath);
|
||
if (isset($backup)) @unlink($backup);
|
||
}
|
||
}
|
||
|
||
public function test_failed_fresh_process_verification_restores_original_environment(): void
|
||
{
|
||
$environmentPath = storage_path('framework/testing-system-database.env');
|
||
$original = "DB_HOST=mysql\nDB_DATABASE=akaunting\nSYSTEM_DATABASE_MODE=bootstrap\n";
|
||
file_put_contents($environmentPath, $original);
|
||
|
||
$configuration = new class($environmentPath) extends SystemDatabaseConfiguration {
|
||
public array $commands = [];
|
||
|
||
protected function runFreshVerification(string $command): void
|
||
{
|
||
$this->commands[] = $command;
|
||
|
||
if ($command === 'system-database:verify') {
|
||
throw new SystemDatabaseVerificationException('系统表验证失败');
|
||
}
|
||
}
|
||
};
|
||
|
||
try {
|
||
$configuration->persist([
|
||
'host' => 'shared-db.test',
|
||
'port' => 3306,
|
||
'database' => 'shared_system',
|
||
'username' => 'shared_writer',
|
||
'password' => 'shared-secret',
|
||
'prefix' => 'ak_',
|
||
'tls_required' => true,
|
||
'tls_verify_server_cert' => false,
|
||
'tls_ca' => '',
|
||
]);
|
||
|
||
$this->fail('验证失败时应抛出异常。');
|
||
} catch (RuntimeException $exception) {
|
||
$this->assertSame(
|
||
'新配置未通过应用级验证(系统表验证失败),已自动恢复原系统数据库。',
|
||
$exception->getMessage()
|
||
);
|
||
$this->assertSame($original, file_get_contents($environmentPath));
|
||
$this->assertSame(['system-database:verify', 'app:ready'], $configuration->commands);
|
||
} finally {
|
||
@unlink($environmentPath);
|
||
}
|
||
}
|
||
|
||
public function test_root_owned_or_invalid_backup_directory_uses_writable_storage_fallback(): void
|
||
{
|
||
$environmentPath = storage_path('framework/testing-system-database-fallback.env');
|
||
$blockedBackupPath = storage_path('framework/testing-blocked-backup-path');
|
||
file_put_contents($environmentPath, "DB_HOST=mysql\nDB_DATABASE=akaunting\n");
|
||
file_put_contents($blockedBackupPath, 'not-a-directory');
|
||
|
||
$configuration = new class($environmentPath, $blockedBackupPath) extends SystemDatabaseConfiguration {
|
||
protected function runFreshVerification(string $command): void
|
||
{
|
||
}
|
||
};
|
||
|
||
try {
|
||
$backupPath = $configuration->persist([
|
||
'host' => 'shared-db.test',
|
||
'port' => 3306,
|
||
'database' => 'shared_system',
|
||
'username' => 'shared_writer',
|
||
'password' => 'shared-secret',
|
||
'prefix' => 'ak_',
|
||
'tls_required' => true,
|
||
'tls_verify_server_cert' => false,
|
||
'tls_ca' => '',
|
||
]);
|
||
|
||
$this->assertSame(storage_path('app'), dirname($backupPath));
|
||
$this->assertFileExists($backupPath);
|
||
} finally {
|
||
@unlink($environmentPath);
|
||
@unlink($blockedBackupPath);
|
||
|
||
if (isset($backupPath)) {
|
||
@unlink($backupPath);
|
||
}
|
||
}
|
||
}
|
||
|
||
public function test_fresh_verification_uses_values_from_the_just_saved_environment(): void
|
||
{
|
||
$environmentPath = storage_path('framework/testing-fresh-verification.env');
|
||
file_put_contents($environmentPath, implode("\n", [
|
||
'DB_HOST="new-shared-host.test"',
|
||
'DB_DATABASE="new_shared_database"',
|
||
'DB_PASSWORD="new-shared-password"',
|
||
'SYSTEM_DATABASE_MODE="shared"',
|
||
'UPSTREAM_STORED_CONFIG_ENABLED="true"',
|
||
'UPSTREAM_LOCAL_CONFIG_ENABLED="false"',
|
||
'',
|
||
]));
|
||
|
||
try {
|
||
$configuration = new class($environmentPath) extends SystemDatabaseConfiguration {
|
||
public function verificationEnvironment(): array
|
||
{
|
||
return $this->freshVerificationEnvironment();
|
||
}
|
||
};
|
||
|
||
$environment = $configuration->verificationEnvironment();
|
||
|
||
$this->assertSame('new-shared-host.test', $environment['DB_HOST']);
|
||
$this->assertSame('new_shared_database', $environment['DB_DATABASE']);
|
||
$this->assertSame('new-shared-password', $environment['DB_PASSWORD']);
|
||
$this->assertSame('shared', $environment['SYSTEM_DATABASE_MODE']);
|
||
$this->assertSame('true', $environment['UPSTREAM_STORED_CONFIG_ENABLED']);
|
||
$this->assertSame('false', $environment['UPSTREAM_LOCAL_CONFIG_ENABLED']);
|
||
$this->assertFalse($environment['DB_PREFIX']);
|
||
$this->assertFalse($environment['DATABASE_URL']);
|
||
$this->assertFalse($environment['DB_HOST_READ']);
|
||
$this->assertFalse($environment['DB_HOST_WRITE']);
|
||
$this->assertFalse($environment['DB_SOCKET']);
|
||
} finally {
|
||
@unlink($environmentPath);
|
||
}
|
||
}
|
||
|
||
public function test_php_cli_finder_returns_an_executable_for_fresh_verification(): void
|
||
{
|
||
$configuration = new class extends SystemDatabaseConfiguration {
|
||
public function executable(): string
|
||
{
|
||
return $this->phpExecutable();
|
||
}
|
||
};
|
||
|
||
$executable = $configuration->executable();
|
||
|
||
$this->assertNotSame('', $executable);
|
||
$this->assertTrue(is_executable($executable));
|
||
$this->assertSame('php', pathinfo($executable, PATHINFO_FILENAME));
|
||
}
|
||
|
||
public function test_failed_rollback_reports_candidate_and_rollback_stages(): void
|
||
{
|
||
$environmentPath = storage_path('framework/testing-system-database-rollback.env');
|
||
$original = "DB_HOST=mysql\nDB_DATABASE=akaunting\nSYSTEM_DATABASE_MODE=bootstrap\n";
|
||
file_put_contents($environmentPath, $original);
|
||
|
||
$configuration = new class($environmentPath) extends SystemDatabaseConfiguration {
|
||
protected function runFreshVerification(string $command): void
|
||
{
|
||
throw new SystemDatabaseVerificationException(
|
||
$command === 'system-database:verify'
|
||
? 'Laravel 数据库连接失败'
|
||
: '运行目录不可写'
|
||
);
|
||
}
|
||
|
||
protected function clearConfigurationCache(): void
|
||
{
|
||
}
|
||
};
|
||
|
||
try {
|
||
$configuration->persist([
|
||
'host' => 'shared-db.test',
|
||
'port' => 3306,
|
||
'database' => 'shared_system',
|
||
'username' => 'shared_writer',
|
||
'password' => 'shared-secret',
|
||
'prefix' => 'ak_',
|
||
'tls_required' => true,
|
||
'tls_verify_server_cert' => false,
|
||
'tls_ca' => '',
|
||
]);
|
||
|
||
$this->fail('新旧配置验证均失败时应抛出异常。');
|
||
} catch (RuntimeException $exception) {
|
||
$this->assertSame(
|
||
'新配置未通过应用级验证(Laravel 数据库连接失败),旧配置文件已经恢复,但旧配置复核失败(运行目录不可写)。',
|
||
$exception->getMessage()
|
||
);
|
||
$this->assertSame($original, file_get_contents($environmentPath));
|
||
} finally {
|
||
@unlink($environmentPath);
|
||
}
|
||
}
|
||
}
|