This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
use Illuminate\Foundation\Testing\TestCase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use RuntimeException;
|
||||
|
||||
// All database calls are mocked. No migrations, cleanup or real database access.
|
||||
class CheckDevelopmentEnvironmentTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
config(['database.default' => 'pgsql', 'app.key' => 'test-key']);
|
||||
Schema::swap(Mockery::mock(Builder::class));
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Sleep::fake(false);
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
private function expectSchemaChecks(): void
|
||||
{
|
||||
DB::shouldReceive('selectOne')->with("SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'postgis') AS installed")
|
||||
->once()->andReturn((object) ['installed' => true]);
|
||||
Schema::shouldReceive('hasTable')->times(7)->andReturn(true);
|
||||
}
|
||||
|
||||
public function test_recovery_is_retried_and_startup_succeeds_without_exposing_the_exception(): void
|
||||
{
|
||||
Sleep::fake();
|
||||
DB::shouldReceive('selectOne')->with('SELECT 1')->once()
|
||||
->andThrow(new RuntimeException('SQLSTATE[08006] FATAL: the database system is in recovery mode; secret-password'));
|
||||
DB::shouldReceive('purge')->once();
|
||||
DB::shouldReceive('selectOne')->with('SELECT 1')->once()->andReturn((object) ['value' => 1]);
|
||||
$this->expectSchemaChecks();
|
||||
|
||||
$this->artisan('portal:dev-check', ['--wait' => 10])
|
||||
->expectsOutputToContain('[AGUARDA] PostgreSQL em arranque ou recuperação')
|
||||
->doesntExpectOutputToContain('secret-password')
|
||||
->expectsOutput('[OK] Ligação à base de dados')
|
||||
->assertSuccessful();
|
||||
Sleep::assertSleptTimes(1);
|
||||
}
|
||||
|
||||
public function test_recovery_without_wait_fails_with_an_actionable_diagnosis(): void
|
||||
{
|
||||
DB::shouldReceive('selectOne')->with('SELECT 1')->once()
|
||||
->andThrow(new RuntimeException('SQLSTATE[57P03] server starting'));
|
||||
DB::shouldReceive('purge')->never();
|
||||
|
||||
$this->artisan('portal:dev-check')
|
||||
->expectsOutputToContain('PostgreSQL em arranque ou recuperação')
|
||||
->expectsOutputToContain('./deploy/local/start.ps1')
|
||||
->expectsOutput('[FALHA] Ligação à base de dados')
|
||||
->assertFailed();
|
||||
}
|
||||
|
||||
public function test_persistent_unavailability_stops_at_the_deadline(): void
|
||||
{
|
||||
DB::shouldReceive('selectOne')->with('SELECT 1')->twice()
|
||||
->andThrow(new RuntimeException('SQLSTATE[08006] connection refused'));
|
||||
DB::shouldReceive('purge')->once();
|
||||
|
||||
$this->artisan('portal:dev-check', ['--wait' => 1])
|
||||
->expectsOutputToContain('[AGUARDA] PostgreSQL indisponível')
|
||||
->expectsOutput('[FALHA] Ligação à base de dados')
|
||||
->assertFailed();
|
||||
}
|
||||
|
||||
/** @return array<string, array{string, string}> */
|
||||
public static function permanentFailures(): array
|
||||
{
|
||||
return [
|
||||
'authentication behind generic PDO state' => ['SQLSTATE[08006] password authentication failed secret-password', 'Autenticação PostgreSQL recusada'],
|
||||
'authentication SQLSTATE' => ['SQLSTATE[28P01] secret-password', 'Autenticação PostgreSQL recusada'],
|
||||
'missing database behind generic PDO state' => ['SQLSTATE[08006] database "missing" does not exist secret-password', 'A base PostgreSQL configurada não existe'],
|
||||
'missing database SQLSTATE' => ['SQLSTATE[3D000] secret-password', 'A base PostgreSQL configurada não existe'],
|
||||
'unknown failure' => ['unexpected secret-password', 'Falha PostgreSQL não transitória'],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('permanentFailures')]
|
||||
public function test_configuration_failures_do_not_wait_or_expose_credentials(string $message, string $diagnosis): void
|
||||
{
|
||||
Sleep::fake();
|
||||
$error = new QueryException('pgsql', 'SELECT 1', [], new RuntimeException($message));
|
||||
DB::shouldReceive('selectOne')->with('SELECT 1')->once()->andThrow($error);
|
||||
DB::shouldReceive('purge')->never();
|
||||
|
||||
$this->artisan('portal:dev-check', ['--wait' => 900])
|
||||
->expectsOutputToContain($diagnosis)
|
||||
->doesntExpectOutputToContain('secret-password')
|
||||
->assertFailed();
|
||||
Sleep::assertNeverSlept();
|
||||
}
|
||||
|
||||
public function test_schema_errors_are_not_reported_as_connection_failures(): void
|
||||
{
|
||||
DB::shouldReceive('selectOne')->with('SELECT 1')->once()->andReturn((object) ['value' => 1]);
|
||||
DB::shouldReceive('selectOne')->with("SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'postgis') AS installed")
|
||||
->once()->andThrow(new RuntimeException('permission denied secret-password'));
|
||||
|
||||
$this->artisan('portal:dev-check')
|
||||
->expectsOutput('[OK] Ligação à base de dados')
|
||||
->expectsOutput('[FALHA] Esquema da base de dados verificável')
|
||||
->doesntExpectOutputToContain('secret-password')
|
||||
->assertFailed();
|
||||
}
|
||||
|
||||
public function test_an_invalid_wait_is_rejected_before_database_access(): void
|
||||
{
|
||||
DB::shouldReceive('selectOne')->never();
|
||||
|
||||
$this->artisan('portal:dev-check', ['--wait' => -1])
|
||||
->expectsOutput('--wait deve ser um inteiro entre 0 e 3600 segundos.')
|
||||
->assertExitCode(2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user