mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Enhance Realtime adapter to support delete action and add corresponding tests
This commit is contained in:
@@ -22,9 +22,9 @@ class Realtime extends MessagingAdapter
|
||||
/**
|
||||
* Action suffixes recognized in channel names. A channel like `documents.create`
|
||||
* is split into base channel `documents` plus action `create`. Add new actions
|
||||
* (e.g. `delete`) here to extend support — no other code change is required.
|
||||
* here to extend support — no other code change is required.
|
||||
*/
|
||||
public const SUPPORTED_ACTIONS = ['create', 'update', 'upsert'];
|
||||
public const SUPPORTED_ACTIONS = ['create', 'update', 'upsert', 'delete'];
|
||||
|
||||
/**
|
||||
* Connection Tree
|
||||
|
||||
@@ -2827,7 +2827,7 @@ trait RealtimeQueryBase
|
||||
$clientMulti->close();
|
||||
}
|
||||
|
||||
public function testChannelActionFilterUnsupportedActionTreatedAsLiteral(): void
|
||||
public function testChannelActionFilterDeliversDeleteEvents(): void
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$session = $user['session'] ?? '';
|
||||
@@ -2840,17 +2840,64 @@ trait RealtimeQueryBase
|
||||
|
||||
['databaseId' => $databaseId, 'collectionId' => $collectionId] = $this->createActorsCollection();
|
||||
|
||||
// `delete` is intentionally NOT in SUPPORTED_ACTIONS yet, so parseActionChannel
|
||||
// leaves the channel name intact and treats it as a literal channel that no
|
||||
// published event ever carries — the subscriber should receive nothing.
|
||||
$client = $this->getWebsocket(['documents.delete'], $headers);
|
||||
$connected = $this->assertConnectionStatusIfSupported($client);
|
||||
$deleteChannel = "databases.{$databaseId}.collections.{$collectionId}.documents.delete";
|
||||
$clientDelete = $this->getWebsocket([$deleteChannel], $headers);
|
||||
$connected = $this->assertConnectionStatusIfSupported($clientDelete);
|
||||
if ($connected !== null) {
|
||||
$this->assertContains('documents.delete', $connected['data']['channels']);
|
||||
$this->assertContains($deleteChannel, $connected['data']['channels']);
|
||||
}
|
||||
|
||||
$documentId = ID::unique();
|
||||
$this->createActor($databaseId, $collectionId, $documentId, 'No Delete Listener');
|
||||
$this->createActor($databaseId, $collectionId, $documentId, 'About To Be Deleted');
|
||||
|
||||
// Create event must not arrive — the action filter is `delete`.
|
||||
try {
|
||||
$clientDelete->receive();
|
||||
$this->fail('Delete subscriber should not receive a create event.');
|
||||
} catch (TimeoutException $e) {
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, "/databases/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()));
|
||||
|
||||
$deleteEvent = json_decode($clientDelete->receive(), true);
|
||||
$this->assertEquals('event', $deleteEvent['type']);
|
||||
$this->assertContains(
|
||||
"databases.{$databaseId}.collections.{$collectionId}.documents.{$documentId}.delete",
|
||||
$deleteEvent['data']['events']
|
||||
);
|
||||
$this->assertEquals($documentId, $deleteEvent['data']['payload']['$id']);
|
||||
|
||||
$clientDelete->close();
|
||||
}
|
||||
|
||||
public function testChannelActionFilterUnknownSuffixTreatedAsLiteral(): void
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$session = $user['session'] ?? '';
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
$headers = [
|
||||
'origin' => 'http://localhost',
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
||||
];
|
||||
|
||||
['databaseId' => $databaseId, 'collectionId' => $collectionId] = $this->createActorsCollection();
|
||||
|
||||
// An unrecognised suffix is NOT in SUPPORTED_ACTIONS, so parseActionChannel
|
||||
// leaves the channel name intact and treats it as a literal channel that no
|
||||
// published event ever carries — the subscriber should receive nothing.
|
||||
$client = $this->getWebsocket(['documents.bogus'], $headers);
|
||||
$connected = $this->assertConnectionStatusIfSupported($client);
|
||||
if ($connected !== null) {
|
||||
$this->assertContains('documents.bogus', $connected['data']['channels']);
|
||||
}
|
||||
|
||||
$documentId = ID::unique();
|
||||
$this->createActor($databaseId, $collectionId, $documentId, 'No Bogus Listener');
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, "/databases/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -2859,7 +2906,7 @@ trait RealtimeQueryBase
|
||||
|
||||
try {
|
||||
$client->receive();
|
||||
$this->fail('`documents.delete` is not (yet) a supported action channel and should not deliver.');
|
||||
$this->fail('Unrecognised action suffix should not deliver any events.');
|
||||
} catch (TimeoutException $e) {
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
@@ -523,17 +523,22 @@ class MessagingTest extends TestCase
|
||||
$this->assertSame(['documents', 'create'], Realtime::parseActionChannel('documents.create'));
|
||||
$this->assertSame(['documents', 'update'], Realtime::parseActionChannel('documents.update'));
|
||||
$this->assertSame(['documents', 'upsert'], Realtime::parseActionChannel('documents.upsert'));
|
||||
$this->assertSame(['documents', 'delete'], Realtime::parseActionChannel('documents.delete'));
|
||||
$this->assertSame(
|
||||
['databases.X.collections.Y.documents.Z', 'create'],
|
||||
Realtime::parseActionChannel('databases.X.collections.Y.documents.Z.create')
|
||||
);
|
||||
$this->assertSame(
|
||||
['databases.X.collections.Y.documents.Z', 'delete'],
|
||||
Realtime::parseActionChannel('databases.X.collections.Y.documents.Z.delete')
|
||||
);
|
||||
|
||||
// No action suffix → unchanged with '*' default.
|
||||
$this->assertSame(['documents', '*'], Realtime::parseActionChannel('documents'));
|
||||
$this->assertSame(['documents.789', '*'], Realtime::parseActionChannel('documents.789'));
|
||||
|
||||
// Unrecognised suffix (e.g. delete is not yet supported) → unchanged.
|
||||
$this->assertSame(['documents.delete', '*'], Realtime::parseActionChannel('documents.delete'));
|
||||
// Unrecognised suffix → unchanged (treated as literal channel name).
|
||||
$this->assertSame(['documents.bogus', '*'], Realtime::parseActionChannel('documents.bogus'));
|
||||
}
|
||||
|
||||
public function testActionChannelFiltersByEventAction(): void
|
||||
@@ -590,6 +595,44 @@ class MessagingTest extends TestCase
|
||||
$this->assertArrayNotHasKey('sub-create', $receivers[1]);
|
||||
}
|
||||
|
||||
public function testActionChannelDeleteFilter(): void
|
||||
{
|
||||
$realtime = new Realtime();
|
||||
|
||||
$realtime->subscribe(
|
||||
'1',
|
||||
1,
|
||||
'sub-delete',
|
||||
[Role::any()->toString()],
|
||||
['documents.delete'],
|
||||
);
|
||||
|
||||
$deleteEvent = [
|
||||
'project' => '1',
|
||||
'roles' => [Role::any()->toString()],
|
||||
'data' => [
|
||||
'channels' => ['documents'],
|
||||
'events' => [
|
||||
'databases.db.collections.col.documents.doc.delete',
|
||||
'databases.*.collections.*.documents.*.delete',
|
||||
],
|
||||
'payload' => ['$id' => 'doc'],
|
||||
],
|
||||
];
|
||||
|
||||
$receivers = $realtime->getSubscribers($deleteEvent);
|
||||
$this->assertArrayHasKey(1, $receivers);
|
||||
$this->assertArrayHasKey('sub-delete', $receivers[1]);
|
||||
|
||||
// Other actions on the same base channel should not match the delete filter.
|
||||
$createEvent = $deleteEvent;
|
||||
$createEvent['data']['events'] = [
|
||||
'databases.db.collections.col.documents.doc.create',
|
||||
'databases.*.collections.*.documents.*.create',
|
||||
];
|
||||
$this->assertEmpty($realtime->getSubscribers($createEvent));
|
||||
}
|
||||
|
||||
public function testActionChannelHonorsResourceId(): void
|
||||
{
|
||||
$realtime = new Realtime();
|
||||
|
||||
Reference in New Issue
Block a user