user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $connection = (string) config('database.default'); config(["database.connections.$connection.password" => 'shared-system-database-secret']); } public function test_super_admin_can_add_multiple_encrypted_database_configurations(): void { $this->loginAs() ->post(route('system-settings.databases.store'), $this->payload([ 'name' => 'JianSun 测试库', 'source_key' => 'jiansun', 'password' => 'first-secret', ])) ->assertRedirect(route('system-settings.databases.index')); $this->post(route('system-settings.databases.store'), $this->payload([ 'name' => '云数据库二号', 'source_key' => '', 'host' => '127.0.0.2', 'password' => 'second-secret', ]))->assertRedirect(route('system-settings.databases.index')); $this->assertSame(2, DatabaseConnection::where('company_id', 0)->count()); $connection = DatabaseConnection::where('source_key', 'jiansun')->firstOrFail(); $this->assertSame('first-secret', $connection->password); $this->assertNotSame( 'first-secret', DB::table('system_database_connections')->where('id', $connection->id)->value('password') ); $sharedPassword = DB::table('system_database_connections') ->where('id', $connection->id) ->value('shared_password'); $this->assertIsString($sharedPassword); $this->assertNotSame('first-secret', $sharedPassword); $this->assertSame( 'first-secret', app(UpstreamCredentialCipher::class)->decrypt($sharedPassword) ); $sources = app(UpstreamSourceRegistry::class); $this->assertSame('127.0.0.1', $sources->value('jiansun', 'connection.host')); $this->assertSame('first-secret', $sources->value('jiansun', 'connection.password')); $this->get(route('system-settings.databases.index')) ->assertOk() ->assertSeeText('JianSun 测试库') ->assertSeeText('云数据库二号') ->assertDontSee('first-secret'); } public function test_editing_without_password_preserves_existing_password(): void { $connection = $this->connection('original-secret'); $this->loginAs() ->patch(route('system-settings.databases.update', $connection->id), $this->payload([ 'name' => $connection->name, 'password' => '', 'host' => '127.0.0.2', ])) ->assertRedirect(route('system-settings.databases.index')); $connection->refresh(); $this->assertSame('original-secret', $connection->password); $this->assertSame('127.0.0.2', $connection->host); $this->get(route('system-settings.databases.edit', $connection->id)) ->assertOk() ->assertDontSee('original-secret'); } public function test_local_password_is_used_when_shared_record_uses_another_app_key(): void { $connection = $this->connection('shared-secret'); $connection->update(['source_key' => 'jiansun']); $originalEncrypter = $this->app->make('encrypter'); try { $this->app->instance( 'encrypter', new Encrypter(str_repeat('x', 32), (string) config('app.cipher')) ); config([ 'upstreams.local_configuration.enabled' => true, 'jiansun.connection.host' => 'local-db.test', 'jiansun.connection.database' => 'local_database', 'jiansun.connection.username' => 'local_user', 'jiansun.connection.password' => 'local-secret', 'jiansun.connection.port' => 11433, ]); $sources = new UpstreamSourceRegistry(new UpstreamStoredConfiguration()); $this->assertSame( 'local-secret', $sources->value('jiansun', 'connection.password') ); $this->assertSame('local-db.test', $sources->value('jiansun', 'connection.host')); $this->assertSame('local_database', $sources->value('jiansun', 'connection.database')); $this->assertSame('local_user', $sources->value('jiansun', 'connection.username')); $this->assertSame(11433, $sources->value('jiansun', 'connection.port')); } finally { $this->app->instance('encrypter', $originalEncrypter); } } public function test_shared_password_is_available_with_a_different_app_key(): void { $connection = $this->connection('shared-secret'); $connection->update(['source_key' => 'jiansun']); $originalEncrypter = $this->app->make('encrypter'); try { $this->app->instance( 'encrypter', new Encrypter(str_repeat('x', 32), (string) config('app.cipher')) ); config(['upstreams.local_configuration.enabled' => false]); $sources = new UpstreamSourceRegistry(new UpstreamStoredConfiguration()); // 兼容尚未重建的旧本地镜像:旧模块仍直接读取 password 属性。 $this->assertSame('shared-secret', $connection->fresh()->password); $this->assertSame( 'shared-secret', $sources->value('jiansun', 'connection.password') ); } finally { $this->app->instance('encrypter', $originalEncrypter); } } public function test_whole_connection_comes_from_shared_system_database(): void { $connection = $this->connection('shared-secret'); $connection->update([ 'source_key' => 'jiansun', 'host' => 'shared-db.test', 'port' => 14330, 'database_name' => 'shared_database', 'username' => 'shared_reader', ]); config([ 'upstreams.local_configuration.enabled' => false, 'jiansun.connection' => [ 'host' => null, 'port' => 1433, 'database' => null, 'username' => null, 'password' => null, 'odbc_driver' => 'FreeTDS', 'tds_version' => '7.4', 'encryption' => 'off', 'connect_timeout' => 5, 'query_timeout' => 15, 'application_intent' => '', ], ]); $sources = new UpstreamSourceRegistry(new UpstreamStoredConfiguration()); $resolved = $sources->value('jiansun', 'connection', []); $this->assertSame('shared-db.test', $resolved['host']); $this->assertSame(14330, $resolved['port']); $this->assertSame('shared_database', $resolved['database']); $this->assertSame('shared_reader', $resolved['username']); $this->assertSame('shared-secret', $resolved['password']); $this->assertSame('off', $resolved['encryption']); } public function test_global_wildcard_scope_is_rejected(): void { $this->withExceptionHandling(); $this->loginAs() ->post(route('system-settings.databases.store'), $this->payload([ 'allowed_scopes' => '*', ])) ->assertSessionHasErrors('allowed_scopes'); $this->assertDatabaseMissing('system_database_connections', ['name' => '测试云数据库']); } public function test_regular_admin_can_open_database_settings(): void { $this->user->roles()->sync([ Role::where('name', 'admin')->firstOrFail()->id, ]); $this->loginAs() ->get(route('system-settings.databases.index')) ->assertOk() ->assertSeeText('财务系统配置数据库') ->assertSeeText('固定 · 不可删除') ->assertSeeText('云数据库'); $this->get(route('system-settings.databases.system.edit')) ->assertOk() ->assertSeeText('测试并保存'); } public function test_database_page_separates_system_and_cloud_database_configuration(): void { $connectionName = (string) config('database.default'); config(["database.connections.$connectionName.password" => 'system-database-test-secret']); $this->loginAs() ->get(route('system-settings.databases.index')) ->assertOk() ->assertSee('data-system-database="fixed"', false) ->assertSeeTextInOrder([ '系统数据库', '财务系统配置数据库', '固定 · 不可删除', '云数据库', '添加云数据库', ]); $this->get(route('system-settings.databases.system.edit')) ->assertOk() ->assertSeeText('测试并保存') ->assertDontSee('system-database-test-secret'); } public function test_cloud_database_create_page_opens_inside_system_settings(): void { $this->loginAs() ->get(route('system-settings.databases.create')) ->assertOk() ->assertSeeText('新增数据库') ->assertDontSeeText('Not able to create a new user.'); } public function test_production_stored_configuration_requires_explicit_private_flag(): void { $connection = $this->connection('production-secret'); $connection->update(['source_key' => 'jiansun']); $originalEnvironment = $this->app->environment(); try { $this->app['env'] = 'production'; config(['upstreams.stored_configuration.enabled' => false]); $disabled = app(UpstreamStoredConfiguration::class); $this->assertFalse($disabled->editable()); $this->assertNull($disabled->find('jiansun')); config(['upstreams.stored_configuration.enabled' => true]); $enabled = new UpstreamStoredConfiguration(); $this->assertTrue($enabled->editable()); $this->assertSame($connection->id, $enabled->find('jiansun')?->id); } finally { $this->app['env'] = $originalEnvironment; } } private function payload(array $overrides = []): array { return array_merge([ 'name' => '测试云数据库', 'source_key' => '', 'host' => '127.0.0.1', 'port' => 1433, 'database_name' => 'test_finance', 'username' => 'readonly_user', 'password' => 'secret', 'odbc_driver' => 'FreeTDS', 'tds_version' => '7.4', 'connect_timeout' => 3, 'query_timeout' => 8, 'allowed_scopes' => 'supplier.options', 'enabled' => '1', ], $overrides); } private function connection(string $password): DatabaseConnection { return DatabaseConnection::create([ 'company_id' => 0, 'source_key' => null, 'name' => '已有云数据库', 'driver' => 'sqlsrv', 'host' => '127.0.0.1', 'port' => 1433, 'database_name' => 'test_finance', 'username' => 'readonly_user', 'password' => $password, 'odbc_driver' => 'FreeTDS', 'tds_version' => '7.4', 'connect_timeout' => 3, 'query_timeout' => 8, 'allowed_scopes' => ['supplier.options'], 'charset' => 'UTF-8', 'enabled' => true, ]); } }