diff --git a/src/EventServiceProvider.php b/src/EventServiceProvider.php index 3db5be3..d2afe14 100644 --- a/src/EventServiceProvider.php +++ b/src/EventServiceProvider.php @@ -47,7 +47,7 @@ protected function registerSnsBroadcaster() * @param array $config * @return \Illuminate\Contracts\Broadcasting\Broadcaster */ - protected function createSnsDriver(array $config) + public function createSnsDriver(array $config) { $config = self::prepareConfigurationCredentials($config); @@ -81,9 +81,7 @@ protected function registerEventBridgeBroadcaster() { $this->app->resolving(BroadcastManager::class, function (BroadcastManager $manager) { $manager->extend('eventbridge', function (Container $app, array $config) { - return $this->createEventBridgeDriver(array_merge($config, [ - 'version' => '2015-10-07', - ])); + return $this->createEventBridgeDriver($config); }); }); } @@ -94,12 +92,12 @@ protected function registerEventBridgeBroadcaster() * @param array $config * @return \Illuminate\Contracts\Broadcasting\Broadcaster */ - protected function createEventBridgeDriver(array $config) + public function createEventBridgeDriver(array $config) { $config = self::prepareConfigurationCredentials($config); return new EventBridgeBroadcaster( - new EventBridgeClient($config), + new EventBridgeClient(array_merge($config, ['version' => '2015-10-07'])), $config['source'] ?? '' ); } @@ -110,12 +108,24 @@ protected function createEventBridgeDriver(array $config) * @param array $config * @return array */ - public static function prepareConfigurationCredentials(array $config) + public static function prepareConfigurationCredentials(array $config): array { - if (Arr::has($config, ['key', 'secret'])) { + if (static::configHasCredentials($config)) { $config['credentials'] = Arr::only($config, ['key', 'secret', 'token']); } return $config; } + + /** + * Make sure some AWS credentials were provided to the configuration array. + * + * @return bool + */ + private static function configHasCredentials(array $config): bool + { + return Arr::has($config, ['key', 'secret']) + && is_string(Arr::get($config, 'key')) + && is_string(Arr::get($config, 'secret')); + } } diff --git a/tests/Console/ListenerMakeCommandTest.php b/tests/Console/ListenerMakeCommandTest.php index b7cfd7f..4240c7e 100644 --- a/tests/Console/ListenerMakeCommandTest.php +++ b/tests/Console/ListenerMakeCommandTest.php @@ -2,6 +2,8 @@ namespace PodPoint\AwsPubSub\Tests\Console; +use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Str; use PodPoint\AwsPubSub\Tests\TestCase; class ListenerMakeCommandTest extends TestCase @@ -25,20 +27,27 @@ public function it_can_generate_pubsub_event_listeners() { $this->assertFileDoesNotExist(app_path('Listeners/PubSub/SomeListener.php')); - $this->artisan('pubsub:make:listener SomeListener') - ->expectsOutput('Listener created successfully.'); + $exitCode = $this->withoutMockingConsoleOutput() + ->artisan('pubsub:make:listener SomeListener'); + $this->assertEquals(0, $exitCode); + Str::contains(Artisan::output(), 'created successfully'); $this->assertFileExists(app_path('Listeners/PubSub/SomeListener.php')); } /** @test */ public function it_cannot_generate_pubsub_event_listeners_which_already_exist() { - $this->artisan('pubsub:make:listener SomeListener') - ->expectsOutput('Listener created successfully.'); + $this->assertFileDoesNotExist(app_path('Listeners/PubSub/SomeListener.php')); + $this->artisan('pubsub:make:listener SomeListener')->assertSuccessful(); + $this->assertFileExists(app_path('Listeners/PubSub/SomeListener.php')); - $this->artisan('pubsub:make:listener SomeListener') - ->expectsOutput('Listener already exists!'); + $exitCode = $this->withoutMockingConsoleOutput() + ->artisan('pubsub:make:listener SomeListener'); + + $this->assertEquals(0, $exitCode); + Str::contains(Artisan::output(), 'already exists'); + $this->assertFileExists(app_path('Listeners/PubSub/SomeListener.php')); } private function cleanup() diff --git a/tests/Pub/BasicEvents/EventBridgeTest.php b/tests/Pub/BasicEvents/EventBridgeTest.php index 58f2b0a..fc99bb5 100644 --- a/tests/Pub/BasicEvents/EventBridgeTest.php +++ b/tests/Pub/BasicEvents/EventBridgeTest.php @@ -2,8 +2,7 @@ namespace PodPoint\AwsPubSub\Tests\Pub\BasicEvents; -use Illuminate\Foundation\Application; -use Mockery; +use Mockery as m; use Mockery\MockInterface; use PodPoint\AwsPubSub\Tests\Pub\Concerns\InteractsWithEventBridge; use PodPoint\AwsPubSub\Tests\Pub\TestClasses\Events\UserRetrieved; @@ -17,28 +16,16 @@ class EventBridgeTest extends TestCase { use InteractsWithEventBridge; - /** - * @param Application $app - */ - protected function getEnvironmentSetUp($app) - { - $this->setEventBridgeBroadcaster($app); - - $this->setTestDatabase($app); - } - /** @test */ public function it_broadcasts_basic_event_with_the_event_name_as_the_detail_type_and_serialised_event_as_the_detail() { - $jane = $this->createJane(); - - $janeRetrieved = new UserRetrieved($jane); + $janeRetrieved = new UserRetrieved($this->createJane()); $this->mockEventBridge(function (MockInterface $eventBridge) use ($janeRetrieved) { $eventBridge ->shouldReceive('putEvents') ->once() - ->with(Mockery::on(function ($arg) use ($janeRetrieved) { + ->with(m::on(function ($arg) use ($janeRetrieved) { return $arg['Entries'][0]['Detail'] === json_encode($janeRetrieved) && $arg['Entries'][0]['DetailType'] === UserRetrieved::class && $arg['Entries'][0]['EventBusName'] === 'users' @@ -52,19 +39,15 @@ public function it_broadcasts_basic_event_with_the_event_name_as_the_detail_type /** @test */ public function it_broadcasts_basic_event_with_action() { - $jane = $this->createJane(); - - $janeRetrieved = new UserRetrievedWithCustomName($jane); + $janeRetrieved = new UserRetrievedWithCustomName($this->createJane()); $this->mockEventBridge(function (MockInterface $eventBridge) use ($janeRetrieved) { $eventBridge ->shouldReceive('putEvents') ->once() - ->with(Mockery::on(function ($arg) use ($janeRetrieved) { + ->with(m::on(function ($arg) use ($janeRetrieved) { return $arg['Entries'][0]['Detail'] === json_encode($janeRetrieved) - && $arg['Entries'][0]['DetailType'] === 'user.retrieved' - && $arg['Entries'][0]['EventBusName'] === 'users' - && $arg['Entries'][0]['Source'] === 'my-app'; + && $arg['Entries'][0]['DetailType'] === 'user.retrieved'; })); }); @@ -74,21 +57,18 @@ public function it_broadcasts_basic_event_with_action() /** @test */ public function it_broadcasts_basic_event_with_action_and_custom_payload() { - $jane = $this->createJane(); - - $janeRetrieved = new UserRetrievedWithCustomPayload($jane); + $janeRetrieved = new UserRetrievedWithCustomPayload($this->createJane()); $this->mockEventBridge(function (MockInterface $eventBridge) use ($janeRetrieved) { $eventBridge ->shouldReceive('putEvents') ->once() - ->with(Mockery::on(function ($arg) use ($janeRetrieved) { + ->with(m::on(function ($arg) use ($janeRetrieved) { $customPayload = array_merge($janeRetrieved->broadcastWith(), ['socket' => null]); return $arg['Entries'][0]['Detail'] === json_encode($customPayload) && $arg['Entries'][0]['DetailType'] === UserRetrievedWithCustomPayload::class - && $arg['Entries'][0]['EventBusName'] === 'users' - && $arg['Entries'][0]['Source'] === 'my-app'; + && $arg['Entries'][0]['EventBusName'] === 'users'; })); }); @@ -98,37 +78,55 @@ public function it_broadcasts_basic_event_with_action_and_custom_payload() /** @test */ public function it_broadcasts_basic_event_to_multiple_channels_as_buses() { - $jane = $this->createJane(); - - $janeRetrieved = new UserRetrievedWithMultipleChannels($jane); + $janeRetrieved = new UserRetrievedWithMultipleChannels($this->createJane()); $this->mockEventBridge(function (MockInterface $eventBridge) use ($janeRetrieved) { $eventBridge ->shouldReceive('putEvents') ->once() - ->with(Mockery::on(function ($arg) use ($janeRetrieved) { + ->with(m::on(function ($arg) use ($janeRetrieved) { return collect($janeRetrieved->broadcastOn()) ->map(function ($channel, $key) use ($arg, $janeRetrieved) { return $arg['Entries'][$key]['Detail'] === json_encode($janeRetrieved) && $arg['Entries'][$key]['DetailType'] === UserRetrievedWithMultipleChannels::class - && $arg['Entries'][$key]['EventBusName'] === $channel - && $arg['Entries'][$key]['Source'] === 'my-app'; + && $arg['Entries'][$key]['EventBusName'] === $channel; }) ->filter() - ->count() > 0; + ->count() === 2; + })); + }); + + event($janeRetrieved); + } + + /** @test */ + public function it_can_use_a_source() + { + config(['broadcasting.connections.eventbridge.source' => 'some-other-source']); + + $janeRetrieved = new UserRetrievedWithMultipleChannels($this->createJane()); + + $this->mockEventBridge(function (MockInterface $eventBridge) use ($janeRetrieved) { + $eventBridge + ->shouldReceive('putEvents') + ->once() + ->with(m::on(function ($arg) use ($janeRetrieved) { + return collect($janeRetrieved->broadcastOn()) + ->map(function ($channel, $key) use ($arg) { + return $arg['Entries'][$key]['Source'] === 'some-other-source'; + }) + ->filter() + ->count() > 0; })); }); event($janeRetrieved); } - /** - * @return User - */ - protected function createJane() + protected function createJane(): User { return User::create([ - 'name' => 'Jane', + 'name' => 'Jane Doe', 'email' => 'jane@doe.com', 'password' => 'shh', ])->fresh(); diff --git a/tests/Pub/BasicEvents/SnsTest.php b/tests/Pub/BasicEvents/SnsTest.php index d218736..c53e36c 100644 --- a/tests/Pub/BasicEvents/SnsTest.php +++ b/tests/Pub/BasicEvents/SnsTest.php @@ -26,15 +26,17 @@ public function it_broadcasts_basic_event() ->with(m::on(function ($argument) { $message = json_decode($argument['Message'], true); - return $message['user']['email'] === 'john@doe.com' + return $message['user']['name'] === 'John Doe' + && $message['user']['email'] === 'john@doe.com' + && $message['user']['password'] === 'secret' && $message['foo'] = 'bar'; })); }); event(new UserRetrieved(User::create([ - 'name' => $this->faker->name(), + 'name' => 'John Doe', 'email' => 'john@doe.com', - 'password' => $this->faker->password(), + 'password' => 'secret', ]))); } @@ -144,4 +146,25 @@ public function it_broadcasts_basic_event_name_as_subject_if_specified() 'password' => $this->faker->password(), ]))); } + + /** @test */ + public function it_can_use_an_arn_prefix_and_suffix() + { + config(['broadcasting.connections.sns.arn-prefix' => 'some-prefix:']); + config(['broadcasting.connections.sns.arn-suffix' => '-some-suffix']); + + $this->mockSns(function (MockInterface $sns) { + $sns->shouldReceive('publish') + ->once() + ->with(m::on(function ($argument) { + return $argument['TopicArn'] === 'some-prefix:users-some-suffix'; + })); + }); + + event(new UserRetrieved(User::create([ + 'name' => $this->faker->name(), + 'email' => $this->faker->email(), + 'password' => $this->faker->password(), + ]))); + } } diff --git a/tests/Pub/Broadcasting/Broadcasters/EventBridgeBroadcasterTest.php b/tests/Pub/Broadcasting/Broadcasters/EventBridgeBroadcasterTest.php new file mode 100644 index 0000000..6903320 --- /dev/null +++ b/tests/Pub/Broadcasting/Broadcasters/EventBridgeBroadcasterTest.php @@ -0,0 +1,53 @@ +app))->createEventBridgeDriver([ + 'driver' => 'eventbridge', + 'key' => 'dummy-key', + 'secret' => 'dummy-secret', + 'region' => 'eu-west-1', + 'event_bus' => 'default', + 'source' => 'my-app', + ]); + + $this->assertInstanceOf(EventBridgeBroadcaster::class, $broadcaster); + } + + /** @test */ + public function it_supports_optional_aws_credentials() + { + $broadcaster = (new EventServiceProvider($this->app))->createEventBridgeDriver([ + 'driver' => 'eventbridge', + 'region' => 'eu-west-1', + 'event_bus' => 'default', + 'source' => 'my-app', + ]); + + $this->assertInstanceOf(EventBridgeBroadcaster::class, $broadcaster); + } + + /** @test */ + public function it_supports_null_aws_credentials() + { + $broadcaster = (new EventServiceProvider($this->app))->createEventBridgeDriver([ + 'driver' => 'eventbridge', + 'key' => null, + 'secret' => null, + 'region' => 'eu-west-1', + 'event_bus' => 'default', + 'source' => 'my-app', + ]); + + $this->assertInstanceOf(EventBridgeBroadcaster::class, $broadcaster); + } +} diff --git a/tests/Pub/Broadcasting/Broadcasters/SnsBroadcasterTest.php b/tests/Pub/Broadcasting/Broadcasters/SnsBroadcasterTest.php new file mode 100644 index 0000000..5e67573 --- /dev/null +++ b/tests/Pub/Broadcasting/Broadcasters/SnsBroadcasterTest.php @@ -0,0 +1,53 @@ +app))->createSnsDriver([ + 'driver' => 'sns', + 'key' => 'dummy-key', + 'secret' => 'dummy-secret', + 'arn-prefix' => 'aws:arn:12345:', + 'region' => 'eu-west-1', + ]); + + $this->assertInstanceOf(SnsBroadcaster::class, $broadcaster); + } + + /** @test */ + public function it_supports_optional_aws_credentials() + { + $broadcaster = (new EventServiceProvider($this->app))->createSnsDriver([ + 'driver' => 'sns', + 'arn-prefix' => 'aws:arn:12345:', + 'region' => 'eu-west-1', + ]); + + $this->assertInstanceOf(SnsBroadcaster::class, $broadcaster); + } + + /** @test */ + public function it_supports_null_aws_credentials() + { + $broadcaster = (new EventServiceProvider($this->app))->createSnsDriver([ + 'driver' => 'sns', + 'key' => null, + 'secret' => null, + 'arn-prefix' => 'aws:arn:12345:', + 'region' => 'eu-west-1', + ]); + + $this->assertInstanceOf(SnsBroadcaster::class, $broadcaster); + } +} diff --git a/tests/Pub/Concerns/InteractsWithEventBridge.php b/tests/Pub/Concerns/InteractsWithEventBridge.php index e4c26bb..4d3b212 100644 --- a/tests/Pub/Concerns/InteractsWithEventBridge.php +++ b/tests/Pub/Concerns/InteractsWithEventBridge.php @@ -6,27 +6,45 @@ use Closure; use Illuminate\Broadcasting\BroadcastManager; use Illuminate\Contracts\Broadcasting\Broadcaster; -use Mockery; +use Illuminate\Foundation\Application; +use Mockery as m; use PodPoint\AwsPubSub\Pub\Broadcasting\Broadcasters\EventBridgeBroadcaster; trait InteractsWithEventBridge { + /** + * @param Application $app + */ + public function getEnvironmentSetUp($app) + { + $app->config->set('broadcasting.default', 'eventbridge'); + $app->config->set('broadcasting.connections.eventbridge', [ + 'driver' => 'eventbridge', + 'key' => 'dummy-key', + 'secret' => 'dummy-secret', + 'region' => 'eu-west-1', + 'event_bus' => 'default', + 'source' => 'my-app', + ]); + } + /** * @param Closure|null $mock * @return void */ private function mockEventBridge(Closure $mock = null) { - $eventBridge = Mockery::mock(EventBridgeClient::class, $mock); + $eventBridge = m::mock(EventBridgeClient::class, $mock); - $broadcaster = Mockery::mock( - EventBridgeBroadcaster::class, - [$eventBridge, config('broadcasting.connections.eventbridge.source')] - )->makePartial(); + $connection = config('broadcasting.default'); + $broadcaster = m::mock(EventBridgeBroadcaster::class, [ + $eventBridge, + config("broadcasting.connections.{$connection}.source", ''), + ])->makePartial(); $this->swap(Broadcaster::class, $broadcaster); - $manager = Mockery::mock(BroadcastManager::class, [$this->app], function ($mock) use ($broadcaster) { + $manager = m::mock(BroadcastManager::class, [$this->app], function ($mock) use ($broadcaster) { $mock->shouldReceive('connection')->andReturn($broadcaster); })->makePartial(); diff --git a/tests/Pub/Concerns/InteractsWithSns.php b/tests/Pub/Concerns/InteractsWithSns.php index 7b89e27..8607eb4 100644 --- a/tests/Pub/Concerns/InteractsWithSns.php +++ b/tests/Pub/Concerns/InteractsWithSns.php @@ -5,11 +5,27 @@ use Aws\Sns\SnsClient; use Illuminate\Broadcasting\BroadcastManager; use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract; +use Illuminate\Foundation\Application; use Mockery as m; use PodPoint\AwsPubSub\Pub\Broadcasting\Broadcasters\SnsBroadcaster; trait InteractsWithSns { + /** + * @param Application $app + */ + public function getEnvironmentSetUp($app) + { + $app->config->set('broadcasting.default', 'sns'); + $app->config->set('broadcasting.connections.sns', [ + 'driver' => 'sns', + 'key' => 'dummy-key', + 'secret' => 'dummy-secret', + 'arn-prefix' => 'aws:arn:12345:', + 'region' => 'eu-west-1', + ]); + } + /** * Mocks the SnsClient through the SnsBroadcaster and the BroadcastManager. * @@ -20,7 +36,12 @@ private function mockSns(\Closure $mock = null) { $sns = m::mock(SnsClient::class, $mock); - $broadcaster = m::mock(SnsBroadcaster::class, [$sns])->makePartial(); + $connection = config('broadcasting.default'); + $broadcaster = m::mock(SnsBroadcaster::class, [ + $sns, + config("broadcasting.connections.{$connection}.arn-prefix", ''), + config("broadcasting.connections.{$connection}.arn-suffix", ''), + ])->makePartial(); $this->swap(BroadcasterContract::class, $broadcaster); diff --git a/tests/Sub/Queue/Connectors/SqsSnsConnectorTest.php b/tests/Sub/Queue/Connectors/SqsSnsConnectorTest.php index f444e2e..bbdab34 100644 --- a/tests/Sub/Queue/Connectors/SqsSnsConnectorTest.php +++ b/tests/Sub/Queue/Connectors/SqsSnsConnectorTest.php @@ -11,7 +11,14 @@ class SqsSnsConnectorTest extends TestCase /** @test */ public function it_can_instantiate_the_connector_and_connect_to_the_queue() { - $queue = (new SqsSnsConnector)->connect(config('queue.connections.pub-sub')); + $queue = (new SqsSnsConnector)->connect([ + 'driver' => 'sqs-sns', + 'key' => 'dummy-key', + 'secret' => 'dummy-secret', + 'prefix' => 'https://sqs.eu-west-1.amazonaws.com/13245', + 'queue' => 'default', + 'region' => 'eu-west-1', + ]); $this->assertInstanceOf(SqsSnsQueue::class, $queue); } @@ -19,7 +26,14 @@ public function it_can_instantiate_the_connector_and_connect_to_the_queue() /** @test */ public function it_can_use_a_queue_prefix() { - $queue = (new SqsSnsConnector)->connect(config('queue.connections.pub-sub')); + $queue = (new SqsSnsConnector)->connect([ + 'driver' => 'sqs-sns', + 'key' => 'dummy-key', + 'secret' => 'dummy-secret', + 'prefix' => 'https://sqs.eu-west-1.amazonaws.com/13245', + 'queue' => 'default', + 'region' => 'eu-west-1', + ]); $this->assertEquals('https://sqs.eu-west-1.amazonaws.com/13245/default', $queue->getQueue(null)); } @@ -27,10 +41,44 @@ public function it_can_use_a_queue_prefix() /** @test */ public function it_can_use_a_queue_suffix() { - config(['queue.connections.pub-sub.suffix' => '-testing']); - - $queue = (new SqsSnsConnector)->connect(config('queue.connections.pub-sub')); + $queue = (new SqsSnsConnector)->connect([ + 'driver' => 'sqs-sns', + 'key' => null, + 'secret' => null, + 'prefix' => 'https://sqs.eu-west-1.amazonaws.com/13245', + 'queue' => 'default', + 'suffix' => '-testing', + 'region' => 'eu-west-1', + ]); $this->assertEquals('https://sqs.eu-west-1.amazonaws.com/13245/default-testing', $queue->getQueue(null)); } + + /** @test */ + public function it_supports_optional_aws_credentials() + { + $queue = (new SqsSnsConnector)->connect([ + 'driver' => 'sqs-sns', + 'prefix' => 'https://sqs.eu-west-1.amazonaws.com/13245', + 'queue' => 'default', + 'region' => 'eu-west-1', + ]); + + $this->assertEquals('https://sqs.eu-west-1.amazonaws.com/13245/default', $queue->getQueue(null)); + } + + /** @test */ + public function it_supports_null_aws_credentials() + { + $queue = (new SqsSnsConnector)->connect([ + 'driver' => 'sqs-sns', + 'key' => null, + 'secret' => null, + 'prefix' => 'https://sqs.eu-west-1.amazonaws.com/13245', + 'queue' => 'default', + 'region' => 'eu-west-1', + ]); + + $this->assertEquals('https://sqs.eu-west-1.amazonaws.com/13245/default', $queue->getQueue(null)); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index c347c15..96ade20 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,7 +19,7 @@ abstract class TestCase extends Orchestra * @param \Illuminate\Foundation\Application $app * @return array */ - protected function getPackageProviders($app) + protected function getPackageProviders($app): array { return [ AwsPubSubServiceProvider::class, @@ -27,34 +27,15 @@ protected function getPackageProviders($app) ]; } - /** - * @param \Illuminate\Foundation\Application $app - * @return void - */ - protected function setTestDatabase($app) - { - /** DATABASE */ - $app['config']->set('database.default', 'testbench'); - $app['config']->set('database.connections.testbench', [ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '', - ]); - } - /** * Define environment setup. * * @param \Illuminate\Foundation\Application $app * @return void */ - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { - $this->setSnsBroadcaster($app); - - $this->setSubQueue($app); - - $this->setTestDatabase($app); + $this->configureTestDatabase($app); } /** @@ -69,52 +50,15 @@ protected function defineDatabaseMigrations() /** * @param \Illuminate\Foundation\Application $app + * @return void */ - protected function setSnsBroadcaster($app): void - { - /** PUB */ - $app['config']->set('broadcasting.default', 'sns'); - $app['config']->set('broadcasting.connections.sns', [ - 'driver' => 'sns', - 'key' => 'dummy-key', - 'secret' => 'dummy-secret', - 'arn-prefix' => 'aws:arn:12345:', - 'arn-suffix' => '', - 'region' => 'eu-west-1', - ]); - } - - /** - * @param \Illuminate\Foundation\Application $app - */ - protected function setEventBridgeBroadcaster($app) - { - /** PUB */ - $app['config']->set('broadcasting.default', 'eventbridge'); - $app['config']->set('broadcasting.connections.eventbridge', [ - 'driver' => 'eventbridge', - 'key' => 'dummy-key', - 'secret' => 'dummy-secret', - 'region' => 'eu-west-1', - 'event_bus' => 'default', - 'source' => 'my-app', - ]); - } - - /** - * @param \Illuminate\Foundation\Application $app - */ - protected function setSubQueue($app): void + protected function configureTestDatabase($app) { - /** SUB */ - $app['config']->set('queue.connections.pub-sub', [ - 'driver' => 'sqs-sns', - 'key' => 'dummy-key', - 'secret' => 'dummy-secret', - 'prefix' => 'https://sqs.eu-west-1.amazonaws.com/13245', - 'queue' => 'default', - 'suffix' => '', - 'region' => 'eu-west-1', + $app->config->set('database.default', 'testbench'); + $app->config->set('database.connections.testbench', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', ]); } }