Add tests for channel conversion and event handling in Messaging

- Implement `test_convert_channels_rewrites_account_action_suffixes` to ensure
  that account action suffixes are correctly rewritten to user-scoped channels.
- Add `test_convert_channels_drops_account_actions_for_guest` to verify that
  account actions are dropped for guests without a user ID.
- Introduce `test_from_payload_does_not_suffix_account_for_nested_user_events`
  to confirm that nested user events do not leak action suffixes onto account channels.
This commit is contained in:
ArnabChatterjee20k
2026-04-27 16:40:15 +05:30
parent e6d5c216eb
commit 340ce9d56b
3 changed files with 239 additions and 81 deletions
+35 -5
View File
@@ -399,7 +399,11 @@ class Realtime extends MessagingAdapter
/**
* Converts the channels from the Query Params into an array.
* Also renames the account channel to account.USER_ID and removes all illegal account channel variations.
* Also renames the account channel to account.USER_ID, rewrites action-suffixed
* account variants (`account.create`, `account.update`, `account.upsert`,
* `account.delete`) to `account.USER_ID.{action}` so they match the channels
* fromPayload() publishes for top-level user events, and removes all other
* illegal account channel variations (e.g. another user's `account.{otherId}`).
*/
public static function convertChannels(array $channels, string $userId): array
{
@@ -407,15 +411,26 @@ class Realtime extends MessagingAdapter
foreach ($channels as $key => $value) {
switch (true) {
case str_starts_with($key, 'account.'):
unset($channels[$key]);
break;
case $key === 'account':
if (! empty($userId)) {
$channels['account.'.$userId] = $value;
}
break;
case \in_array(\substr($key, \strlen('account.')), self::SUPPORTED_ACTIONS, true) && str_starts_with($key, 'account.'):
// Translate `account.{action}` into the user-scoped `account.{userId}.{action}`
// so a subscriber only receives their own account events. Without the rewrite
// the literal `account.{action}` channel would match every user's events.
unset($channels[$key]);
if (! empty($userId)) {
$action = \substr($key, \strlen('account.'));
$channels['account.'.$userId.'.'.$action] = $value;
}
break;
case str_starts_with($key, 'account.'):
unset($channels[$key]);
break;
}
}
@@ -672,6 +687,21 @@ class Realtime extends MessagingAdapter
$action = $parts[$count - 2];
}
// The `users` branch emits only user-level account channels
// (`account`, `account.{userId}`) regardless of event depth, so nested events
// like `users.U.sessions.S.create` or `users.U.challenges.C.create` would
// otherwise be suffixed as `account.create` — making a subscription to
// `account.create` receive unrelated session/challenge/recovery/verification
// events. Restrict suffixing to top-level user events where the action sits
// at parts[2] (`users.U.create`, `users.U.update.email`, etc.).
if (
$action !== null
&& ($parts[0] ?? null) === 'users'
&& ($parts[2] ?? null) !== $action
) {
$action = null;
}
if ($action !== null && ! empty($channels)) {
$augmented = $channels;
foreach ($channels as $channel) {