fix: Fix unit test failures for RuntimeQuery and Messaging tests

- Add missing isSelectAll() method to RuntimeQuery class
- Update RuntimeQueryTest to use compile() before filter() since filter()
  expects pre-compiled query arrays, not Query objects
- Remove incorrect break statement in Realtime::getSubscribers() that was
  stopping iteration after the first matching channel, causing subscribers
  to not be found for subsequent channels
- Use local variable $subscriptionsByChannel to avoid unused variable warning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jake Barnby
2026-02-05 21:57:00 +13:00
co-authored by Claude Opus 4.5
parent e490f375ee
commit 3e31094e1e
3 changed files with 94 additions and 73 deletions
+3 -4
View File
@@ -270,16 +270,16 @@ class Realtime extends MessagingAdapter
$payload = $event['data']['payload'] ?? [];
foreach ($this->subscriptions[$event['project']] as $role => $subscription) {
foreach ($this->subscriptions[$event['project']] as $role => $subscriptionsByChannel) {
foreach ($event['data']['channels'] as $channel) {
if (
!\array_key_exists($channel, $this->subscriptions[$event['project']][$role])
!\array_key_exists($channel, $subscriptionsByChannel)
|| (!\in_array($role, $event['roles']) && !\in_array(Role::any()->toString(), $event['roles']))
) {
continue;
}
foreach ($this->subscriptions[$event['project']][$role][$channel] as $id => $subscriptions) {
foreach ($subscriptionsByChannel[$channel] as $id => $subscriptions) {
$matched = [];
foreach ($subscriptions as $subscriptionId => $data) {
@@ -298,7 +298,6 @@ class Realtime extends MessagingAdapter
$receivers[$id] += $matched;
}
}
break;
}
}
+17 -4
View File
@@ -32,6 +32,22 @@ class RuntimeQuery extends Query
Query::TYPE_SELECT
];
/**
* Checks if a query is a select("*") query.
*
* @param Query $query
* @return bool
*/
public static function isSelectAll(Query $query): bool
{
if ($query->getMethod() !== Query::TYPE_SELECT) {
return false;
}
$values = $query->getValues();
return count($values) === 1 && $values[0] === '*';
}
/**
* Validates a select query - only select("*") is allowed in Realtime
*
@@ -44,10 +60,7 @@ class RuntimeQuery extends Query
return;
}
$values = $query->getValues();
$isSelectAll = count($values) === 1 && $values[0] === '*';
if (!$isSelectAll) {
if (!self::isSelectAll($query)) {
throw new \InvalidArgumentException(
'Only select("*") is allowed in Realtime queries. select("*") means "listen to all events".'
);
@@ -16,10 +16,19 @@ class RuntimeQueryTest extends TestCase
{
}
/**
* Helper to compile and filter queries in one step for tests.
*/
private function compileAndFilter(array $queries, array $payload): array
{
$compiled = RuntimeQuery::compile($queries);
return RuntimeQuery::filter($compiled, $payload);
}
public function testFilterEmptyQueries(): void
{
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter([], $payload);
$result = $this->compileAndFilter([], $payload);
$this->assertEquals($payload, $result);
}
@@ -27,7 +36,7 @@ class RuntimeQueryTest extends TestCase
{
$queries = [Query::equal('name', ['Jane'])];
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter($queries, $payload);
$result = $this->compileAndFilter($queries, $payload);
$this->assertEquals([], $result);
}
@@ -35,7 +44,7 @@ class RuntimeQueryTest extends TestCase
{
$queries = [Query::equal('name', ['John'])];
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter($queries, $payload);
$result = $this->compileAndFilter($queries, $payload);
$this->assertEquals($payload, $result);
}
@@ -44,7 +53,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('name', ['John']);
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -52,7 +61,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('name', ['Jane']);
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -60,7 +69,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('status', ['active', 'pending', 'approved']);
$payload = ['status' => 'active'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -68,7 +77,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('status', ['active', 'pending', 'approved']);
$payload = ['status' => 'rejected'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -76,7 +85,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('age', [30, 25, 35]);
$payload = ['age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -84,7 +93,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('active', [true]);
$payload = ['active' => true];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -92,7 +101,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('missing', ['value']);
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -101,7 +110,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::notEqual('name', ['Jane']);
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -109,7 +118,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::notEqual('name', ['John']);
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -119,12 +128,12 @@ class RuntimeQueryTest extends TestCase
// and Query::parse will be done first and parse doesn't allow multiple notEqual values
$query = Query::notEqual('status', ['rejected', 'cancelled']);
$payload = ['status' => 'active'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
$query = Query::notEqual('status', ['active', 'pending']);
$payload = ['status' => 'active'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -133,7 +142,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::lessThan('age', 30);
$payload = ['age' => 25];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -141,7 +150,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::lessThan('age', 30);
$payload = ['age' => 35];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -149,7 +158,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::lessThan('age', 30);
$payload = ['age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -159,7 +168,7 @@ class RuntimeQueryTest extends TestCase
// This test uses a single value as Query class requires
$query = Query::lessThan('age', 30);
$payload = ['age' => 25];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -167,7 +176,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::lessThan('name', 'M');
$payload = ['name' => 'A'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -176,7 +185,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::lessThanEqual('age', 30);
$payload = ['age' => 25];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -184,7 +193,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::lessThanEqual('age', 30);
$payload = ['age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -192,7 +201,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::lessThanEqual('age', 30);
$payload = ['age' => 35];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -201,7 +210,7 @@ class RuntimeQueryTest extends TestCase
// Note: Query::lessThanEqual only accepts single value
$query = Query::lessThanEqual('age', 30);
$payload = ['age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -210,7 +219,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::greaterThan('age', 30);
$payload = ['age' => 35];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -218,7 +227,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::greaterThan('age', 30);
$payload = ['age' => 25];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -226,7 +235,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::greaterThan('age', 30);
$payload = ['age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -235,7 +244,7 @@ class RuntimeQueryTest extends TestCase
// Note: Query::greaterThan only accepts single value
$query = Query::greaterThan('age', 20);
$payload = ['age' => 35];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -244,7 +253,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::greaterThanEqual('age', 30);
$payload = ['age' => 35];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -252,7 +261,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::greaterThanEqual('age', 30);
$payload = ['age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -260,7 +269,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::greaterThanEqual('age', 30);
$payload = ['age' => 25];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -269,7 +278,7 @@ class RuntimeQueryTest extends TestCase
// Note: Query::greaterThanEqual only accepts single value
$query = Query::greaterThanEqual('age', 20);
$payload = ['age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -278,7 +287,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::isNull('description');
$payload = ['description' => null];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -286,7 +295,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::isNull('description');
$payload = ['description' => 'Some text'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -294,7 +303,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::isNull('missing');
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -303,7 +312,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::isNotNull('description');
$payload = ['description' => 'Some text'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -311,7 +320,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::isNotNull('description');
$payload = ['description' => null];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -319,7 +328,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::isNotNull('missing');
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -331,7 +340,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('age', [30])
]);
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -342,7 +351,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('age', [25])
]);
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -353,7 +362,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('age', [25])
]);
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -365,7 +374,7 @@ class RuntimeQueryTest extends TestCase
Query::isNotNull('email')
]);
$payload = ['status' => 'active', 'age' => 25, 'email' => 'test@example.com'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -379,7 +388,7 @@ class RuntimeQueryTest extends TestCase
])
]);
$payload = ['name' => 'John', 'age' => 30, 'status' => 'active'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -391,7 +400,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('name', ['Jane'])
]);
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -402,7 +411,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('status', ['pending'])
]);
$payload = ['status' => 'active'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -413,7 +422,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('age', [25])
]);
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -425,7 +434,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('status', ['approved'])
]);
$payload = ['status' => 'pending'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -439,7 +448,7 @@ class RuntimeQueryTest extends TestCase
])
]);
$payload = ['name' => 'Bob'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -450,7 +459,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('email', ['john@example.com'])
]);
$payload = ['name' => 'Jane', 'email' => 'john@example.com'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -465,7 +474,7 @@ class RuntimeQueryTest extends TestCase
])
]);
$payload = ['type' => 'user', 'status' => 'active'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -482,7 +491,7 @@ class RuntimeQueryTest extends TestCase
])
]);
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -494,7 +503,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('age', [30])
];
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter($queries, $payload);
$result = $this->compileAndFilter($queries, $payload);
$this->assertEquals($payload, $result);
}
@@ -505,7 +514,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('age', [25])
];
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter($queries, $payload);
$result = $this->compileAndFilter($queries, $payload);
// With AND logic, if first matches but second doesn't, should return empty
$this->assertEquals([], $result);
}
@@ -517,7 +526,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('age', [30])
];
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter($queries, $payload);
$result = $this->compileAndFilter($queries, $payload);
// With AND logic, if second matches but first doesn't, should return empty
$this->assertEquals([], $result);
}
@@ -529,7 +538,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('age', [25])
];
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter($queries, $payload);
$result = $this->compileAndFilter($queries, $payload);
$this->assertEquals([], $result);
}
@@ -537,7 +546,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('name', ['John']);
$payload = [];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals([], $result);
}
@@ -545,7 +554,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::and([]);
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
// Empty AND should return true (all conditions pass vacuously)
$this->assertEquals($payload, $result);
}
@@ -554,7 +563,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::or([]);
$payload = ['name' => 'John'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
// Empty OR should return false (no conditions match)
$this->assertEquals([], $result);
}
@@ -564,7 +573,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('count', [0]);
$payload = ['count' => 0];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -572,7 +581,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('name', ['']);
$payload = ['name' => ''];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -580,7 +589,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::equal('active', [false]);
$payload = ['active' => false];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -588,7 +597,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::greaterThan('score', 8.5);
$payload = ['score' => 9.2];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -596,7 +605,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::lessThan('version', '10');
$payload = ['version' => '9'];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -667,7 +676,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::select(['*']);
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
@@ -679,7 +688,7 @@ class RuntimeQueryTest extends TestCase
Query::equal('name', ['Jane']), // This would normally fail
];
$payload = ['name' => 'John', 'age' => 30];
$result = RuntimeQuery::filter($queries, $payload);
$result = $this->compileAndFilter($queries, $payload);
// select("*") takes precedence - returns payload
$this->assertEquals($payload, $result);
}
@@ -688,7 +697,7 @@ class RuntimeQueryTest extends TestCase
{
$query = Query::select(['*']);
$payload = [];
$result = RuntimeQuery::filter([$query], $payload);
$result = $this->compileAndFilter([$query], $payload);
$this->assertEquals($payload, $result);
}
}