diff --git a/app/Console/Commands/ApplicationReady.php b/app/Console/Commands/ApplicationReady.php index eac78802..34ca2d43 100644 --- a/app/Console/Commands/ApplicationReady.php +++ b/app/Console/Commands/ApplicationReady.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Utilities\SystemDatabaseConfiguration; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; use Throwable; @@ -18,7 +19,7 @@ class ApplicationReady extends Command protected $description = '检查应用安装、运行目录和数据库是否已经可用'; - public function handle(): int + public function handle(SystemDatabaseConfiguration $configuration): int { if (! config('app.installed')) { return $this->failed('APP_INSTALLED 未被 Laravel 加载。', self::EXIT_NOT_INSTALLED); @@ -42,7 +43,11 @@ class ApplicationReady extends Command return $this->failed('Laravel 数据库连接失败。', self::EXIT_DATABASE_CONNECTION_FAILED); } - $this->components->info('应用已就绪。'); + if ($configuration->current()['mode'] === 'bootstrap') { + $this->components->info('本地网页和临时引导库已就绪;团队共享数据尚未接入。'); + } else { + $this->components->info('应用运行正常,当前数据库可查询。团队数据接入状态请运行 system-database:verify 检查。'); + } return self::SUCCESS; } diff --git a/app/Console/Commands/VerifySystemDatabase.php b/app/Console/Commands/VerifySystemDatabase.php index 658965b0..52d8ddb0 100644 --- a/app/Console/Commands/VerifySystemDatabase.php +++ b/app/Console/Commands/VerifySystemDatabase.php @@ -15,6 +15,8 @@ class VerifySystemDatabase extends Command public const EXIT_SYSTEM_TABLE_VALIDATION_FAILED = 12; + public const EXIT_SHARED_NOT_CONFIGURED = 13; + protected $signature = 'system-database:verify'; protected $description = '在新的 Laravel 进程中验证当前共享系统数据库'; @@ -30,12 +32,16 @@ class VerifySystemDatabase extends Command ); } - if ( - $settings['mode'] !== 'shared' - || ! config('upstreams.stored_configuration.enabled', false) - ) { + if ($settings['mode'] === 'bootstrap') { + $this->components->warn('当前仍使用本机临时引导库,尚未接入团队共享系统数据库;本次没有测试远程连接。'); + $this->line('下一步:在“系统设置 → 数据库管理 → 系统数据库”中输入一次负责人提供的系统数据库密码。'); + + return self::EXIT_SHARED_NOT_CONFIGURED; + } + + if ($settings['mode'] !== 'shared' || ! config('upstreams.stored_configuration.enabled', false)) { return $this->failed( - '新配置未被 Laravel 加载。', + '共享配置未完整启用,请通过系统数据库连接页面重新保存;不要仅手工修改运行模式。', self::EXIT_CONFIG_NOT_LOADED ); } diff --git a/app/Utilities/SystemDatabaseConfiguration.php b/app/Utilities/SystemDatabaseConfiguration.php index 312100f4..e95eb153 100644 --- a/app/Utilities/SystemDatabaseConfiguration.php +++ b/app/Utilities/SystemDatabaseConfiguration.php @@ -462,6 +462,7 @@ class SystemDatabaseConfiguration VerifySystemDatabase::EXIT_CONFIG_NOT_LOADED => '新配置未被加载', VerifySystemDatabase::EXIT_DATABASE_CONNECTION_FAILED => 'Laravel 数据库连接失败', VerifySystemDatabase::EXIT_SYSTEM_TABLE_VALIDATION_FAILED => '系统表验证失败', + VerifySystemDatabase::EXIT_SHARED_NOT_CONFIGURED => '新配置仍指向本机临时引导库', default => '应用级验证失败', }; } diff --git a/deploy/local-init.sh b/deploy/local-init.sh index a01ff624..e2a32135 100644 --- a/deploy/local-init.sh +++ b/deploy/local-init.sh @@ -397,16 +397,30 @@ fi echo "" echo "=====================================================" -echo "本地测试环境已就绪:http://127.0.0.1:18088" +echo "本地网页可访问:http://127.0.0.1:18088" -db_host="$(read_env_value DB_HOST)" -if [ "$db_host" = "mysql" ]; then - echo "临时登录凭据保存在本机私有文件:$credential_file" - echo "请自行打开该文件,不要把内容提交到 Git、聊天或日志。" - echo "" - echo "下一步:登录后打开“系统设置 → 数据库管理 → 系统数据库”," - echo "点击“连接”,只填写负责人提供的系统数据库密码。" +# Docker healthy 只表示当前连接可用;共享接入必须由新 Laravel 进程实测, +# 不能根据 DB_HOST 不是 mysql 就宣称团队数据已连接。 +if docker compose -f "$compose_file" exec -T -u www-data app \ + php artisan system-database:verify --no-ansi; then + shared_status=0 else - echo "已连接团队共享系统数据库,请使用其中属于自己的正式账号登录。" + shared_status=$? fi + +case "$shared_status" in + 0) + echo "团队共享系统数据库验证通过,请使用其中属于自己的正式账号登录。" + ;; + 13) + echo "团队数据接入尚未完成;后台 worker 启动不代表数据已经同步。" + echo "临时登录凭据保存在本机私有文件:$credential_file" + echo "请登录后输入一次系统数据库密码,连接成功后改用团队正式账号登录。" + ;; + *) + echo "团队共享系统数据库未通过验证;不要将 Docker healthy 当作接入成功。" >&2 + echo "请让 Codex 根据上方失败阶段检查本机配置,保留现有配置和未提交代码。" >&2 + exit "$shared_status" + ;; +esac echo "=====================================================" diff --git a/tests/Feature/Installation/ApplicationReadyTest.php b/tests/Feature/Installation/ApplicationReadyTest.php index 74f5cc4d..947cb0f0 100644 --- a/tests/Feature/Installation/ApplicationReadyTest.php +++ b/tests/Feature/Installation/ApplicationReadyTest.php @@ -18,4 +18,20 @@ class ApplicationReadyTest extends TestCase $this->artisan('app:ready') ->assertExitCode(\App\Console\Commands\ApplicationReady::EXIT_NOT_INSTALLED); } + + public function test_bootstrap_stays_healthy_without_claiming_shared_data_is_ready(): void + { + config(['app.installed' => true, 'system_database.mode' => 'bootstrap']); + $this->artisan('app:ready') + ->expectsOutputToContain('团队共享数据尚未接入') + ->assertSuccessful(); + } + + public function test_shared_application_health_does_not_claim_upstream_sync_success(): void + { + config(['app.installed' => true, 'system_database.mode' => 'shared']); + $this->artisan('app:ready') + ->expectsOutputToContain('团队数据接入状态请运行 system-database:verify') + ->assertSuccessful(); + } } diff --git a/tests/Feature/Installation/VerifySystemDatabaseTest.php b/tests/Feature/Installation/VerifySystemDatabaseTest.php index 95284d83..059cba2a 100644 --- a/tests/Feature/Installation/VerifySystemDatabaseTest.php +++ b/tests/Feature/Installation/VerifySystemDatabaseTest.php @@ -10,7 +10,7 @@ use Tests\TestCase; class VerifySystemDatabaseTest extends TestCase { - public function test_verify_command_reports_configuration_not_loaded(): void + public function test_bootstrap_is_not_reported_as_a_failed_remote_connection(): void { $configuration = Mockery::mock(SystemDatabaseConfiguration::class); $configuration->shouldReceive('current')->once()->andReturn([ @@ -19,9 +19,42 @@ class VerifySystemDatabaseTest extends TestCase $this->app->instance(SystemDatabaseConfiguration::class, $configuration); $this->artisan('system-database:verify') + ->expectsOutputToContain('尚未接入团队共享系统数据库;本次没有测试远程连接') + ->assertExitCode(VerifySystemDatabase::EXIT_SHARED_NOT_CONFIGURED); + } + + public function test_shared_mode_with_disabled_stored_configuration_is_not_ready(): void + { + config(['upstreams.stored_configuration.enabled' => false]); + $configuration = Mockery::mock(SystemDatabaseConfiguration::class); + $configuration->shouldReceive('current')->once()->andReturn(['mode' => 'shared']); + $this->app->instance(SystemDatabaseConfiguration::class, $configuration); + $this->artisan('system-database:verify') + ->expectsOutputToContain('共享配置未完整启用') ->assertExitCode(VerifySystemDatabase::EXIT_CONFIG_NOT_LOADED); } + public function test_current_configuration_failure_remains_a_configuration_error(): void + { + $configuration = Mockery::mock(SystemDatabaseConfiguration::class); + $configuration->shouldReceive('current')->once()->andThrow(new RuntimeException('private details')); + $this->app->instance(SystemDatabaseConfiguration::class, $configuration); + $this->artisan('system-database:verify') + ->expectsOutputToContain('新配置未被 Laravel 加载') + ->assertExitCode(VerifySystemDatabase::EXIT_CONFIG_NOT_LOADED); + } + + public function test_shared_success_requires_connection_and_system_table_verification(): void + { + $this->enableSharedMode(); + $settings = ['mode' => 'shared', 'connection' => config('database.default')]; + $configuration = Mockery::mock(SystemDatabaseConfiguration::class); + $configuration->shouldReceive('current')->once()->andReturn($settings); + $configuration->shouldReceive('probe')->once()->with($settings)->andReturn([]); + $this->app->instance(SystemDatabaseConfiguration::class, $configuration); + $this->artisan('system-database:verify')->expectsOutputToContain('共享系统数据库已通过应用级验证')->assertSuccessful(); + } + public function test_verify_command_reports_database_connection_failure(): void { $this->enableSharedMode(); diff --git a/tests/Unit/Deployment/LocalSharedDatabaseStatusTest.php b/tests/Unit/Deployment/LocalSharedDatabaseStatusTest.php new file mode 100644 index 00000000..13c6b4a5 --- /dev/null +++ b/tests/Unit/Deployment/LocalSharedDatabaseStatusTest.php @@ -0,0 +1,39 @@ +assertNotFalse($start); + $process = new Process(['bash', '-c', + 'set -eu; compose_file=fixture; credential_file=fixture; docker() { return ' . $verificationExit . '; }; ' + . substr($script, $start), + ]); + $process->setTimeout(5); + $process->run(); + $this->assertSame($expectedExit, $process->getExitCode()); + $this->assertStringContainsString($message, $process->getOutput() . $process->getErrorOutput()); + if ($verificationExit !== 0) { + $this->assertStringNotContainsString('团队共享系统数据库验证通过', $process->getOutput()); + } + } + + public static function verificationStates(): array + { + return [ + 'bootstrap' => [13, 0, '团队数据接入尚未完成'], + 'shared success' => [0, 0, '团队共享系统数据库验证通过'], + 'config failure' => [10, 10, '未通过验证'], + 'network failure' => [11, 11, '未通过验证'], + 'schema failure' => [12, 12, '未通过验证'], + ]; + } +}