Http::response([ ['username' => 'alice', 'uid' => 1000, 'gid' => 1000, 'home_dir' => '/home/alice', 'shell' => '/bin/bash'], ]), 'http://node.test/groups' => Http::response([ ['groupname' => 'dev', 'gid' => 1000, 'members' => ['alice']], ]), 'http://node.test/users/alice/groups' => Http::response(['username' => 'alice', 'groups' => ['dev']]), ]); $admin = $this->admin(); $server = $this->server(); ServerUserBinding::query()->create([ 'user_id' => $admin->id, 'server_resource_id' => $server->id, 'username' => 'alice', 'remote_exists' => true, ]); $response = $this->actingAs($admin, 'api')->getJson('/servers/'.$server->id.'/system-users/meta'); $response ->assertOk() ->assertJsonPath('code', 0) ->assertJsonPath('data.users.0.username', 'alice') ->assertJsonPath('data.groups.0.groupname', 'dev') ->assertJsonPath('data.user_groups.alice.0', 'dev'); } public function test_creating_user_can_create_remote_server_account_and_binding(): void { Http::preventStrayRequests(); Http::fake([ 'http://node.test/users' => Http::response(['status' => 'ok', 'message' => 'User created.'], 201), ]); $admin = $this->admin(); $server = $this->server(); $response = $this->actingAs($admin, 'api')->postJson('/users', [ 'nickname' => 'Alice', 'email' => 'alice@example.com', 'phone' => '13800138000', 'password' => 'secret123', 'server_bindings' => [[ 'server_resource_id' => $server->id, 'username' => 'alice', 'create_remote' => true, 'groups' => [], ]], ]); $response->assertCreated()->assertJsonPath('code', 0); $user = User::query()->where('email', 'alice@example.com')->firstOrFail(); $this->assertDatabaseHas('server_user_bindings', [ 'user_id' => $user->id, 'server_resource_id' => $server->id, 'username' => 'alice', 'remote_exists' => true, ]); Http::assertSent(function (Request $request): bool { return $request->method() === 'POST' && $request->url() === 'http://node.test/users' && $request['username'] === 'alice' && is_string($request['password_hash']) && str_starts_with($request['password_hash'], '$6$'); }); } public function test_deleting_sso_user_does_not_delete_remote_server_user(): void { Http::fake(); $admin = $this->admin(); $server = $this->server(); $target = User::factory()->create(); ServerUserBinding::query()->create([ 'user_id' => $target->id, 'server_resource_id' => $server->id, 'username' => 'target', 'remote_exists' => true, ]); $response = $this->actingAs($admin, 'api')->deleteJson('/users/'.$target->id); $response->assertOk()->assertJsonPath('code', 0); $this->assertDatabaseMissing('server_user_bindings', ['user_id' => $target->id]); Http::assertNothingSent(); } public function test_server_list_includes_bound_username_without_exposing_token(): void { $admin = $this->admin(); $server = $this->server(); $resource = ServerResource::query()->create([ 'parent_id' => $server->id, 'name' => 'ssh', 'display_name' => 'SSH', 'internal_ip' => '10.0.0.10', 'asset_id' => 1, 'account_id' => 2, 'protocols' => ['SSH'], 'is_active' => true, ]); ServerUserBinding::query()->create([ 'user_id' => $admin->id, 'server_resource_id' => $server->id, 'username' => 'admin', 'remote_exists' => true, ]); $response = $this->actingAs($admin, 'api')->getJson('/servers'); $response ->assertOk() ->assertJsonPath('data.data.0.user_api_configured', true) ->assertJsonMissingPath('data.data.0.user_api_token'); $resourcePayload = collect($response->json('data.data'))->firstWhere('id', $resource->id); $this->assertSame('admin', $resourcePayload['server_username'] ?? null); } private function admin(): User { $user = User::factory()->create(); Role::query()->firstOrCreate(['name' => 'admin', 'guard_name' => 'api']); $user->assignRole('admin'); return $user; } private function server(): ServerResource { return ServerResource::query()->create([ 'name' => 'server01', 'display_name' => 'Server 01', 'internal_ip' => '10.0.0.10', 'user_api_base_url' => 'http://node.test', 'user_api_token' => 'secret-token', 'asset_id' => 1, 'account_id' => null, 'protocols' => [], 'is_active' => true, ]); } }