set up tests

This commit is contained in:
Alex Renoki 2020-08-13 16:18:14 +03:00
parent 64b0fa8382
commit 51f84e3c40
10 changed files with 55 additions and 36 deletions

View File

@ -43,8 +43,11 @@ jobs:
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
- name: Execute tests - name: Execute tests
run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml run: |
REPLICATION_DRIVER=local phpunit --coverage-text --coverage-clover=coverage_local.xml
REPLICATION_DRIVER=redis phpunit --coverage-text --coverage-clover=coverage_redis.xml
- uses: codecov/codecov-action@v1 - uses: codecov/codecov-action@v1
with: with:
fail_ci_if_error: false fail_ci_if_error: false
file: '*.xml'

View File

@ -143,23 +143,21 @@ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Broadcasting Replication | Broadcasting Replication PubSub
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| You can enable replication to publish and subscribe to | You can enable replication to publish and subscribe to
| messages across the driver. | messages across the driver.
|
| By default, it is disabled, but you can configure it to use drivers | By default, it is set to 'local', but you can configure it to use drivers
| like Redis to ensure connection between multiple instances of | like Redis to ensure connection between multiple instances of
| WebSocket servers. | WebSocket servers. Just set the driver to 'redis' to enable the PubSub using Redis.
| |
*/ */
'replication' => [ 'replication' => [
'enabled' => false, 'driver' => 'local',
'driver' => 'redis',
'redis' => [ 'redis' => [

View File

@ -166,7 +166,9 @@ class StartWebSocketServer extends Command
protected function configurePubSubReplication() protected function configurePubSubReplication()
{ {
$this->laravel->get(ReplicationInterface::class)->boot($this->loop); $this->laravel
->get(ReplicationInterface::class)
->boot($this->loop);
return $this; return $this;
} }

View File

@ -13,13 +13,13 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
class FetchChannelsController extends Controller class FetchChannelsController extends Controller
{ {
/** @var ReplicationInterface */ /** @var ReplicationInterface */
protected $replication; protected $pubsub;
public function __construct(ChannelManager $channelManager, ReplicationInterface $replication) public function __construct(ChannelManager $channelManager, ReplicationInterface $pubsub)
{ {
parent::__construct($channelManager); parent::__construct($channelManager);
$this->replication = $replication; $this->pubsub = $pubsub;
} }
public function __invoke(Request $request) public function __invoke(Request $request)
@ -51,7 +51,7 @@ class FetchChannelsController extends Controller
// We ask the replication backend to get us the member count per channel. // We ask the replication backend to get us the member count per channel.
// We get $counts back as a key-value array of channel names and their member count. // We get $counts back as a key-value array of channel names and their member count.
return $this->replication return $this->pubsub
->channelMemberCounts($request->appId, $channelNames) ->channelMemberCounts($request->appId, $channelNames)
->then(function (array $counts) use ($channels, $attributes) { ->then(function (array $counts) use ($channels, $attributes) {
return [ return [

View File

@ -38,7 +38,6 @@ class LocalClient implements ReplicationInterface
*/ */
public function publish(string $appId, string $channel, stdClass $payload): bool public function publish(string $appId, string $channel, stdClass $payload): bool
{ {
// Nothing to do, nobody to publish to
return true; return true;
} }

View File

@ -257,20 +257,24 @@ class RedisClient implements ReplicationInterface
*/ */
protected function getConnectionUri() protected function getConnectionUri()
{ {
$name = config('websockets.replication.connection') ?? 'default'; $name = config('websockets.replication.redis.connection') ?? 'default';
$config = config("database.redis.$name"); $config = config('database.redis')[$name];
$host = $config['host']; $host = $config['host'];
$port = $config['port'] ? (':'.$config['port']) : ':6379'; $port = $config['port'] ?: 6379;
$query = []; $query = [];
if ($config['password']) { if ($config['password']) {
$query['password'] = $config['password']; $query['password'] = $config['password'];
} }
if ($config['database']) { if ($config['database']) {
$query['database'] = $config['database']; $query['database'] = $config['database'];
} }
$query = http_build_query($query); $query = http_build_query($query);
return "redis://$host$port".($query ? '?'.$query : ''); return "redis://{$host}:{$port}".($query ? "?{$query}" : '');
} }
} }

View File

@ -15,7 +15,7 @@ class Channel
protected $channelName; protected $channelName;
/** @var ReplicationInterface */ /** @var ReplicationInterface */
protected $replication; protected $pubsub;
/** @var \Ratchet\ConnectionInterface[] */ /** @var \Ratchet\ConnectionInterface[] */
protected $subscribedConnections = []; protected $subscribedConnections = [];
@ -23,7 +23,7 @@ class Channel
public function __construct(string $channelName) public function __construct(string $channelName)
{ {
$this->channelName = $channelName; $this->channelName = $channelName;
$this->replication = app(ReplicationInterface::class); $this->pubsub = app(ReplicationInterface::class);
} }
public function getChannelName(): string public function getChannelName(): string
@ -68,7 +68,7 @@ class Channel
$this->saveConnection($connection); $this->saveConnection($connection);
// Subscribe to broadcasted messages from the pub/sub backend // Subscribe to broadcasted messages from the pub/sub backend
$this->replication->subscribe($connection->app->id, $this->channelName); $this->pubsub->subscribe($connection->app->id, $this->channelName);
$connection->send(json_encode([ $connection->send(json_encode([
'event' => 'pusher_internal:subscription_succeeded', 'event' => 'pusher_internal:subscription_succeeded',
@ -81,7 +81,7 @@ class Channel
unset($this->subscribedConnections[$connection->socketId]); unset($this->subscribedConnections[$connection->socketId]);
// Unsubscribe from the pub/sub backend // Unsubscribe from the pub/sub backend
$this->replication->unsubscribe($connection->app->id, $this->channelName); $this->pubsub->unsubscribe($connection->app->id, $this->channelName);
if (! $this->hasConnections()) { if (! $this->hasConnections()) {
DashboardLogger::vacated($connection, $this->channelName); DashboardLogger::vacated($connection, $this->channelName);
@ -120,7 +120,7 @@ class Channel
// in this case. If this came from TriggerEventController, then we still want // in this case. If this came from TriggerEventController, then we still want
// to publish to get the message out to other server instances. // to publish to get the message out to other server instances.
if ($publish) { if ($publish) {
$this->replication->publish($appId, $this->channelName, $payload); $this->pubsub->publish($appId, $this->channelName, $payload);
} }
// Performance optimization, if we don't have a socket ID, // Performance optimization, if we don't have a socket ID,

View File

@ -28,7 +28,7 @@ class PresenceChannel extends Channel
public function getUsers(string $appId) public function getUsers(string $appId)
{ {
// Get the members list from the replication backend // Get the members list from the replication backend
return $this->replication return $this->pubsub
->channelMembers($appId, $this->channelName); ->channelMembers($appId, $this->channelName);
} }
@ -49,7 +49,7 @@ class PresenceChannel extends Channel
$this->users[$connection->socketId] = $channelData; $this->users[$connection->socketId] = $channelData;
// Add the connection as a member of the channel // Add the connection as a member of the channel
$this->replication $this->pubsub
->joinChannel( ->joinChannel(
$connection->app->id, $connection->app->id,
$this->channelName, $this->channelName,
@ -59,7 +59,7 @@ class PresenceChannel extends Channel
// We need to pull the channel data from the replication backend, // We need to pull the channel data from the replication backend,
// otherwise we won't be sending the full details of the channel // otherwise we won't be sending the full details of the channel
$this->replication $this->pubsub
->channelMembers($connection->app->id, $this->channelName) ->channelMembers($connection->app->id, $this->channelName)
->then(function ($users) use ($connection) { ->then(function ($users) use ($connection) {
// Send the success event // Send the success event
@ -86,7 +86,7 @@ class PresenceChannel extends Channel
} }
// Remove the connection as a member of the channel // Remove the connection as a member of the channel
$this->replication $this->pubsub
->leaveChannel( ->leaveChannel(
$connection->app->id, $connection->app->id,
$this->channelName, $this->channelName,
@ -110,7 +110,7 @@ class PresenceChannel extends Channel
*/ */
public function toArray(string $appId = null) public function toArray(string $appId = null)
{ {
return $this->replication return $this->pubsub
->channelMembers($appId, $this->channelName) ->channelMembers($appId, $this->channelName)
->then(function ($users) { ->then(function ($users) {
return array_merge(parent::toArray(), [ return array_merge(parent::toArray(), [

View File

@ -24,6 +24,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Pusher\Pusher; use Pusher\Pusher;
use React\EventLoop\Factory as LoopFactory;
class WebSocketsServiceProvider extends ServiceProvider class WebSocketsServiceProvider extends ServiceProvider
{ {
@ -56,19 +57,19 @@ class WebSocketsServiceProvider extends ServiceProvider
protected function configurePubSub() protected function configurePubSub()
{ {
if (config('websockets.replication.enabled') !== true || config('websockets.replication.driver') !== 'redis') { if (config('websockets.replication.driver') === 'local') {
$this->app->singleton(ReplicationInterface::class, function () { $this->app->singleton(ReplicationInterface::class, function () {
return new LocalClient(); return new LocalClient;
}); });
return;
} }
$this->app->singleton(ReplicationInterface::class, function () { if (config('websockets.replication.driver') === 'redis') {
return (new RedisClient())->boot($this->loop); $this->app->singleton(ReplicationInterface::class, function () {
}); return (new RedisClient)->boot($this->loop ?? LoopFactory::create());
});
}
$this->app->get(BroadcastManager::class)->extend('redis-pusher', function ($app, array $config) { $this->app->get(BroadcastManager::class)->extend('websockets', function ($app, array $config) {
$pusher = new Pusher( $pusher = new Pusher(
$config['key'], $config['secret'], $config['key'], $config['secret'],
$config['app_id'], $config['options'] ?? [] $config['app_id'], $config['options'] ?? []

View File

@ -56,6 +56,18 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
], ],
]); ]);
$app['config']->set('database.redis.default', [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
]);
$app['config']->set(
'websockets.replication.driver',
getenv('REPLICATION_DRIVER') ?: 'local'
);
include_once __DIR__.'/../database/migrations/create_websockets_statistics_entries_table.php.stub'; include_once __DIR__.'/../database/migrations/create_websockets_statistics_entries_table.php.stub';
(new \CreateWebSocketsStatisticsEntriesTable())->up(); (new \CreateWebSocketsStatisticsEntriesTable())->up();